logstash-input-file 4.1.3 → 4.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/JAR_VERSION +1 -1
  4. data/README.md +0 -3
  5. data/docs/index.asciidoc +26 -16
  6. data/lib/filewatch/bootstrap.rb +10 -21
  7. data/lib/filewatch/discoverer.rb +35 -28
  8. data/lib/filewatch/observing_base.rb +2 -1
  9. data/lib/filewatch/read_mode/handlers/base.rb +19 -6
  10. data/lib/filewatch/read_mode/handlers/read_file.rb +43 -32
  11. data/lib/filewatch/read_mode/handlers/read_zip_file.rb +8 -3
  12. data/lib/filewatch/read_mode/processor.rb +8 -8
  13. data/lib/filewatch/settings.rb +3 -3
  14. data/lib/filewatch/sincedb_collection.rb +56 -42
  15. data/lib/filewatch/sincedb_value.rb +6 -0
  16. data/lib/filewatch/stat/generic.rb +34 -0
  17. data/lib/filewatch/stat/windows_path.rb +32 -0
  18. data/lib/filewatch/tail_mode/handlers/base.rb +40 -22
  19. data/lib/filewatch/tail_mode/handlers/create.rb +1 -2
  20. data/lib/filewatch/tail_mode/handlers/create_initial.rb +2 -1
  21. data/lib/filewatch/tail_mode/handlers/delete.rb +13 -1
  22. data/lib/filewatch/tail_mode/handlers/grow.rb +5 -2
  23. data/lib/filewatch/tail_mode/handlers/shrink.rb +7 -4
  24. data/lib/filewatch/tail_mode/handlers/unignore.rb +4 -2
  25. data/lib/filewatch/tail_mode/processor.rb +147 -58
  26. data/lib/filewatch/watch.rb +15 -35
  27. data/lib/filewatch/watched_file.rb +237 -41
  28. data/lib/filewatch/watched_files_collection.rb +2 -2
  29. data/lib/filewatch/winhelper.rb +167 -25
  30. data/lib/jars/filewatch-1.0.1.jar +0 -0
  31. data/lib/logstash/inputs/file.rb +9 -2
  32. data/logstash-input-file.gemspec +9 -2
  33. data/spec/file_ext/file_ext_windows_spec.rb +36 -0
  34. data/spec/filewatch/read_mode_handlers_read_file_spec.rb +2 -2
  35. data/spec/filewatch/reading_spec.rb +100 -57
  36. data/spec/filewatch/rotate_spec.rb +451 -0
  37. data/spec/filewatch/spec_helper.rb +33 -10
  38. data/spec/filewatch/tailing_spec.rb +273 -153
  39. data/spec/filewatch/watched_file_spec.rb +3 -3
  40. data/spec/filewatch/watched_files_collection_spec.rb +3 -3
  41. data/spec/filewatch/winhelper_spec.rb +4 -5
  42. data/spec/helpers/logging_level_helper.rb +8 -0
  43. data/spec/helpers/rspec_wait_handler_helper.rb +38 -0
  44. data/spec/helpers/spec_helper.rb +7 -1
  45. data/spec/inputs/file_read_spec.rb +54 -24
  46. data/spec/inputs/file_tail_spec.rb +244 -284
  47. metadata +13 -3
  48. data/lib/jars/filewatch-1.0.0.jar +0 -0
@@ -2,31 +2,163 @@
2
2
 
3
3
  module FileWatch
4
4
  class WatchedFile
5
- include InodeMixin # see bootstrap.rb at `if LogStash::Environment.windows?`
5
+ PATH_BASED_STAT = 0
6
+ IO_BASED_STAT = 1
6
7
 
7
- attr_reader :bytes_read, :state, :file, :buffer, :recent_states
8
- attr_reader :path, :filestat, :accessed_at, :modified_at, :pathname
9
- attr_reader :sdb_key_v1, :last_stat_size, :listener
8
+ attr_reader :bytes_read, :state, :file, :buffer, :recent_states, :bytes_unread
9
+ attr_reader :path, :accessed_at, :modified_at, :pathname, :filename
10
+ attr_reader :listener, :read_loop_count, :read_chunk_size, :stat
11
+ attr_reader :loop_count_type, :loop_count_mode
10
12
  attr_accessor :last_open_warning_at
11
13
 
12
14
  # this class represents a file that has been discovered
15
+ # path based stat is taken at discovery
13
16
  def initialize(pathname, stat, settings)
14
17
  @settings = settings
15
18
  @pathname = Pathname.new(pathname) # given arg pathname might be a string or a Pathname object
16
19
  @path = @pathname.to_path
17
- @bytes_read = 0
18
- @last_stat_size = 0
19
- # the prepare_inode method is sourced from the mixed module above
20
- @sdb_key_v1 = InodeStruct.new(*prepare_inode(path, stat))
20
+ @filename = @pathname.basename.to_s
21
+ full_state_reset(stat)
22
+ watch
23
+ set_standard_read_loop
24
+ set_accessed_at
25
+ end
26
+
27
+ def no_restat_reset
28
+ full_state_reset(@stat)
29
+ end
30
+
31
+ def full_state_reset(this_stat = nil)
32
+ if this_stat.nil?
33
+ begin
34
+ this_stat = PathStatClass.new(pathname)
35
+ rescue Errno::ENOENT
36
+ delay_delete
37
+ return
38
+ end
39
+ end
40
+ @bytes_read = 0 # tracks bytes read from the open file or initialized from a matched sincedb_value off disk.
41
+ @bytes_unread = 0 # tracks bytes not yet read from the open file. So we can warn on shrink when unread bytes are seen.
42
+ file_close
43
+ set_stat(this_stat)
44
+ @listener = nil
45
+ @last_open_warning_at = nil
21
46
  # initial as true means we have not associated this watched_file with a previous sincedb value yet.
22
47
  # and we should read from the beginning if necessary
23
48
  @initial = true
24
49
  @recent_states = [] # keep last 8 states, managed in set_state
25
- @state = :watched
26
- set_stat(stat) # can change @last_stat_size
50
+ # the prepare_inode method is sourced from the mixed module above
51
+ watch if active? || @state.nil?
52
+ end
53
+
54
+ def rotate_from(other)
55
+ # move all state from other to this one
56
+ set_standard_read_loop
57
+ file_close
58
+ @bytes_read = other.bytes_read
59
+ @bytes_unread = other.bytes_unread
27
60
  @listener = nil
61
+ @initial = false
62
+ @recent_states = other.recent_states
63
+ @accessed_at = other.accessed_at
64
+ if !other.delayed_delete?
65
+ # we don't know if a file exists at the other.path yet
66
+ # so no reset
67
+ other.full_state_reset
68
+ end
69
+ set_stat PathStatClass.new(pathname)
70
+ ignore
71
+ end
72
+
73
+ def set_stat(stat)
74
+ @stat = stat
75
+ @size = @stat.size
76
+ @sdb_key_v1 = @stat.inode_struct
77
+ end
78
+
79
+ def rotate_as_initial_file
80
+ # rotation, when no sincedb record exists for new inode - we have never seen this inode before.
81
+ rotate_as_file
82
+ @initial = true
83
+ end
84
+
85
+ def rotate_as_file(bytes_read = 0)
86
+ # rotation, when a sincedb record exists for new inode, but no watched file to rotate from
87
+ # probably caused by a deletion detected in the middle of the rename cascade
88
+ # RARE due to delayed_delete - there would have to be a large time span between the renames.
89
+ @bytes_read = bytes_read # tracks bytes read from the open file or initialized from a matched sincedb_value off disk.
90
+ @bytes_unread = 0 # tracks bytes not yet read from the open file. So we can warn on shrink when unread bytes are seen.
28
91
  @last_open_warning_at = nil
29
- set_accessed_at
92
+ # initial as true means we have not associated this watched_file with a previous sincedb value yet.
93
+ # and we should read from the beginning if necessary
94
+ @initial = false
95
+ @recent_states = [] # keep last 8 states, managed in set_state
96
+ set_stat(PathStatClass.new(pathname))
97
+ reopen
98
+ watch
99
+ end
100
+
101
+ def stat_sincedb_key
102
+ @stat.inode_struct
103
+ end
104
+
105
+ def rotation_detected?
106
+ stat_sincedb_key != sincedb_key
107
+ end
108
+
109
+ def restat
110
+ @stat.restat
111
+ if rotation_detected?
112
+ # switch to new state now
113
+ rotation_in_progress
114
+ else
115
+ @size = @stat.size
116
+ update_bytes_unread
117
+ end
118
+ end
119
+
120
+ def modified_at
121
+ @stat.modified_at
122
+ end
123
+
124
+ def position_for_new_sincedb_value
125
+ if @initial
126
+ # this file was found in first discovery
127
+ @settings.start_new_files_at == :beginning ? 0 : last_stat_size
128
+ else
129
+ # always start at the beginning if found after first discovery
130
+ 0
131
+ end
132
+ end
133
+
134
+ def last_stat_size
135
+ @stat.size
136
+ end
137
+
138
+ def current_size
139
+ @size
140
+ end
141
+
142
+ def shrunk?
143
+ @size < @bytes_read
144
+ end
145
+
146
+ def grown?
147
+ @size > @bytes_read
148
+ end
149
+
150
+ def size_changed?
151
+ # called from closed and ignored
152
+ # before the last stat was taken file should be fully read.
153
+ @size != @bytes_read
154
+ end
155
+
156
+ def all_read?
157
+ @bytes_read >= @size
158
+ end
159
+
160
+ def file_at_path_found_again
161
+ restore_previous_state
30
162
  end
31
163
 
32
164
  def set_listener(observer)
@@ -61,12 +193,11 @@ module FileWatch
61
193
  @path.end_with?('.gz','.gzip')
62
194
  end
63
195
 
64
- def size_changed?
65
- @last_stat_size != bytes_read
66
- end
67
-
68
- def all_read?
69
- @last_stat_size == bytes_read
196
+ def reopen
197
+ if file_open?
198
+ file_close
199
+ open
200
+ end
70
201
  end
71
202
 
72
203
  def open
@@ -88,9 +219,9 @@ module FileWatch
88
219
  @file.sysseek(amount, whence)
89
220
  end
90
221
 
91
- def file_read(amount)
222
+ def file_read(amount = nil)
92
223
  set_accessed_at
93
- @file.sysread(amount)
224
+ @file.sysread(amount || @read_chunk_size)
94
225
  end
95
226
 
96
227
  def file_open?
@@ -101,6 +232,13 @@ module FileWatch
101
232
  @buffer.flush
102
233
  end
103
234
 
235
+ def read_extract_lines(amount)
236
+ data = file_read(amount)
237
+ result = buffer_extract(data)
238
+ increment_bytes_read(data.bytesize)
239
+ result
240
+ end
241
+
104
242
  def buffer_extract(data)
105
243
  warning, additional = "", {}
106
244
  lines = @buffer.extract(data)
@@ -112,7 +250,7 @@ module FileWatch
112
250
  additional["delimiter"] = @settings.delimiter
113
251
  additional["read_position"] = @bytes_read
114
252
  additional["bytes_read_count"] = data.bytesize
115
- additional["last_known_file_size"] = @last_stat_size
253
+ additional["last_known_file_size"] = last_stat_size
116
254
  additional["file_path"] = @path
117
255
  end
118
256
  BufferExtractResult.new(lines, warning, additional)
@@ -121,19 +259,19 @@ module FileWatch
121
259
  def increment_bytes_read(delta)
122
260
  return if delta.nil?
123
261
  @bytes_read += delta
262
+ update_bytes_unread
263
+ @bytes_read
124
264
  end
125
265
 
126
266
  def update_bytes_read(total_bytes_read)
127
267
  return if total_bytes_read.nil?
128
268
  @bytes_read = total_bytes_read
269
+ update_bytes_unread
270
+ @bytes_read
129
271
  end
130
272
 
131
- def update_path(_path)
132
- @path = _path
133
- end
134
-
135
- def update_stat(st)
136
- set_stat(st)
273
+ def rotation_in_progress
274
+ set_state :rotation_in_progress
137
275
  end
138
276
 
139
277
  def activate
@@ -142,7 +280,11 @@ module FileWatch
142
280
 
143
281
  def ignore
144
282
  set_state :ignored
145
- @bytes_read = @filestat.size
283
+ end
284
+
285
+ def ignore_as_unread
286
+ ignore
287
+ @bytes_read = @size
146
288
  end
147
289
 
148
290
  def close
@@ -157,10 +299,26 @@ module FileWatch
157
299
  set_state :unwatched
158
300
  end
159
301
 
302
+ def delay_delete
303
+ set_state :delayed_delete
304
+ end
305
+
306
+ def restore_previous_state
307
+ set_state @recent_states.pop
308
+ end
309
+
310
+ def rotation_in_progress?
311
+ @state == :rotation_in_progress
312
+ end
313
+
160
314
  def active?
161
315
  @state == :active
162
316
  end
163
317
 
318
+ def delayed_delete?
319
+ @state == :delayed_delete
320
+ end
321
+
164
322
  def ignored?
165
323
  @state == :ignored
166
324
  end
@@ -185,21 +343,49 @@ module FileWatch
185
343
  !@settings.ignore_older.nil?
186
344
  end
187
345
 
188
- def shrunk?
189
- @last_stat_size < @bytes_read
190
- end
191
-
192
- def grown?
193
- @last_stat_size > @bytes_read
346
+ def set_standard_read_loop
347
+ @read_loop_count = @settings.file_chunk_count
348
+ @read_chunk_size = @settings.file_chunk_size
349
+ # e.g. 1 * 10 bytes -> 10 or 256 * 65536 -> 1677716 or 140737488355327 * 32768 -> 4611686018427355136
350
+ @standard_loop_max_bytes = @read_loop_count * @read_chunk_size
351
+ end
352
+
353
+ def set_maximum_read_loop
354
+ # used to quickly fully read an open file when rotation is detected
355
+ @read_loop_count = FileWatch::MAX_ITERATIONS
356
+ @read_chunk_size = FileWatch::FILE_READ_SIZE
357
+ @standard_loop_max_bytes = @read_loop_count * @read_chunk_size
358
+ end
359
+
360
+ def loop_control_adjusted_for_stat_size
361
+ more = false
362
+ to_read = current_size - @bytes_read
363
+ return LoopControlResult.new(0, 0, more) if to_read < 1
364
+ return LoopControlResult.new(1, to_read, more) if to_read < @read_chunk_size
365
+ # set as if to_read is greater than or equal to max_bytes
366
+ # use the ones from settings and don't indicate more
367
+ count = @read_loop_count
368
+ if to_read < @standard_loop_max_bytes
369
+ # if the defaults are used then this branch will be taken
370
+ # e.g. to_read is 100 and max_bytes is 4 * 30 -> 120
371
+ # will overrun and trigger EOF, build less iterations
372
+ # will generate 3 * 30 -> 90 this time and we indicate more
373
+ # a 2GB file in read mode will get one loop of 64666 x 32768 (2119006656 / 32768)
374
+ # and a second loop with 1 x 31168
375
+ count = to_read / @read_chunk_size
376
+ more = true
377
+ end
378
+ LoopControlResult.new(count, @read_chunk_size, more)
194
379
  end
195
380
 
196
- def restat
197
- set_stat(pathname.stat)
381
+ def reset_bytes_unread
382
+ # called from shrink
383
+ @bytes_unread = 0
198
384
  end
199
385
 
200
386
  def set_state(value)
201
387
  @recent_states.shift if @recent_states.size == 8
202
- @recent_states << @state
388
+ @recent_states << @state unless @state.nil?
203
389
  @state = value
204
390
  end
205
391
 
@@ -216,7 +402,7 @@ module FileWatch
216
402
  # (Time.now - stat.mtime) <- in jruby, this does int and float
217
403
  # conversions before the subtraction and returns a float.
218
404
  # so use all floats upfront
219
- (Time.now.to_f - @modified_at) > @settings.ignore_older
405
+ (Time.now.to_f - modified_at) > @settings.ignore_older
220
406
  end
221
407
 
222
408
  def file_can_close?
@@ -224,16 +410,26 @@ module FileWatch
224
410
  (Time.now.to_f - @accessed_at) > @settings.close_older
225
411
  end
226
412
 
413
+ def details
414
+ detail = "@filename='#{filename}', @state='#{state}', @recent_states='#{@recent_states.inspect}', "
415
+ detail.concat("@bytes_read='#{@bytes_read}', @bytes_unread='#{@bytes_unread}', current_size='#{current_size}', ")
416
+ detail.concat("last_stat_size='#{last_stat_size}', file_open?='#{file_open?}', @initial=#{@initial}")
417
+ "<FileWatch::WatchedFile: #{detail}, @sincedb_key='#{sincedb_key}'>"
418
+ end
419
+
420
+ def inspect
421
+ "\"<FileWatch::WatchedFile: @filename='#{filename}', @state='#{state}', @sincedb_key='#{sincedb_key}, size=#{@size}>\""
422
+ end
423
+
227
424
  def to_s
228
425
  inspect
229
426
  end
230
427
 
231
428
  private
232
429
 
233
- def set_stat(stat)
234
- @modified_at = stat.mtime.to_f
235
- @last_stat_size = stat.size
236
- @filestat = stat
430
+ def update_bytes_unread
431
+ unread = current_size - @bytes_read
432
+ @bytes_unread = unread < 0 ? 0 : unread
237
433
  end
238
434
  end
239
435
  end
@@ -6,8 +6,8 @@ module FileWatch
6
6
  @sort_by = settings.file_sort_by # "last_modified" | "path"
7
7
  @sort_direction = settings.file_sort_direction # "asc" | "desc"
8
8
  @sort_method = method("#{@sort_by}_#{@sort_direction}".to_sym)
9
- @files = []
10
- @pointers = {}
9
+ @files = Concurrent::Array.new
10
+ @pointers = Concurrent::Hash.new
11
11
  end
12
12
 
13
13
  def add(watched_file)
@@ -6,59 +6,201 @@ module Winhelper
6
6
 
7
7
  ffi_lib 'kernel32'
8
8
  ffi_convention :stdcall
9
+
9
10
  class FileTime < FFI::Struct
10
- layout :lowDateTime, :uint,
11
- :highDateTime, :uint
11
+ layout :lowDateTime, :uint, :highDateTime, :uint
12
12
  end
13
13
 
14
14
  #http://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx
15
15
  class FileInformation < FFI::Struct
16
16
  layout :fileAttributes, :uint, #DWORD dwFileAttributes;
17
- :createTime, FileTime, #FILETIME ftCreationTime;
18
- :lastAccessTime, FileTime, #FILETIME ftLastAccessTime;
19
- :lastWriteTime, FileTime, #FILETIME ftLastWriteTime;
20
- :volumeSerialNumber, :uint, #DWORD dwVolumeSerialNumber;
21
- :fileSizeHigh, :uint, #DWORD nFileSizeHigh;
22
- :fileSizeLow, :uint, #DWORD nFileSizeLow;
23
- :numberOfLinks, :uint, #DWORD nNumberOfLinks;
24
- :fileIndexHigh, :uint, #DWORD nFileIndexHigh;
25
- :fileIndexLow, :uint #DWORD nFileIndexLow;
17
+ :createTime, FileTime, # FILETIME ftCreationTime;
18
+ :lastAccessTime, FileTime, # FILETIME ftLastAccessTime;
19
+ :lastWriteTime, FileTime, # FILETIME ftLastWriteTime;
20
+ :volumeSerialNumber, :uint, # DWORD dwVolumeSerialNumber;
21
+ :fileSizeHigh, :uint, # DWORD nFileSizeHigh;
22
+ :fileSizeLow, :uint, # DWORD nFileSizeLow;
23
+ :numberOfLinks, :uint, # DWORD nNumberOfLinks;
24
+ :fileIndexHigh, :uint, # DWORD nFileIndexHigh;
25
+ :fileIndexLow, :uint # DWORD nFileIndexLow;
26
+ end
27
+
28
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/hh965605(v=vs.85).aspx
29
+ class FileId128 < FFI::Struct
30
+ layout :lowPart, :ulong_long, :highPart, :ulong_long
26
31
  end
27
32
 
33
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/hh802691(v=vs.85).aspx
34
+ class FileIdInfo < FFI::Struct
35
+ layout :volumeSerialNumber, :ulong_long, :fileId, FileId128
36
+ # ULONGLONG VolumeSerialNumber;
37
+ # FILE_ID_128 FileId;
38
+ end
39
+
40
+ FileInfoEnum = enum(
41
+ :FileBasicInfo,
42
+ :FileStandardInfo,
43
+ :FileNameInfo,
44
+ :FileRenameInfo,
45
+ :FileDispositionInfo,
46
+ :FileAllocationInfo,
47
+ :FileEndOfFileInfo,
48
+ :FileStreamInfo,
49
+ :FileCompressionInfo,
50
+ :FileAttributeTagInfo,
51
+ :FileIdBothDirectoryInfo,
52
+ :FileIdBothDirectoryRestartInfo,
53
+ :FileIoPriorityHintInfo,
54
+ :FileRemoteProtocolInfo,
55
+ :FileFullDirectoryInfo,
56
+ :FileFullDirectoryRestartInfo,
57
+ :FileStorageInfo,
58
+ :FileAlignmentInfo,
59
+ :FileIdInfo,
60
+ :FileIdExtdDirectoryInfo,
61
+ :FileIdExtdDirectoryRestartInfo
62
+ )
28
63
 
29
64
  #http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
30
- #HANDLE WINAPI CreateFile(_In_ LPCTSTR lpFileName,_In_ DWORD dwDesiredAccess,_In_ DWORD dwShareMode,
31
- # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,_In_ DWORD dwCreationDisposition,
32
- # _In_ DWORD dwFlagsAndAttributes,_In_opt_ HANDLE hTemplateFile);
33
- attach_function :GetOpenFileHandle, :CreateFileA, [:pointer, :uint, :uint, :pointer, :uint, :uint, :pointer], :pointer
65
+ #HANDLE WINAPI CreateFile(
66
+ # _In_ LPCTSTR lpFileName,
67
+ # _In_ DWORD dwDesiredAccess,
68
+ # _In_ DWORD dwShareMode,
69
+ # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
70
+ # _In_ DWORD dwCreationDisposition,
71
+ # _In_ DWORD dwFlagsAndAttributes, _In_opt_ HANDLE hTemplateFile);
72
+ attach_function :CreateFileA, [:pointer, :uint, :uint, :pointer, :uint, :uint, :pointer], :pointer
73
+ attach_function :CreateFileW, [:pointer, :uint, :uint, :pointer, :uint, :uint, :pointer], :pointer
34
74
 
35
75
  #http://msdn.microsoft.com/en-us/library/windows/desktop/aa364952(v=vs.85).aspx
36
- #BOOL WINAPI GetFileInformationByHandle(_In_ HANDLE hFile,_Out_ LPBY_HANDLE_FILE_INFORMATION lpFileInformation);
76
+ #BOOL WINAPI GetFileInformationByHandle(
77
+ # _In_ HANDLE hFile,
78
+ # _Out_ LPBY_HANDLE_FILE_INFORMATION lpFileInformation);
37
79
  attach_function :GetFileInformationByHandle, [:pointer, :pointer], :int
38
80
 
81
+ #https://msdn.microsoft.com/en-us/library/windows/desktop/aa364953(v=vs.85).aspx
82
+ #BOOL WINAPI GetFileInformationByHandleEx(
83
+ # _In_ HANDLE hFile,
84
+ # _In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
85
+ # _Out_ LPVOID lpFileInformation,
86
+ # _In_ DWORD dwBufferSize );
87
+ attach_function :GetFileInformationByHandleEx, [:pointer, FileInfoEnum, :pointer, :uint], :uint
88
+
39
89
  attach_function :CloseHandle, [:pointer], :int
40
90
 
91
+ #https://msdn.microsoft.com/en-us/library/windows/desktop/aa964920(v=vs.85).aspx
92
+ #BOOL WINAPI GetVolumeInformationByHandleW(
93
+ # _In_ HANDLE hFile,
94
+ # _Out_opt_ LPWSTR lpVolumeNameBuffer,
95
+ # _In_ DWORD nVolumeNameSize,
96
+ # _Out_opt_ LPDWORD lpVolumeSerialNumber,
97
+ # _Out_opt_ LPDWORD lpMaximumComponentLength,
98
+ # _Out_opt_ LPDWORD lpFileSystemFlags,
99
+ # _Out_opt_ LPWSTR lpFileSystemNameBuffer,
100
+ # _In_ DWORD nFileSystemNameSize);
101
+ attach_function :GetVolumeInformationByHandleW, [:pointer, :pointer, :uint, :pointer, :pointer, :pointer, :pointer, :uint], :int
102
+
103
+ def self.file_system_type_from_path(path)
104
+ file_system_type_from_handle(open_handle_from_path(path))
105
+ end
106
+
107
+ def self.file_system_type_from_io(io)
108
+ FileWatch::FileExt.io_handle(io) do |pointer|
109
+ file_system_type_from_handle(pointer, false)
110
+ end
111
+ end
41
112
 
42
- def self.GetWindowsUniqueFileIdentifier(path)
43
- handle = GetOpenFileHandle(path, 0, 7, nil, 3, 128, nil)
113
+ def self.file_system_type_from_handle(handle, close_handle = true)
114
+ out = FFI::MemoryPointer.new(:char, 256, true)
115
+ if GetVolumeInformationByHandleW(handle, nil, 0, nil, nil, nil, out, 256) > 0
116
+ char_pointer_to_ruby_string(out)
117
+ else
118
+ "unknown"
119
+ end
120
+ ensure
121
+ CloseHandle(handle) if close_handle
122
+ end
123
+
124
+ def self.identifier_from_io(io)
125
+ FileWatch::FileExt.io_handle(io) do |pointer|
126
+ identifier_from_handle(pointer, false)
127
+ end
128
+ end
129
+
130
+ def self.identifier_from_path(path)
131
+ identifier_from_handle(open_handle_from_path(path))
132
+ end
133
+
134
+ def self.identifier_from_path_ex(path)
135
+ identifier_from_handle_ex(open_handle_from_path(path))
136
+ end
137
+
138
+ def self.identifier_from_io_ex(io)
139
+ FileWatch::FileExt.io_handle(io) do |pointer|
140
+ identifier_from_handle_ex(pointer, false)
141
+ end
142
+ end
143
+
144
+ def self.identifier_from_handle_ex(handle, close_handle = true)
145
+ fileIdInfo = Winhelper::FileIdInfo.new
146
+ success = GetFileInformationByHandleEx(handle, :FileIdInfo, fileIdInfo, fileIdInfo.size)
147
+ if success > 0
148
+ vsn = fileIdInfo[:volumeSerialNumber]
149
+ lpfid = fileIdInfo[:fileId][:lowPart]
150
+ hpfid = fileIdInfo[:fileId][:highPart]
151
+ return "#{vsn}-#{lpfid}-#{hpfid}"
152
+ else
153
+ return 'unknown'
154
+ end
155
+ ensure
156
+ CloseHandle(handle) if close_handle
157
+ end
158
+
159
+ def self.identifier_from_handle(handle, close_handle = true)
44
160
  fileInfo = Winhelper::FileInformation.new
45
161
  success = GetFileInformationByHandle(handle, fileInfo)
46
- CloseHandle(handle)
47
- if success == 1
162
+ if success > 0
48
163
  #args = [
49
- # fileInfo[:fileAttributes], fileInfo[:volumeSerialNumber], fileInfo[:fileSizeHigh], fileInfo[:fileSizeLow],
50
- # fileInfo[:numberOfLinks], fileInfo[:fileIndexHigh], fileInfo[:fileIndexLow]
51
- # ]
164
+ # fileInfo[:fileAttributes], fileInfo[:volumeSerialNumber], fileInfo[:fileSizeHigh], fileInfo[:fileSizeLow],
165
+ # fileInfo[:numberOfLinks], fileInfo[:fileIndexHigh], fileInfo[:fileIndexLow]
166
+ # ]
52
167
  #p "Information: %u %u %u %u %u %u %u " % args
53
168
  #this is only guaranteed on NTFS, for ReFS on windows 2012, GetFileInformationByHandleEx should be used with FILE_ID_INFO, which returns a 128 bit identifier
54
169
  return "#{fileInfo[:volumeSerialNumber]}-#{fileInfo[:fileIndexLow]}-#{fileInfo[:fileIndexHigh]}"
55
170
  else
56
- #p "cannot retrieve file information, returning path"
57
- return path;
171
+ return 'unknown'
58
172
  end
173
+ ensure
174
+ CloseHandle(handle) if close_handle
175
+ end
176
+
177
+ private
178
+
179
+ def self.open_handle_from_path(path)
180
+ CreateFileW(in_buffer(path), 0, 7, nil, 3, 128, nil)
181
+ end
182
+
183
+ def self.in_buffer(string)
184
+ utf16le(string)
185
+ end
186
+
187
+ def self.char_pointer_to_ruby_string(char_pointer, length = 256)
188
+ bytes = char_pointer.get_array_of_uchar(0, length)
189
+ ignore = bytes.reverse.index{|b| b != 0} - 1
190
+ our_bytes = bytes[0, bytes.length - ignore]
191
+ our_bytes.pack("C*").force_encoding("UTF-16LE").encode("UTF-8")
192
+ end
193
+
194
+ def self.utf16le(string)
195
+ string.encode("UTF-16LE")
196
+ end
197
+
198
+ def self.win1252(string)
199
+ string.encode("Windows-1252")
59
200
  end
60
201
  end
61
202
 
203
+
62
204
  #fileId = Winhelper.GetWindowsUniqueFileIdentifier('C:\inetpub\logs\LogFiles\W3SVC1\u_ex1fdsadfsadfasdf30612.log')
63
205
  #p "FileId: " + fileId
64
206
  #p "outside function, sleeping"