file-temp 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,254 @@
1
+ require 'ffi'
2
+ require 'tmpdir'
3
+
4
+ class File::Temp < File
5
+ extend FFI::Library
6
+ ffi_lib FFI::Library::LIBC
7
+
8
+ # :stopdoc:
9
+
10
+ private
11
+
12
+ if File::ALT_SEPARATOR
13
+ attach_function :_close, [:int], :int
14
+ attach_function :fclose, [:pointer], :int
15
+ attach_function :_fdopen, [:int, :string], :pointer
16
+ attach_function :_fileno, [:pointer], :int
17
+ attach_function :_get_errno, [:pointer], :int
18
+ attach_function :_open, [:string, :int, :int], :int
19
+ attach_function :_open_osfhandle, [:long, :int], :int
20
+ attach_function :tmpnam_s, [:pointer, :size_t], :int
21
+ attach_function :mktemp_s, :_mktemp_s, [:pointer, :size_t], :int
22
+
23
+ private_class_method :_close, :_fdopen, :_get_errno, :_open
24
+ private_class_method :_open_osfhandle, :mktemp_s, :tmpnam_s
25
+
26
+ ffi_lib :kernel32
27
+
28
+ attach_function :CloseHandle, [:long], :bool
29
+ attach_function :CreateFileW, [:buffer_in, :ulong, :ulong, :pointer, :ulong, :ulong, :ulong], :long
30
+ attach_function :DeleteFileW, [:string], :bool
31
+ attach_function :GetTempPathW, [:ulong, :buffer_out], :ulong
32
+ attach_function :GetTempFileNameW, [:buffer_in, :string, :uint, :buffer_out], :uint
33
+
34
+ private_class_method :_close, :_fdopen, :_open, :_open_osfhandle
35
+ private_class_method :CloseHandle, :CreateFileW, :DeleteFileW
36
+ private_class_method :GetTempPathW, :GetTempFileNameW
37
+
38
+ S_IWRITE = 128
39
+ S_IREAD = 256
40
+ BINARY = 0x8000
41
+ SHORT_LIVED = 0x1000
42
+ GENERIC_READ = 0x80000000
43
+ GENERIC_WRITE = 0x40000000
44
+ CREATE_ALWAYS = 2
45
+
46
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
47
+ FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
48
+
49
+ FILE_ATTRIBUTE_NORMAL = 0x00000080
50
+ FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
51
+ INVALID_HANDLE_VALUE = -1
52
+ else
53
+ attach_function :fclose, [:pointer], :int
54
+ attach_function :_fileno, :fileno, [:pointer], :int
55
+ attach_function :strerror, [:int], :string
56
+ attach_function :tmpfile, [], :pointer
57
+ attach_function :tmpnam, [:pointer], :string
58
+ attach_function :mktemp, [:pointer], :string
59
+
60
+ private_class_method :mktemp, :strerror, :tmpfile, :tmpnam
61
+ end
62
+
63
+ private_class_method :fclose, :_fileno
64
+
65
+ public
66
+
67
+ # :startdoc:
68
+
69
+ # The version of the file-temp library.
70
+ VERSION = '1.2.1'
71
+
72
+ # The temporary directory used on MS Windows or Unix.
73
+ if File::ALT_SEPARATOR
74
+ TMPDIR = ENV['TEMP'] || ENV['TMP'] || ENV['USERPROFILE'] || Dir.tmpdir
75
+ else
76
+ TMPDIR = ENV['TEMP'] || ENV['TMP'] || ENV['TMPDIR'] || Dir.tmpdir
77
+ end
78
+
79
+ # The name of the temporary file. Set to nil if the +delete+ option to the
80
+ # constructor is true.
81
+ attr_reader :path
82
+
83
+ # Creates a new, anonymous, temporary file in your File::Temp::TMPDIR
84
+ # directory
85
+ #
86
+ # If the +delete+ option is set to true (the default) then the temporary file
87
+ # will be deleted automatically as soon as all references to it are closed.
88
+ # Otherwise, the file will live on in your File::Temp::TMPDIR path.
89
+ #
90
+ # If the +delete+ option is set to false, then the file is not deleted. In
91
+ # addition, you can supply a string +template+ that the system replaces with
92
+ # a unique filename. This template should end with 3 to 6 'X' characters.
93
+ # The default template is 'rb_file_temp_XXXXXX'. In this case the temporary
94
+ # file lives in the directory where it was created.
95
+ #
96
+ # The +template+ argument is ignored if the +delete+ argument is true.
97
+ #
98
+ # Example:
99
+ #
100
+ # fh = File::Temp.new(true, 'rb_file_temp_XXXXXX') => file
101
+ # fh.puts 'hello world'
102
+ # fh.close
103
+ #
104
+ def initialize(delete = true, template = 'rb_file_temp_XXXXXX')
105
+ @fptr = nil
106
+
107
+ if delete
108
+ @fptr = tmpfile()
109
+ fd = _fileno(@fptr)
110
+ else
111
+ begin
112
+ omask = File.umask(077)
113
+
114
+ ptr = FFI::MemoryPointer.from_string(template)
115
+
116
+ if File::ALT_SEPARATOR
117
+ errno = mktemp_s(ptr, ptr.size)
118
+
119
+ raise SystemCallError.new('mktemp_s', errno) if errno != 0
120
+ else
121
+ str = mktemp(ptr)
122
+
123
+ if str.nil? || str.empty?
124
+ raise SystemCallError.new('mktemp', FFI.errno)
125
+ end
126
+ end
127
+
128
+ @path = File.join(TMPDIR, ptr.read_string)
129
+ @path.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
130
+ ensure
131
+ File.umask(omask)
132
+ end
133
+ end
134
+
135
+ if delete
136
+ super(fd, 'wb+')
137
+ else
138
+ super(@path, 'wb+')
139
+ end
140
+ end
141
+
142
+ # The close method was overridden to ensure the internal file pointer we
143
+ # created in the constructor is closed. It is otherwise identical to the
144
+ # File#close method.
145
+ #
146
+ def close
147
+ super
148
+ fclose(@fptr) if @fptr
149
+ end
150
+
151
+ # Generates a unique file name.
152
+ #
153
+ # Note that a file is not actually generated on the filesystem.
154
+ #--
155
+ # NOTE: One quirk of the Windows function is that, after the first call, it
156
+ # adds a file extension of sequential numbers in base 32, e.g. .1-.1vvvvvu.
157
+ #
158
+ def self.temp_name
159
+ if File::ALT_SEPARATOR
160
+ ptr = FFI::MemoryPointer.new(:char, 1024)
161
+ errno = tmpnam_s(ptr, ptr.size)
162
+
163
+ raise SystemCallError.new('tmpnam_s', errno) if errno != 0
164
+
165
+ TMPDIR + ptr.read_string + 'tmp'
166
+ else
167
+ tmpnam(nil) << '.tmp'
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ # For those times when we want the posix errno rather than a formatted string.
174
+ # This is necessary because FFI.errno appears to be using GetLastError() which
175
+ # does not always match what _get_errno() returns.
176
+ #
177
+ def get_posix_errno
178
+ if File::ALT_SEPARATOR
179
+ ptr = FFI::MemoryPointer.new(:int)
180
+ _get_errno(ptr)
181
+ ptr.read_int
182
+ else
183
+ FFI.errno
184
+ end
185
+ end
186
+
187
+ if File::ALT_SEPARATOR
188
+ # Simple wrapper around the GetTempPath function.
189
+ #
190
+ def get_temp_path
191
+ buf = 0.chr * 1024
192
+ buf.encode!("UTF-16LE")
193
+
194
+ if GetTempPathW(buf.size, buf) == 0
195
+ raise SystemCallError, FFI.errno, 'GetTempPathW'
196
+ end
197
+
198
+ buf.strip.chop # remove trailing slash
199
+ end
200
+
201
+ # The version of tmpfile() implemented by Microsoft is unacceptable.
202
+ # It attempts to write to C:\ (root) instead of a temporary directory.
203
+ # This is not only bad behavior, it won't work on Windows 7 and later
204
+ # without admin rights due to security restrictions.
205
+ #
206
+ # This is a custom implementation based on some code from the Cairo
207
+ # project.
208
+ #
209
+ def tmpfile
210
+ file_name = get_temp_path()
211
+ buf = 0.chr * 1024
212
+ buf.encode!("UTF-16LE")
213
+
214
+ if GetTempFileNameW(file_name, 'rb_', 0, buf) == 0
215
+ raise SystemCallError, FFI.errno, 'GetTempFileNameW'
216
+ end
217
+
218
+ file_name = buf.strip
219
+
220
+ handle = CreateFileW(
221
+ file_name,
222
+ GENERIC_READ | GENERIC_WRITE,
223
+ 0,
224
+ nil,
225
+ CREATE_ALWAYS,
226
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
227
+ 0
228
+ )
229
+
230
+ if handle == INVALID_HANDLE_VALUE
231
+ error = FFI.errno
232
+ DeleteFileW(file_name)
233
+ raise SystemCallError.new('CreateFileW', error)
234
+ end
235
+
236
+ fd = _open_osfhandle(handle, 0)
237
+
238
+ if fd < 0
239
+ CloseHandle(handle)
240
+ raise SystemCallError, get_posix_errno, '_open_osfhandle'
241
+ end
242
+
243
+ fp = _fdopen(fd, 'w+b')
244
+
245
+ if fp.nil?
246
+ _close(fd)
247
+ CloseHandle(handle)
248
+ raise SystemCallError, get_posix_errno, 'fdopen'
249
+ end
250
+
251
+ fp
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,76 @@
1
+ require 'java'
2
+ import java.lang.System
3
+
4
+ class File::Temp < File
5
+ # The version of the file-temp library.
6
+ VERSION = '1.2.1'
7
+
8
+ # The temporary directory used on MS Windows or Unix.
9
+ TMPDIR = java.lang.System.getProperties["java.io.tmpdir"]
10
+
11
+ # The name of the temporary file.
12
+ attr_reader :path
13
+
14
+ # Creates a new, anonymous, temporary file in your File::Temp::TMPDIR
15
+ # directory.
16
+ #
17
+ # If the +delete+ option is set to true (the default) then the temporary file
18
+ # will be deleted automatically as soon as all references to it are closed.
19
+ # Otherwise, the file will live on in your File::Temp::TMPDIR path.
20
+ #
21
+ # If the +delete+ option is set to false, then the file is not deleted. In
22
+ # addition, you can supply a string +template+ that the system replaces with
23
+ # a unique filename. This template should end with 3 to 6 'X' characters.
24
+ # The default template is 'rb_file_temp_XXXXXX'. In this case the temporary
25
+ # file lives in the directory where it was created.
26
+ #
27
+ # Note that when using JRuby the template naming is not as strict, and the
28
+ # trailing 'X' characters are simply replaced with the GUID that Java
29
+ # generates for unique file names.
30
+ #
31
+ # Example:
32
+ #
33
+ # fh = File::Temp.new(true, 'rb_file_temp_XXXXXX') => file
34
+ # fh.puts 'hello world'
35
+ # fh.close
36
+ #
37
+ def initialize(delete = true, template = 'rb_file_temp_XXXXXX')
38
+ raise TypeError unless template.is_a?(String)
39
+
40
+ # Since Java uses a GUID extension to generate a unique file name
41
+ # we'll simply chop off the 'X' characters and let Java do the rest.
42
+ template = template.sub(/_X{1,6}/, '_')
43
+
44
+ # For consistency between implementations, convert errors here
45
+ # to Errno::EINVAL.
46
+ begin
47
+ @file = java.io.File.createTempFile(template, nil)
48
+ rescue NativeException => err
49
+ raise SystemCallError.new(22), template # 22 is EINVAL
50
+ end
51
+
52
+ @file.deleteOnExit if delete
53
+
54
+ @path = @file.getName
55
+
56
+ super(@path, 'wb+')
57
+ end
58
+
59
+ # Generates a unique file name.
60
+ #
61
+ def self.temp_name
62
+ file = java.io.File.createTempFile('rb_file_temp_', nil)
63
+ file.deleteOnExit
64
+ name = file.getName
65
+ file.finalize
66
+ name
67
+ end
68
+
69
+ # Identical to the File#close method except that we also finalize
70
+ # the underlying Java File object.
71
+ #
72
+ def close
73
+ super
74
+ @file.finalize
75
+ end
76
+ end
@@ -1,120 +1,135 @@
1
- ######################################################################
2
- # test_file_temp.rb
3
- #
4
- # Test suite for the file-temp library. These tests should be run
5
- # via the 'rake test' task.
6
- ######################################################################
7
- require 'rubygems'
8
- require 'test-unit'
9
- require 'file/temp'
10
-
11
- class TC_File_Temp < Test::Unit::TestCase
12
- WINDOWS = File::ALT_SEPARATOR
13
-
14
- def setup
15
- @dir = File::Temp::TMPDIR
16
- @template = 'file-temp-test-XXXXX'
17
- @fh = nil
18
-
19
- # Because Dir[] doesn't work right with backslashes
20
- @dir = @dir.tr("\\", "/") if WINDOWS
21
- end
22
-
23
- def test_file_temp_version
24
- assert_equal('1.2.0', File::Temp::VERSION)
25
- end
26
-
27
- def test_file_temp_threaded
28
- threads = []
29
- assert_nothing_raised{ 100.times{ threads << Thread.new{ File::Temp.new }}}
30
- assert_nothing_raised{ threads.join }
31
- end
32
-
33
- def test_file_temp_tmpdir
34
- assert_not_nil(File::Temp::TMPDIR)
35
- assert_kind_of(String, File::Temp::TMPDIR)
36
- end
37
-
38
- def test_file_temp_auto_delete
39
- assert_nothing_raised{ @fh = File::Temp.new }
40
- assert_nothing_raised{ @fh.print "hello" }
41
- assert_nothing_raised{ @fh.close }
42
- end
43
-
44
- def test_file_temp_no_delete
45
- assert_nothing_raised{ @fh = File::Temp.new(false) }
46
- assert_nothing_raised{ @fh.print "hello" }
47
- assert_nothing_raised{ @fh.close }
48
- assert_true(Dir["#{@dir}/rb_file_temp*"].length == 1)
49
- end
50
-
51
- def test_file_temp_no_delete_with_template
52
- assert_nothing_raised{ File::Temp.new(false, 'temp_foo_XXXXXX').close }
53
- assert_true(Dir["#{@dir}/temp_foo*"].length >= 1)
54
- end
55
-
56
- def test_file_temp_no_delete_with_block
57
- assert_nothing_raised{ File::Temp.open(false, 'temp_foo_XXXXXX'){ |fh| fh.puts "hello" } }
58
- assert_true(Dir["#{@dir}/temp_foo*"].length >= 1)
59
- end
60
-
61
- def test_file_temp_expected_errors
62
- assert_raise(TypeError, ArgumentError){ @fh = File::Temp.new(false, 1) }
63
- assert_raise(ArgumentError){ @fh = File::Temp.new(true, 'temp_bar_XXXXX', 1) }
64
- end
65
-
66
- def test_file_temp_name_basic_functionality
67
- assert_respond_to(File::Temp, :temp_name)
68
- assert_nothing_raised{ File::Temp.temp_name }
69
- assert_kind_of(String, File::Temp.temp_name)
70
- end
71
-
72
- def test_file_temp_name
73
- assert_equal('.tmp', File.extname(File::Temp.temp_name))
74
- end
75
-
76
- def test_file_temp_path_basic_functionality
77
- @fh = File::Temp.new
78
- assert_respond_to(@fh, :path)
79
- end
80
-
81
- def test_file_temp_path_is_nil_if_delete_option_is_true
82
- @fh = File::Temp.new
83
- assert_nil(@fh.path)
84
- end
85
-
86
- def test_file_temp_path_is_not_nil_if_delete_option_is_false
87
- @fh = File::Temp.new(false)
88
- assert_not_nil(@fh.path)
89
- end
90
-
91
- def test_ffi_functions_are_private
92
- methods = File::Temp.methods(false).map{ |e| e.to_s }
93
- assert_false(methods.include?('_fileno'))
94
- assert_false(methods.include?('mkstemp'))
95
- assert_false(methods.include?('_umask'))
96
- assert_false(methods.include?('fclose'))
97
- assert_false(methods.include?('strerror'))
98
- assert_false(methods.include?('tmpnam'))
99
- assert_false(methods.include?('CloseHandle'))
100
- assert_false(methods.include?('CreateFileA'))
101
- assert_false(methods.include?('DeleteFileA'))
102
- assert_false(methods.include?('GetTempPathA'))
103
- assert_false(methods.include?('GetTempFileNameA'))
104
- end
105
-
106
- def teardown
107
- @dir = nil
108
- @template = nil
109
- @fh.close if @fh && !@fh.closed?
110
- @fh = nil
111
-
112
- Dir["temp_*"].each{ |f| File.delete(f) }
113
- Dir["rb_file_temp_*"].each{ |f| File.delete(f) }
114
-
115
- Dir.chdir(File::Temp::TMPDIR) do
116
- Dir["temp_*"].each{ |f| File.delete(f) }
117
- Dir["rb_file_temp_*"].each{ |f| File.delete(f) }
118
- end
119
- end
120
- end
1
+ ######################################################################
2
+ # test_file_temp.rb
3
+ #
4
+ # Test suite for the file-temp library. These tests should be run
5
+ # via the 'rake test' task.
6
+ ######################################################################
7
+ require 'rubygems'
8
+ require 'test-unit'
9
+ require 'file/temp'
10
+
11
+ class TC_File_Temp < Test::Unit::TestCase
12
+ WINDOWS = File::ALT_SEPARATOR
13
+
14
+ def setup
15
+ @dir = File::Temp::TMPDIR
16
+ @template = 'file-temp-test-XXXXX'
17
+ @fh = nil
18
+
19
+ # Because Dir[] doesn't work right with backslashes
20
+ @dir = @dir.tr("\\", "/") if WINDOWS
21
+ end
22
+
23
+ test "library version is set to expected value" do
24
+ assert_equal('1.2.1', File::Temp::VERSION)
25
+ end
26
+
27
+ # Fails with JRuby, not sure why.
28
+ test "library works as expected with multiple threads" do
29
+ threads = []
30
+ assert_nothing_raised{ 100.times{ threads << Thread.new{ File::Temp.new }}}
31
+ assert_nothing_raised{ threads.each{ |t| t.join } }
32
+ end
33
+
34
+ test "TMPDIR constant is defined" do
35
+ assert_not_nil(File::Temp::TMPDIR)
36
+ assert_kind_of(String, File::Temp::TMPDIR)
37
+ end
38
+
39
+ test "constructor works as expected with default auto delete option" do
40
+ assert_nothing_raised{
41
+ @fh = File::Temp.new
42
+ @fh.print "hello"
43
+ @fh.close
44
+ }
45
+ end
46
+
47
+ test "constructor works as expected with false auto delete option" do
48
+ assert_nothing_raised{
49
+ @fh = File::Temp.new(false)
50
+ @fh.print "hello"
51
+ @fh.close
52
+ }
53
+ end
54
+
55
+ test "constructor accepts and uses an optional template as expected" do
56
+ assert_nothing_raised{ File::Temp.new(false, 'temp_foo_XXXXXX').close }
57
+ assert_true(Dir["#{@dir}/temp_foo*"].length >= 1)
58
+ end
59
+
60
+ test "constructor with false auto delete and block works as expected" do
61
+ assert_nothing_raised{ File::Temp.open(false, 'temp_foo_XXXXXX'){ |fh| fh.puts "hello" } }
62
+ assert_true(Dir["#{@dir}/temp_foo*"].length >= 1)
63
+ end
64
+
65
+ test "second argument to constructor must be a string" do
66
+ assert_raise(TypeError, ArgumentError){ @fh = File::Temp.new(false, 1) }
67
+ end
68
+
69
+ test "an error is raised if a custom template is invalid" do
70
+ assert_raise(Errno::EINVAL){ File::Temp.new(false, 'xx') }
71
+ end
72
+
73
+ test "constructor accepts a maximum of two arguments" do
74
+ assert_raise(ArgumentError){ @fh = File::Temp.new(true, 'temp_bar_XXXXX', 1) }
75
+ end
76
+
77
+ test "temp_name basic functionality" do
78
+ assert_respond_to(File::Temp, :temp_name)
79
+ assert_nothing_raised{ File::Temp.temp_name }
80
+ assert_kind_of(String, File::Temp.temp_name)
81
+ end
82
+
83
+ test "temp_name returns expected value" do
84
+ if File::ALT_SEPARATOR
85
+ assert_match(/^.*?\d*?tmp/, File.extname(File::Temp.temp_name))
86
+ else
87
+ assert_equal('.tmp', File.extname(File::Temp.temp_name))
88
+ end
89
+ end
90
+
91
+ test "temp path basic functionality" do
92
+ @fh = File::Temp.new
93
+ assert_respond_to(@fh, :path)
94
+ end
95
+
96
+ test "temp path is nil if delete option is true" do
97
+ @fh = File::Temp.new
98
+ assert_nil(@fh.path)
99
+ end
100
+
101
+ test "temp path is not nil if delete option is false" do
102
+ @fh = File::Temp.new(false)
103
+ assert_not_nil(@fh.path)
104
+ end
105
+
106
+ test "ffi functions are private" do
107
+ methods = File::Temp.methods(false).map{ |e| e.to_s }
108
+ assert_false(methods.include?('_fileno'))
109
+ assert_false(methods.include?('mkstemp'))
110
+ assert_false(methods.include?('_umask'))
111
+ assert_false(methods.include?('fclose'))
112
+ assert_false(methods.include?('strerror'))
113
+ assert_false(methods.include?('tmpnam'))
114
+ assert_false(methods.include?('CloseHandle'))
115
+ assert_false(methods.include?('CreateFileA'))
116
+ assert_false(methods.include?('DeleteFileA'))
117
+ assert_false(methods.include?('GetTempPathA'))
118
+ assert_false(methods.include?('GetTempFileNameA'))
119
+ end
120
+
121
+ def teardown
122
+ @dir = nil
123
+ @template = nil
124
+ @fh.close if @fh && !@fh.closed?
125
+ @fh = nil
126
+
127
+ Dir["temp_*"].each{ |f| File.delete(f) }
128
+ Dir["rb_file_temp_*"].each{ |f| File.delete(f) }
129
+
130
+ Dir.chdir(File::Temp::TMPDIR) do
131
+ Dir["temp_*"].each{ |f| File.delete(f) }
132
+ Dir["rb_file_temp_*"].each{ |f| File.delete(f) }
133
+ end
134
+ end
135
+ end