file-temp 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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