logstash-input-file 4.1.3 → 4.1.4

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.
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"