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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/JAR_VERSION +1 -1
- data/README.md +0 -3
- data/docs/index.asciidoc +26 -16
- data/lib/filewatch/bootstrap.rb +10 -21
- data/lib/filewatch/discoverer.rb +35 -28
- data/lib/filewatch/observing_base.rb +2 -1
- data/lib/filewatch/read_mode/handlers/base.rb +19 -6
- data/lib/filewatch/read_mode/handlers/read_file.rb +43 -32
- data/lib/filewatch/read_mode/handlers/read_zip_file.rb +8 -3
- data/lib/filewatch/read_mode/processor.rb +8 -8
- data/lib/filewatch/settings.rb +3 -3
- data/lib/filewatch/sincedb_collection.rb +56 -42
- data/lib/filewatch/sincedb_value.rb +6 -0
- data/lib/filewatch/stat/generic.rb +34 -0
- data/lib/filewatch/stat/windows_path.rb +32 -0
- data/lib/filewatch/tail_mode/handlers/base.rb +40 -22
- data/lib/filewatch/tail_mode/handlers/create.rb +1 -2
- data/lib/filewatch/tail_mode/handlers/create_initial.rb +2 -1
- data/lib/filewatch/tail_mode/handlers/delete.rb +13 -1
- data/lib/filewatch/tail_mode/handlers/grow.rb +5 -2
- data/lib/filewatch/tail_mode/handlers/shrink.rb +7 -4
- data/lib/filewatch/tail_mode/handlers/unignore.rb +4 -2
- data/lib/filewatch/tail_mode/processor.rb +147 -58
- data/lib/filewatch/watch.rb +15 -35
- data/lib/filewatch/watched_file.rb +237 -41
- data/lib/filewatch/watched_files_collection.rb +2 -2
- data/lib/filewatch/winhelper.rb +167 -25
- data/lib/jars/filewatch-1.0.1.jar +0 -0
- data/lib/logstash/inputs/file.rb +9 -2
- data/logstash-input-file.gemspec +9 -2
- data/spec/file_ext/file_ext_windows_spec.rb +36 -0
- data/spec/filewatch/read_mode_handlers_read_file_spec.rb +2 -2
- data/spec/filewatch/reading_spec.rb +100 -57
- data/spec/filewatch/rotate_spec.rb +451 -0
- data/spec/filewatch/spec_helper.rb +33 -10
- data/spec/filewatch/tailing_spec.rb +273 -153
- data/spec/filewatch/watched_file_spec.rb +3 -3
- data/spec/filewatch/watched_files_collection_spec.rb +3 -3
- data/spec/filewatch/winhelper_spec.rb +4 -5
- data/spec/helpers/logging_level_helper.rb +8 -0
- data/spec/helpers/rspec_wait_handler_helper.rb +38 -0
- data/spec/helpers/spec_helper.rb +7 -1
- data/spec/inputs/file_read_spec.rb +54 -24
- data/spec/inputs/file_tail_spec.rb +244 -284
- metadata +13 -3
- 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
|
-
|
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, :
|
9
|
-
attr_reader :
|
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
|
-
@
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
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
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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"] =
|
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
|
132
|
-
|
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
|
-
|
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
|
189
|
-
@
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
197
|
-
|
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 -
|
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
|
234
|
-
|
235
|
-
@
|
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)
|
data/lib/filewatch/winhelper.rb
CHANGED
@@ -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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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(
|
31
|
-
#
|
32
|
-
#
|
33
|
-
|
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(
|
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.
|
43
|
-
|
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
|
-
|
47
|
-
if success == 1
|
162
|
+
if success > 0
|
48
163
|
#args = [
|
49
|
-
#
|
50
|
-
#
|
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
|
-
|
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"
|