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