win32-file-stat 1.5.1 → 1.5.2
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/CHANGES +154 -148
- data/MANIFEST +10 -10
- data/README +94 -93
- data/Rakefile +31 -31
- data/lib/win32/file/stat.rb +994 -984
- data/lib/win32/file/windows/constants.rb +94 -94
- data/lib/win32/file/windows/functions.rb +68 -67
- data/lib/win32/file/windows/helper.rb +13 -13
- data/lib/win32/file/windows/structs.rb +190 -190
- data/test/test_file_stat.rb +677 -677
- data/win32-file-stat.gemspec +27 -27
- metadata +13 -13
data/Rakefile
CHANGED
@@ -1,31 +1,31 @@
|
|
1
|
-
require 'rake'
|
2
|
-
require 'rake/clean'
|
3
|
-
require 'rake/testtask'
|
4
|
-
|
5
|
-
CLEAN.include("**/*.gem", "**/*.rbx", "**/*.rbc", "**/*.log", "**/*.exe", "**/*.txt")
|
6
|
-
|
7
|
-
namespace :gem do
|
8
|
-
desc "Create the win32-file-stat gem"
|
9
|
-
task :create => [:clean] do
|
10
|
-
spec = eval(IO.read("win32-file-stat.gemspec"))
|
11
|
-
if Gem::VERSION < "2.0.0"
|
12
|
-
Gem::Builder.new(spec).build
|
13
|
-
else
|
14
|
-
require 'rubygems/package'
|
15
|
-
Gem::Package.build(spec)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
desc "Install the win32-file-stat gem"
|
20
|
-
task :install => [:create] do
|
21
|
-
file = Dir["win32-file-stat*.gem"].first
|
22
|
-
sh "gem install -l #{file}"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
Rake::TestTask.new do |t|
|
27
|
-
t.verbose = true
|
28
|
-
t.warning = true
|
29
|
-
end
|
30
|
-
|
31
|
-
task :default => :test
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
CLEAN.include("**/*.gem", "**/*.rbx", "**/*.rbc", "**/*.log", "**/*.exe", "**/*.txt")
|
6
|
+
|
7
|
+
namespace :gem do
|
8
|
+
desc "Create the win32-file-stat gem"
|
9
|
+
task :create => [:clean] do
|
10
|
+
spec = eval(IO.read("win32-file-stat.gemspec"))
|
11
|
+
if Gem::VERSION < "2.0.0"
|
12
|
+
Gem::Builder.new(spec).build
|
13
|
+
else
|
14
|
+
require 'rubygems/package'
|
15
|
+
Gem::Package.build(spec)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Install the win32-file-stat gem"
|
20
|
+
task :install => [:create] do
|
21
|
+
file = Dir["win32-file-stat*.gem"].first
|
22
|
+
sh "gem install -l #{file}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::TestTask.new do |t|
|
27
|
+
t.verbose = true
|
28
|
+
t.warning = true
|
29
|
+
end
|
30
|
+
|
31
|
+
task :default => :test
|
data/lib/win32/file/stat.rb
CHANGED
@@ -1,984 +1,994 @@
|
|
1
|
-
require_relative 'windows/helper'
|
2
|
-
require_relative 'windows/constants'
|
3
|
-
require_relative 'windows/structs'
|
4
|
-
require_relative 'windows/functions'
|
5
|
-
require 'pp'
|
6
|
-
|
7
|
-
class File::Stat
|
8
|
-
include Windows::Stat::Constants
|
9
|
-
include Windows::Stat::Structs
|
10
|
-
include Windows::Stat::Functions
|
11
|
-
include Comparable
|
12
|
-
|
13
|
-
# We have to undefine these first in order to avoid redefinition warnings.
|
14
|
-
undef_method :atime, :ctime, :mtime, :blksize, :blockdev?, :blocks, :chardev?
|
15
|
-
undef_method :dev, :dev_major, :dev_minor, :directory?, :executable?
|
16
|
-
undef_method :executable_real?, :file?
|
17
|
-
undef_method :ftype, :gid, :grpowned?, :ino, :mode, :nlink, :owned?
|
18
|
-
undef_method :pipe?, :readable?, :readable_real?, :rdev, :rdev_major
|
19
|
-
undef_method :rdev_minor, :setuid?, :setgid?
|
20
|
-
undef_method :size, :size?, :socket?, :sticky?, :symlink?, :uid
|
21
|
-
undef_method :world_readable?, :world_writable?, :writable?, :writable_real?
|
22
|
-
undef_method :<=>, :inspect, :pretty_print, :zero?
|
23
|
-
|
24
|
-
# A Time object containing the last access time.
|
25
|
-
attr_reader :atime
|
26
|
-
|
27
|
-
# A Time object indicating when the file was last changed.
|
28
|
-
attr_reader :ctime
|
29
|
-
|
30
|
-
# A Time object containing the last modification time.
|
31
|
-
attr_reader :mtime
|
32
|
-
|
33
|
-
# The native filesystems' block size.
|
34
|
-
attr_reader :blksize
|
35
|
-
|
36
|
-
# The number of native filesystem blocks allocated for this file.
|
37
|
-
attr_reader :blocks
|
38
|
-
|
39
|
-
# The serial number of the file's volume.
|
40
|
-
attr_reader :rdev
|
41
|
-
|
42
|
-
# The file's unique identifier. Only valid for regular files.
|
43
|
-
attr_reader :ino
|
44
|
-
|
45
|
-
# Integer representing the permission bits of the file.
|
46
|
-
attr_reader :mode
|
47
|
-
|
48
|
-
# The number of hard links to the file.
|
49
|
-
attr_reader :nlink
|
50
|
-
|
51
|
-
# The size of the file in bytes.
|
52
|
-
attr_reader :size
|
53
|
-
|
54
|
-
# Nil on Windows
|
55
|
-
attr_reader :dev_major, :dev_minor, :rdev_major, :rdev_minor
|
56
|
-
|
57
|
-
# Alternate streams
|
58
|
-
attr_reader :streams
|
59
|
-
|
60
|
-
# The version of the win32-file-stat library
|
61
|
-
WIN32_FILE_STAT_VERSION = '1.5.
|
62
|
-
|
63
|
-
# Creates and returns a File::Stat object, which encapsulate common status
|
64
|
-
# information for File objects on MS Windows sytems. The information is
|
65
|
-
# recorded at the moment the File::Stat object is created; changes made to
|
66
|
-
# the file after that point will not be reflected.
|
67
|
-
#
|
68
|
-
def initialize(file)
|
69
|
-
file = string_check(file)
|
70
|
-
|
71
|
-
path = file.tr('/', "\\")
|
72
|
-
@path = path
|
73
|
-
|
74
|
-
@user_sid = get_file_sid(file, OWNER_SECURITY_INFORMATION)
|
75
|
-
@grp_sid = get_file_sid(file, GROUP_SECURITY_INFORMATION)
|
76
|
-
|
77
|
-
@uid = @user_sid.split('-').last.to_i
|
78
|
-
@gid = @grp_sid.split('-').last.to_i
|
79
|
-
|
80
|
-
@owned = @user_sid == get_current_process_sid(TokenUser)
|
81
|
-
@grpowned = @grp_sid == get_current_process_sid(TokenGroups)
|
82
|
-
|
83
|
-
begin
|
84
|
-
# The handle returned will be used by other functions
|
85
|
-
handle = get_handle(path)
|
86
|
-
|
87
|
-
@blockdev = get_blockdev(path)
|
88
|
-
@blksize = get_blksize(path)
|
89
|
-
|
90
|
-
if handle
|
91
|
-
@filetype = get_filetype(handle)
|
92
|
-
@streams = get_streams(handle)
|
93
|
-
@chardev = @filetype == FILE_TYPE_CHAR
|
94
|
-
@regular = @filetype == FILE_TYPE_DISK
|
95
|
-
@pipe = @filetype == FILE_TYPE_PIPE
|
96
|
-
|
97
|
-
@
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
handle =
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
@
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
@
|
163
|
-
@
|
164
|
-
@
|
165
|
-
@
|
166
|
-
@
|
167
|
-
|
168
|
-
@
|
169
|
-
@
|
170
|
-
@
|
171
|
-
@
|
172
|
-
@
|
173
|
-
@
|
174
|
-
|
175
|
-
@
|
176
|
-
|
177
|
-
@
|
178
|
-
@
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
#
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
#
|
222
|
-
#
|
223
|
-
def
|
224
|
-
@
|
225
|
-
end
|
226
|
-
|
227
|
-
# Returns whether or not the file is a
|
228
|
-
#
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
#
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
#
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
#
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
#
|
253
|
-
#
|
254
|
-
def
|
255
|
-
@
|
256
|
-
end
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
#
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
# Returns
|
268
|
-
#
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
#
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
#
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
#
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
#
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
# Returns whether or not the file is
|
306
|
-
#
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
#
|
314
|
-
def
|
315
|
-
@
|
316
|
-
end
|
317
|
-
|
318
|
-
# Returns the
|
319
|
-
|
320
|
-
#
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
#
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
#
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
#
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
#
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
#
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
#
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
#
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
#
|
446
|
-
|
447
|
-
|
448
|
-
# the
|
449
|
-
#
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
#
|
456
|
-
|
457
|
-
|
458
|
-
#
|
459
|
-
#
|
460
|
-
#
|
461
|
-
def
|
462
|
-
@
|
463
|
-
end
|
464
|
-
|
465
|
-
# Returns whether or not the file is writable by
|
466
|
-
|
467
|
-
|
468
|
-
#
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
}
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
mode |=
|
607
|
-
end
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
def
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
end
|
748
|
-
|
749
|
-
arr
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
if token_type == TokenUser
|
821
|
-
|
822
|
-
else
|
823
|
-
|
824
|
-
end
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
1
|
+
require_relative 'windows/helper'
|
2
|
+
require_relative 'windows/constants'
|
3
|
+
require_relative 'windows/structs'
|
4
|
+
require_relative 'windows/functions'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
class File::Stat
|
8
|
+
include Windows::Stat::Constants
|
9
|
+
include Windows::Stat::Structs
|
10
|
+
include Windows::Stat::Functions
|
11
|
+
include Comparable
|
12
|
+
|
13
|
+
# We have to undefine these first in order to avoid redefinition warnings.
|
14
|
+
undef_method :atime, :ctime, :mtime, :blksize, :blockdev?, :blocks, :chardev?
|
15
|
+
undef_method :dev, :dev_major, :dev_minor, :directory?, :executable?
|
16
|
+
undef_method :executable_real?, :file?
|
17
|
+
undef_method :ftype, :gid, :grpowned?, :ino, :mode, :nlink, :owned?
|
18
|
+
undef_method :pipe?, :readable?, :readable_real?, :rdev, :rdev_major
|
19
|
+
undef_method :rdev_minor, :setuid?, :setgid?
|
20
|
+
undef_method :size, :size?, :socket?, :sticky?, :symlink?, :uid
|
21
|
+
undef_method :world_readable?, :world_writable?, :writable?, :writable_real?
|
22
|
+
undef_method :<=>, :inspect, :pretty_print, :zero?
|
23
|
+
|
24
|
+
# A Time object containing the last access time.
|
25
|
+
attr_reader :atime
|
26
|
+
|
27
|
+
# A Time object indicating when the file was last changed.
|
28
|
+
attr_reader :ctime
|
29
|
+
|
30
|
+
# A Time object containing the last modification time.
|
31
|
+
attr_reader :mtime
|
32
|
+
|
33
|
+
# The native filesystems' block size.
|
34
|
+
attr_reader :blksize
|
35
|
+
|
36
|
+
# The number of native filesystem blocks allocated for this file.
|
37
|
+
attr_reader :blocks
|
38
|
+
|
39
|
+
# The serial number of the file's volume.
|
40
|
+
attr_reader :rdev
|
41
|
+
|
42
|
+
# The file's unique identifier. Only valid for regular files.
|
43
|
+
attr_reader :ino
|
44
|
+
|
45
|
+
# Integer representing the permission bits of the file.
|
46
|
+
attr_reader :mode
|
47
|
+
|
48
|
+
# The number of hard links to the file.
|
49
|
+
attr_reader :nlink
|
50
|
+
|
51
|
+
# The size of the file in bytes.
|
52
|
+
attr_reader :size
|
53
|
+
|
54
|
+
# Nil on Windows
|
55
|
+
attr_reader :dev_major, :dev_minor, :rdev_major, :rdev_minor
|
56
|
+
|
57
|
+
# Alternate streams
|
58
|
+
attr_reader :streams
|
59
|
+
|
60
|
+
# The version of the win32-file-stat library
|
61
|
+
WIN32_FILE_STAT_VERSION = '1.5.2'
|
62
|
+
|
63
|
+
# Creates and returns a File::Stat object, which encapsulate common status
|
64
|
+
# information for File objects on MS Windows sytems. The information is
|
65
|
+
# recorded at the moment the File::Stat object is created; changes made to
|
66
|
+
# the file after that point will not be reflected.
|
67
|
+
#
|
68
|
+
def initialize(file)
|
69
|
+
file = string_check(file)
|
70
|
+
|
71
|
+
path = file.tr('/', "\\")
|
72
|
+
@path = path
|
73
|
+
|
74
|
+
@user_sid = get_file_sid(file, OWNER_SECURITY_INFORMATION)
|
75
|
+
@grp_sid = get_file_sid(file, GROUP_SECURITY_INFORMATION)
|
76
|
+
|
77
|
+
@uid = @user_sid.split('-').last.to_i
|
78
|
+
@gid = @grp_sid.split('-').last.to_i
|
79
|
+
|
80
|
+
@owned = @user_sid == get_current_process_sid(TokenUser)
|
81
|
+
@grpowned = @grp_sid == get_current_process_sid(TokenGroups)
|
82
|
+
|
83
|
+
begin
|
84
|
+
# The handle returned will be used by other functions
|
85
|
+
handle = get_handle(path)
|
86
|
+
|
87
|
+
@blockdev = get_blockdev(path)
|
88
|
+
@blksize = get_blksize(path)
|
89
|
+
|
90
|
+
if handle
|
91
|
+
@filetype = get_filetype(handle)
|
92
|
+
@streams = get_streams(handle)
|
93
|
+
@chardev = @filetype == FILE_TYPE_CHAR
|
94
|
+
@regular = @filetype == FILE_TYPE_DISK
|
95
|
+
@pipe = @filetype == FILE_TYPE_PIPE
|
96
|
+
|
97
|
+
if @pipe
|
98
|
+
@socket = !GetNamedPipeInfo(handle, nil, nil, nil, nil)
|
99
|
+
else
|
100
|
+
@socket = false
|
101
|
+
end
|
102
|
+
else
|
103
|
+
@chardev = false
|
104
|
+
@regular = false
|
105
|
+
@pipe = false
|
106
|
+
@socket = false
|
107
|
+
end
|
108
|
+
|
109
|
+
fpath = path.wincode
|
110
|
+
|
111
|
+
if handle == nil || ((@blockdev || @chardev || @pipe) && GetDriveType(fpath) != DRIVE_REMOVABLE)
|
112
|
+
data = WIN32_FIND_DATA.new
|
113
|
+
CloseHandle(handle) if handle
|
114
|
+
|
115
|
+
handle = FindFirstFile(fpath, data)
|
116
|
+
|
117
|
+
if handle == INVALID_HANDLE_VALUE
|
118
|
+
raise SystemCallError.new('FindFirstFile', FFI.errno)
|
119
|
+
end
|
120
|
+
|
121
|
+
FindClose(handle)
|
122
|
+
handle = nil
|
123
|
+
|
124
|
+
@nlink = 1 # Default from stat/wstat function.
|
125
|
+
@ino = nil
|
126
|
+
@rdev = nil
|
127
|
+
else
|
128
|
+
data = BY_HANDLE_FILE_INFORMATION.new
|
129
|
+
|
130
|
+
unless GetFileInformationByHandle(handle, data)
|
131
|
+
raise SystemCallError.new('GetFileInformationByHandle', FFI.errno)
|
132
|
+
end
|
133
|
+
|
134
|
+
@nlink = data[:nNumberOfLinks]
|
135
|
+
@ino = (data[:nFileIndexHigh] << 32) | data[:nFileIndexLow]
|
136
|
+
@rdev = data[:dwVolumeSerialNumber]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Not supported and/or meaningless on MS Windows
|
140
|
+
@dev_major = nil
|
141
|
+
@dev_minor = nil
|
142
|
+
@rdev_major = nil
|
143
|
+
@rdev_minor = nil
|
144
|
+
@setgid = false
|
145
|
+
@setuid = false
|
146
|
+
@sticky = false
|
147
|
+
|
148
|
+
# Originally used GetBinaryType, but it only worked
|
149
|
+
# for .exe files, and it could return false positives.
|
150
|
+
@executable = %w[.bat .cmd .com .exe].include?(File.extname(@path).downcase)
|
151
|
+
|
152
|
+
# Set blocks equal to size / blksize, rounded up
|
153
|
+
case @blksize
|
154
|
+
when nil
|
155
|
+
@blocks = nil
|
156
|
+
when 0
|
157
|
+
@blocks = 0
|
158
|
+
else
|
159
|
+
@blocks = (data.size.to_f / @blksize.to_f).ceil
|
160
|
+
end
|
161
|
+
|
162
|
+
@attr = data[:dwFileAttributes]
|
163
|
+
@atime = Time.at(data.atime)
|
164
|
+
@ctime = Time.at(data.ctime)
|
165
|
+
@mtime = Time.at(data.mtime)
|
166
|
+
@size = data.size
|
167
|
+
|
168
|
+
@archive = @attr & FILE_ATTRIBUTE_ARCHIVE > 0
|
169
|
+
@compressed = @attr & FILE_ATTRIBUTE_COMPRESSED > 0
|
170
|
+
@directory = @attr & FILE_ATTRIBUTE_DIRECTORY > 0
|
171
|
+
@encrypted = @attr & FILE_ATTRIBUTE_ENCRYPTED > 0
|
172
|
+
@hidden = @attr & FILE_ATTRIBUTE_HIDDEN > 0
|
173
|
+
@indexed = @attr & ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED > 0
|
174
|
+
@normal = @attr & FILE_ATTRIBUTE_NORMAL > 0
|
175
|
+
@offline = @attr & FILE_ATTRIBUTE_OFFLINE > 0
|
176
|
+
@readonly = @attr & FILE_ATTRIBUTE_READONLY > 0
|
177
|
+
@reparse_point = @attr & FILE_ATTRIBUTE_REPARSE_POINT > 0
|
178
|
+
@sparse = @attr & FILE_ATTRIBUTE_SPARSE_FILE > 0
|
179
|
+
@system = @attr & FILE_ATTRIBUTE_SYSTEM > 0
|
180
|
+
@temporary = @attr & FILE_ATTRIBUTE_TEMPORARY > 0
|
181
|
+
|
182
|
+
@mode = get_mode
|
183
|
+
|
184
|
+
@readable = access_check(path, GENERIC_READ)
|
185
|
+
@readable_real = @readable
|
186
|
+
|
187
|
+
# The MSDN docs say that the readonly attribute is honored for directories
|
188
|
+
if @directory
|
189
|
+
@writable = access_check(path, GENERIC_WRITE)
|
190
|
+
else
|
191
|
+
@writable = access_check(path, GENERIC_WRITE) && !@readonly
|
192
|
+
end
|
193
|
+
|
194
|
+
@writable_real = @writable
|
195
|
+
|
196
|
+
@world_readable = access_check_world(path, FILE_READ_DATA)
|
197
|
+
@world_writable = access_check_world(path, FILE_WRITE_DATA) && !@readonly
|
198
|
+
|
199
|
+
if @reparse_point
|
200
|
+
@symlink = get_symlink(path)
|
201
|
+
else
|
202
|
+
@symlink = false
|
203
|
+
end
|
204
|
+
ensure
|
205
|
+
CloseHandle(handle) if handle
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
## Comparable
|
210
|
+
|
211
|
+
# Compares two File::Stat objects using modification time.
|
212
|
+
#--
|
213
|
+
# Custom implementation necessary since we altered File::Stat.
|
214
|
+
#
|
215
|
+
def <=>(other)
|
216
|
+
@mtime.to_i <=> other.mtime.to_i
|
217
|
+
end
|
218
|
+
|
219
|
+
## Other
|
220
|
+
|
221
|
+
# Returns whether or not the file is an archive file.
|
222
|
+
#
|
223
|
+
def archive?
|
224
|
+
@archive
|
225
|
+
end
|
226
|
+
|
227
|
+
# Returns whether or not the file is a block device. For MS Windows a
|
228
|
+
# block device is a removable drive, cdrom or ramdisk.
|
229
|
+
#
|
230
|
+
def blockdev?
|
231
|
+
@blockdev
|
232
|
+
end
|
233
|
+
|
234
|
+
# Returns whether or not the file is a character device.
|
235
|
+
#
|
236
|
+
def chardev?
|
237
|
+
@chardev
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns whether or not the file is compressed.
|
241
|
+
#
|
242
|
+
def compressed?
|
243
|
+
@compressed
|
244
|
+
end
|
245
|
+
|
246
|
+
# Returns whether or not the file is a directory.
|
247
|
+
#
|
248
|
+
def directory?
|
249
|
+
@directory
|
250
|
+
end
|
251
|
+
|
252
|
+
# Returns whether or not the file in encrypted.
|
253
|
+
#
|
254
|
+
def encrypted?
|
255
|
+
@encrypted
|
256
|
+
end
|
257
|
+
|
258
|
+
# Returns whether or not the file is executable. Generally speaking, this
|
259
|
+
# means .bat, .cmd, .com, and .exe files.
|
260
|
+
#
|
261
|
+
def executable?
|
262
|
+
@executable
|
263
|
+
end
|
264
|
+
|
265
|
+
alias executable_real? executable?
|
266
|
+
|
267
|
+
# Returns whether or not the file is a regular file, as opposed to a pipe,
|
268
|
+
# socket, etc.
|
269
|
+
#
|
270
|
+
def file?
|
271
|
+
@regular && !@directory && !@reparse_point
|
272
|
+
end
|
273
|
+
|
274
|
+
# Returns the user ID of the file. If full_sid is true, then the full
|
275
|
+
# string sid is returned instead.
|
276
|
+
#--
|
277
|
+
# The user id is the RID of the SID.
|
278
|
+
#
|
279
|
+
def gid(full_sid = false)
|
280
|
+
full_sid ? @grp_sid : @gid
|
281
|
+
end
|
282
|
+
|
283
|
+
# Returns true if the process owner's ID is the same as one of the file's groups.
|
284
|
+
#--
|
285
|
+
# Internally we're checking the process sid against the TokenGroups sid.
|
286
|
+
#
|
287
|
+
def grpowned?
|
288
|
+
@grpowned
|
289
|
+
end
|
290
|
+
|
291
|
+
# Returns whether or not the file is hidden.
|
292
|
+
#
|
293
|
+
def hidden?
|
294
|
+
@hidden
|
295
|
+
end
|
296
|
+
|
297
|
+
# Returns whether or not the file is content indexed.
|
298
|
+
#
|
299
|
+
def indexed?
|
300
|
+
@indexed
|
301
|
+
end
|
302
|
+
|
303
|
+
alias content_indexed? indexed?
|
304
|
+
|
305
|
+
# Returns whether or not the file is 'normal'. This is only true if
|
306
|
+
# virtually all other attributes are false.
|
307
|
+
#
|
308
|
+
def normal?
|
309
|
+
@normal
|
310
|
+
end
|
311
|
+
|
312
|
+
# Returns whether or not the file is offline.
|
313
|
+
#
|
314
|
+
def offline?
|
315
|
+
@offline
|
316
|
+
end
|
317
|
+
|
318
|
+
# Returns whether or not the current process owner is the owner of the file.
|
319
|
+
#--
|
320
|
+
# Internally we're checking the process sid against the owner's sid.
|
321
|
+
def owned?
|
322
|
+
@owned
|
323
|
+
end
|
324
|
+
|
325
|
+
# Returns the drive number of the disk containing the file, or -1 if there
|
326
|
+
# is no associated drive number.
|
327
|
+
#
|
328
|
+
# If the +letter+ option is true, returns the drive letter instead. If there
|
329
|
+
# is no drive letter, it will return nil.
|
330
|
+
#--
|
331
|
+
# This differs slightly from MRI in that it will return -1 if the path
|
332
|
+
# does not have a drive letter.
|
333
|
+
#
|
334
|
+
# Note: Bug in JRuby as of JRuby 1.7.8, which does not expand NUL properly.
|
335
|
+
#
|
336
|
+
def dev(letter = false)
|
337
|
+
fpath = File.expand_path(@path).wincode
|
338
|
+
num = PathGetDriveNumber(fpath)
|
339
|
+
|
340
|
+
if letter
|
341
|
+
if num == -1
|
342
|
+
nil
|
343
|
+
else
|
344
|
+
(num + 'A'.ord).chr + ':'
|
345
|
+
end
|
346
|
+
else
|
347
|
+
num
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# Returns whether or not the file is readable by the process owner.
|
352
|
+
#--
|
353
|
+
# In Windows terms, we're checking for GENERIC_READ privileges.
|
354
|
+
#
|
355
|
+
def readable?
|
356
|
+
@readable
|
357
|
+
end
|
358
|
+
|
359
|
+
# A synonym for File::Stat#readable?
|
360
|
+
#
|
361
|
+
def readable_real?
|
362
|
+
@readable_real
|
363
|
+
end
|
364
|
+
|
365
|
+
# Returns whether or not the file is readonly.
|
366
|
+
#
|
367
|
+
def readonly?
|
368
|
+
@readonly
|
369
|
+
end
|
370
|
+
|
371
|
+
alias read_only? readonly?
|
372
|
+
|
373
|
+
# Returns whether or not the file is a pipe.
|
374
|
+
#
|
375
|
+
def pipe?
|
376
|
+
@pipe
|
377
|
+
end
|
378
|
+
|
379
|
+
# Returns whether or not the file is a socket.
|
380
|
+
def socket?
|
381
|
+
@socket
|
382
|
+
end
|
383
|
+
|
384
|
+
# Returns whether or not the file is a reparse point.
|
385
|
+
#
|
386
|
+
def reparse_point?
|
387
|
+
@reparse_point
|
388
|
+
end
|
389
|
+
|
390
|
+
# Returns false on MS Windows.
|
391
|
+
#--
|
392
|
+
# I had to explicitly define this because of a bug in JRuby.
|
393
|
+
#
|
394
|
+
def setgid?
|
395
|
+
@setgid
|
396
|
+
end
|
397
|
+
|
398
|
+
# Returns false on MS Windows.
|
399
|
+
#--
|
400
|
+
# I had to explicitly define this because of a bug in JRuby.
|
401
|
+
#
|
402
|
+
def setuid?
|
403
|
+
@setuid
|
404
|
+
end
|
405
|
+
|
406
|
+
# Returns whether or not the file size is zero.
|
407
|
+
#
|
408
|
+
def size?
|
409
|
+
@size > 0 ? @size : nil
|
410
|
+
end
|
411
|
+
|
412
|
+
# Returns whether or not the file is a sparse file. In most cases a sparse
|
413
|
+
# file is an image file.
|
414
|
+
#
|
415
|
+
def sparse?
|
416
|
+
@sparse
|
417
|
+
end
|
418
|
+
|
419
|
+
# Returns false on MS Windows.
|
420
|
+
#--
|
421
|
+
# I had to explicitly define this because of a bug in JRuby.
|
422
|
+
#
|
423
|
+
def sticky?
|
424
|
+
@sticky
|
425
|
+
end
|
426
|
+
|
427
|
+
# Returns whether or not the file is a symlink.
|
428
|
+
#
|
429
|
+
def symlink?
|
430
|
+
@symlink
|
431
|
+
end
|
432
|
+
|
433
|
+
# Returns whether or not the file is a system file.
|
434
|
+
#
|
435
|
+
def system?
|
436
|
+
@system
|
437
|
+
end
|
438
|
+
|
439
|
+
# Returns whether or not the file is being used for temporary storage.
|
440
|
+
#
|
441
|
+
def temporary?
|
442
|
+
@temporary
|
443
|
+
end
|
444
|
+
|
445
|
+
# Returns the user ID of the file. If the +full_sid+ is true, then the
|
446
|
+
# full string sid is returned instead.
|
447
|
+
#--
|
448
|
+
# The user id is the RID of the SID.
|
449
|
+
#
|
450
|
+
def uid(full_sid = false)
|
451
|
+
full_sid ? @user_sid : @uid
|
452
|
+
end
|
453
|
+
|
454
|
+
# Returns whether or not the file is readable by others. Note that this
|
455
|
+
# merely returns true or false, not permission bits (or nil).
|
456
|
+
#--
|
457
|
+
# In Windows terms, this is checking the access right FILE_READ_DATA against
|
458
|
+
# the well-known SID "S-1-1-0", aka "Everyone".
|
459
|
+
#
|
460
|
+
#
|
461
|
+
def world_readable?
|
462
|
+
@world_readable
|
463
|
+
end
|
464
|
+
|
465
|
+
# Returns whether or not the file is writable by others. Note that this
|
466
|
+
# merely returns true or false, not permission bits (or nil).
|
467
|
+
#--
|
468
|
+
# In Windows terms, this is checking the access right FILE_WRITE_DATA against
|
469
|
+
# the well-known SID "S-1-1-0", aka "Everyone".
|
470
|
+
#
|
471
|
+
def world_writable?
|
472
|
+
@world_writable
|
473
|
+
end
|
474
|
+
|
475
|
+
# Returns whether or not the file is writable by the current process owner.
|
476
|
+
#--
|
477
|
+
# In Windows terms, we're checking for GENERIC_WRITE privileges.
|
478
|
+
#
|
479
|
+
def writable?
|
480
|
+
@writable
|
481
|
+
end
|
482
|
+
|
483
|
+
# A synonym for File::Stat#readable?
|
484
|
+
#
|
485
|
+
def writable_real?
|
486
|
+
@writable_real
|
487
|
+
end
|
488
|
+
|
489
|
+
# Returns whether or not the file size is zero.
|
490
|
+
#
|
491
|
+
def zero?
|
492
|
+
@size == 0
|
493
|
+
end
|
494
|
+
|
495
|
+
# Identifies the type of file. The return string is one of 'file',
|
496
|
+
# 'directory', 'characterSpecial', 'socket' or 'unknown'.
|
497
|
+
#
|
498
|
+
def ftype
|
499
|
+
return 'directory' if @directory
|
500
|
+
|
501
|
+
case @filetype
|
502
|
+
when FILE_TYPE_CHAR
|
503
|
+
'characterSpecial'
|
504
|
+
when FILE_TYPE_DISK
|
505
|
+
'file'
|
506
|
+
when FILE_TYPE_PIPE
|
507
|
+
'socket'
|
508
|
+
else
|
509
|
+
if blockdev?
|
510
|
+
'blockSpecial'
|
511
|
+
else
|
512
|
+
'unknown'
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# Returns a stringified version of a File::Stat object.
|
518
|
+
#
|
519
|
+
def inspect
|
520
|
+
members = %w[
|
521
|
+
archive? atime blksize blockdev? blocks compressed? ctime dev
|
522
|
+
encrypted? gid hidden? indexed? ino mode mtime rdev nlink normal?
|
523
|
+
offline? readonly? reparse_point? size sparse? system? streams
|
524
|
+
temporary? uid
|
525
|
+
]
|
526
|
+
|
527
|
+
str = "#<#{self.class}"
|
528
|
+
|
529
|
+
members.sort.each{ |mem|
|
530
|
+
if mem == 'mode'
|
531
|
+
str << " #{mem}=" << sprintf("0%o", send(mem.intern))
|
532
|
+
elsif mem[-1].chr == '?' # boolean methods
|
533
|
+
str << " #{mem.chop}=" << send(mem.intern).to_s
|
534
|
+
else
|
535
|
+
str << " #{mem}=" << send(mem.intern).to_s
|
536
|
+
end
|
537
|
+
}
|
538
|
+
|
539
|
+
str
|
540
|
+
end
|
541
|
+
|
542
|
+
# A custom pretty print method. This was necessary not only to handle
|
543
|
+
# the additional attributes, but to work around an error caused by the
|
544
|
+
# builtin method for the current File::Stat class (see pp.rb).
|
545
|
+
#
|
546
|
+
def pretty_print(q)
|
547
|
+
members = %w[
|
548
|
+
archive? atime blksize blockdev? blocks compressed? ctime dev
|
549
|
+
encrypted? gid hidden? indexed? ino mode mtime rdev nlink normal?
|
550
|
+
offline? readonly? reparse_point? size sparse? streams system? temporary?
|
551
|
+
uid
|
552
|
+
]
|
553
|
+
|
554
|
+
q.object_group(self){
|
555
|
+
q.breakable
|
556
|
+
members.each{ |mem|
|
557
|
+
q.group{
|
558
|
+
q.text("#{mem}".ljust(15) + "=> ")
|
559
|
+
if mem == 'mode'
|
560
|
+
q.text(sprintf("0%o", send(mem.intern)))
|
561
|
+
else
|
562
|
+
val = self.send(mem.intern)
|
563
|
+
if val.nil?
|
564
|
+
q.text('nil')
|
565
|
+
else
|
566
|
+
q.text(val.to_s)
|
567
|
+
end
|
568
|
+
end
|
569
|
+
}
|
570
|
+
q.comma_breakable unless mem == members.last
|
571
|
+
}
|
572
|
+
}
|
573
|
+
end
|
574
|
+
|
575
|
+
private
|
576
|
+
|
577
|
+
# Workaround for bug in 64-big JRuby. Hope to remove it some day.
|
578
|
+
def get_ptr_type
|
579
|
+
if RUBY_PLATFORM == 'java' && ENV_JAVA['sun.arch.data.model'] == '64'
|
580
|
+
:ulong_long
|
581
|
+
else
|
582
|
+
:uintptr_t
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
# Allow stringy arguments
|
587
|
+
def string_check(arg)
|
588
|
+
return arg if arg.is_a?(String)
|
589
|
+
return arg.send(:to_str) if arg.respond_to?(:to_str, true) # MRI honors private to_str
|
590
|
+
return arg.to_path if arg.respond_to?(:to_path)
|
591
|
+
raise TypeError
|
592
|
+
end
|
593
|
+
|
594
|
+
# This is based on fileattr_to_unixmode in win32.c
|
595
|
+
#
|
596
|
+
def get_mode
|
597
|
+
mode = 0
|
598
|
+
|
599
|
+
s_iread = 0x0100; s_iwrite = 0x0080; s_iexec = 0x0040
|
600
|
+
s_ifreg = 0x8000; s_ifdir = 0x4000; s_iwusr = 0200
|
601
|
+
s_iwgrp = 0020; s_iwoth = 0002;
|
602
|
+
|
603
|
+
if @readonly
|
604
|
+
mode |= s_iread
|
605
|
+
else
|
606
|
+
mode |= s_iread | s_iwrite | s_iwusr
|
607
|
+
end
|
608
|
+
|
609
|
+
if @directory
|
610
|
+
mode |= s_ifdir | s_iexec
|
611
|
+
else
|
612
|
+
mode |= s_ifreg
|
613
|
+
end
|
614
|
+
|
615
|
+
if @executable
|
616
|
+
mode |= s_iexec
|
617
|
+
end
|
618
|
+
|
619
|
+
mode |= (mode & 0700) >> 3;
|
620
|
+
mode |= (mode & 0700) >> 6;
|
621
|
+
|
622
|
+
mode &= ~(s_iwgrp | s_iwoth)
|
623
|
+
|
624
|
+
mode
|
625
|
+
end
|
626
|
+
|
627
|
+
# Returns whether or not +path+ is a block device.
|
628
|
+
#
|
629
|
+
def get_blockdev(path)
|
630
|
+
ptr = FFI::MemoryPointer.from_string(path.wincode)
|
631
|
+
|
632
|
+
if PathStripToRoot(ptr)
|
633
|
+
fpath = ptr.read_bytes(path.size * 2).split("\000\000").first
|
634
|
+
else
|
635
|
+
fpath = nil
|
636
|
+
end
|
637
|
+
|
638
|
+
case GetDriveType(fpath)
|
639
|
+
when DRIVE_REMOVABLE, DRIVE_CDROM, DRIVE_RAMDISK
|
640
|
+
true
|
641
|
+
else
|
642
|
+
false
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
# Returns the blksize for +path+.
|
647
|
+
#---
|
648
|
+
# The jruby-ffi gem (as of 1.9.3) reports a failure here where it shouldn't.
|
649
|
+
# Consequently, this method returns 4096 automatically for now on JRuby.
|
650
|
+
#
|
651
|
+
def get_blksize(path)
|
652
|
+
return 4096 if RUBY_PLATFORM == 'java' # Bug in jruby-ffi
|
653
|
+
|
654
|
+
ptr = FFI::MemoryPointer.from_string(path.wincode)
|
655
|
+
|
656
|
+
if PathStripToRoot(ptr)
|
657
|
+
fpath = ptr.read_bytes(path.size * 2).split("\000\000").first
|
658
|
+
else
|
659
|
+
fpath = nil
|
660
|
+
end
|
661
|
+
|
662
|
+
size = nil
|
663
|
+
|
664
|
+
sectors = FFI::MemoryPointer.new(:ulong)
|
665
|
+
bytes = FFI::MemoryPointer.new(:ulong)
|
666
|
+
free = FFI::MemoryPointer.new(:ulong)
|
667
|
+
total = FFI::MemoryPointer.new(:ulong)
|
668
|
+
|
669
|
+
if GetDiskFreeSpace(fpath, sectors, bytes, free, total)
|
670
|
+
size = sectors.read_ulong * bytes.read_ulong
|
671
|
+
else
|
672
|
+
unless PathIsUNC(fpath)
|
673
|
+
raise SystemCallError.new('GetDiskFreeSpace', FFI.errno)
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
size
|
678
|
+
end
|
679
|
+
|
680
|
+
# Generic method for retrieving a handle.
|
681
|
+
#
|
682
|
+
def get_handle(path)
|
683
|
+
fpath = path.wincode
|
684
|
+
|
685
|
+
handle = CreateFile(
|
686
|
+
fpath,
|
687
|
+
GENERIC_READ,
|
688
|
+
FILE_SHARE_READ,
|
689
|
+
nil,
|
690
|
+
OPEN_EXISTING,
|
691
|
+
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
|
692
|
+
0
|
693
|
+
)
|
694
|
+
|
695
|
+
if handle == INVALID_HANDLE_VALUE
|
696
|
+
return nil if FFI.errno == 32 # ERROR_SHARING_VIOLATION. Locked files.
|
697
|
+
raise SystemCallError.new('CreateFile', FFI.errno)
|
698
|
+
end
|
699
|
+
|
700
|
+
handle
|
701
|
+
end
|
702
|
+
|
703
|
+
# Determines whether or not +file+ is a symlink.
|
704
|
+
#
|
705
|
+
def get_symlink(file)
|
706
|
+
bool = false
|
707
|
+
fpath = File.expand_path(file).wincode
|
708
|
+
|
709
|
+
begin
|
710
|
+
data = WIN32_FIND_DATA.new
|
711
|
+
handle = FindFirstFile(fpath, data)
|
712
|
+
|
713
|
+
if handle == INVALID_HANDLE_VALUE
|
714
|
+
raise SystemCallError.new('FindFirstFile', FFI.errno)
|
715
|
+
end
|
716
|
+
|
717
|
+
if data[:dwReserved0] == IO_REPARSE_TAG_SYMLINK
|
718
|
+
bool = true
|
719
|
+
end
|
720
|
+
ensure
|
721
|
+
FindClose(handle) if handle
|
722
|
+
end
|
723
|
+
|
724
|
+
bool
|
725
|
+
end
|
726
|
+
|
727
|
+
# Returns the filetype for the given +handle+.
|
728
|
+
#
|
729
|
+
def get_filetype(handle)
|
730
|
+
file_type = GetFileType(handle)
|
731
|
+
|
732
|
+
if file_type == FILE_TYPE_UNKNOWN && FFI.errno != NO_ERROR
|
733
|
+
raise SystemCallError.new('GetFileType', FFI.errno)
|
734
|
+
end
|
735
|
+
|
736
|
+
file_type
|
737
|
+
end
|
738
|
+
|
739
|
+
def get_streams(handle)
|
740
|
+
io_status = IO_STATUS_BLOCK.new
|
741
|
+
ptr = FFI::MemoryPointer.new(:uchar, 1024 * 64)
|
742
|
+
|
743
|
+
NtQueryInformationFile(handle, io_status, ptr, ptr.size, FileStreamInformation)
|
744
|
+
|
745
|
+
if FFI.errno != 0
|
746
|
+
raise SystemCallError.new('NtQueryInformationFile', FFI.errno)
|
747
|
+
end
|
748
|
+
|
749
|
+
arr = []
|
750
|
+
|
751
|
+
while true
|
752
|
+
info = FILE_STREAM_INFORMATION.new(ptr)
|
753
|
+
break if info[:StreamNameLength] == 0
|
754
|
+
arr << info[:StreamName].to_ptr.read_bytes(info[:StreamNameLength]).delete(0.chr)
|
755
|
+
break if info[:NextEntryOffset] == 0
|
756
|
+
info = FILE_STREAM_INFORMATION.new(ptr += info[:NextEntryOffset])
|
757
|
+
end
|
758
|
+
|
759
|
+
arr
|
760
|
+
end
|
761
|
+
|
762
|
+
# Return a sid of the file's owner.
|
763
|
+
#
|
764
|
+
def get_file_sid(file, info)
|
765
|
+
wfile = file.wincode
|
766
|
+
size_needed_ptr = FFI::MemoryPointer.new(:ulong)
|
767
|
+
|
768
|
+
# First pass, get the size needed
|
769
|
+
bool = GetFileSecurity(wfile, info, nil, 0, size_needed_ptr)
|
770
|
+
|
771
|
+
size_needed = size_needed_ptr.read_ulong
|
772
|
+
security_ptr = FFI::MemoryPointer.new(size_needed)
|
773
|
+
|
774
|
+
# Second pass, this time with the appropriately sized security pointer
|
775
|
+
bool = GetFileSecurity(wfile, info, security_ptr, security_ptr.size, size_needed_ptr)
|
776
|
+
|
777
|
+
unless bool
|
778
|
+
error = FFI.errno
|
779
|
+
return "S-1-5-80-0" if error == 32 # ERROR_SHARING_VIOLATION. Locked files, etc.
|
780
|
+
raise SystemCallError.new("GetFileSecurity", error)
|
781
|
+
end
|
782
|
+
|
783
|
+
sid_ptr = FFI::MemoryPointer.new(:pointer)
|
784
|
+
defaulted = FFI::MemoryPointer.new(:bool)
|
785
|
+
|
786
|
+
if info == OWNER_SECURITY_INFORMATION
|
787
|
+
bool = GetSecurityDescriptorOwner(security_ptr, sid_ptr, defaulted)
|
788
|
+
meth = "GetSecurityDescriptorOwner"
|
789
|
+
else
|
790
|
+
bool = GetSecurityDescriptorGroup(security_ptr, sid_ptr, defaulted)
|
791
|
+
meth = "GetSecurityDescriptorGroup"
|
792
|
+
end
|
793
|
+
|
794
|
+
raise SystemCallError.new(meth, FFI.errno) unless bool
|
795
|
+
|
796
|
+
ptr = FFI::MemoryPointer.new(:string)
|
797
|
+
|
798
|
+
unless ConvertSidToStringSid(sid_ptr.read_pointer, ptr)
|
799
|
+
raise SystemCallError.new("ConvertSidToStringSid")
|
800
|
+
end
|
801
|
+
|
802
|
+
ptr.read_pointer.read_string
|
803
|
+
end
|
804
|
+
|
805
|
+
# Return the sid of the current process.
|
806
|
+
#
|
807
|
+
def get_current_process_sid(token_type)
|
808
|
+
token = FFI::MemoryPointer.new(get_ptr_type)
|
809
|
+
sid = nil
|
810
|
+
|
811
|
+
# Get the current process sid
|
812
|
+
unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
|
813
|
+
raise SystemCallError.new("OpenProcessToken", FFI.errno)
|
814
|
+
end
|
815
|
+
|
816
|
+
begin
|
817
|
+
token = token.read_pointer.to_i
|
818
|
+
rlength = FFI::MemoryPointer.new(:pointer)
|
819
|
+
|
820
|
+
if token_type == TokenUser
|
821
|
+
buf = 0.chr * 512
|
822
|
+
else
|
823
|
+
buf = TOKEN_GROUP.new
|
824
|
+
end
|
825
|
+
|
826
|
+
unless GetTokenInformation(token, token_type, buf, buf.size, rlength)
|
827
|
+
raise SystemCallError.new("GetTokenInformation", FFI.errno)
|
828
|
+
end
|
829
|
+
|
830
|
+
if token_type == TokenUser
|
831
|
+
tsid = buf[FFI.type_size(:pointer)*2, (rlength.read_ulong - FFI.type_size(:pointer)*2)]
|
832
|
+
else
|
833
|
+
tsid = buf[:Groups][0][:Sid]
|
834
|
+
end
|
835
|
+
|
836
|
+
ptr = FFI::MemoryPointer.new(:string)
|
837
|
+
|
838
|
+
unless ConvertSidToStringSid(tsid, ptr)
|
839
|
+
raise SystemCallError.new("ConvertSidToStringSid")
|
840
|
+
end
|
841
|
+
|
842
|
+
sid = ptr.read_pointer.read_string
|
843
|
+
ensure
|
844
|
+
CloseHandle(token)
|
845
|
+
end
|
846
|
+
|
847
|
+
sid
|
848
|
+
end
|
849
|
+
|
850
|
+
# Returns whether or not the current process has given access rights for +path+.
|
851
|
+
#
|
852
|
+
def access_check(path, access_rights)
|
853
|
+
wfile = path.wincode
|
854
|
+
check = false
|
855
|
+
size_needed_ptr = FFI::MemoryPointer.new(:ulong)
|
856
|
+
|
857
|
+
flags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
|
858
|
+
DACL_SECURITY_INFORMATION
|
859
|
+
|
860
|
+
# First attempt, get the size needed
|
861
|
+
bool = GetFileSecurity(wfile, flags, nil, 0, size_needed_ptr)
|
862
|
+
|
863
|
+
# If it fails horribly here, assume the answer is no.
|
864
|
+
if !bool && FFI.errno != ERROR_INSUFFICIENT_BUFFER
|
865
|
+
return false
|
866
|
+
end
|
867
|
+
|
868
|
+
size_needed = size_needed_ptr.read_ulong
|
869
|
+
security_ptr = FFI::MemoryPointer.new(size_needed)
|
870
|
+
|
871
|
+
# Second attempt, now with the needed size
|
872
|
+
if GetFileSecurity(wfile, flags, security_ptr, size_needed, size_needed_ptr)
|
873
|
+
token = FFI::MemoryPointer.new(get_ptr_type)
|
874
|
+
|
875
|
+
pflags = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ
|
876
|
+
|
877
|
+
if OpenProcessToken(GetCurrentProcess(), pflags, token)
|
878
|
+
begin
|
879
|
+
token = token.read_pointer.to_i
|
880
|
+
token2 = FFI::MemoryPointer.new(get_ptr_type)
|
881
|
+
|
882
|
+
if DuplicateToken(token, SecurityImpersonation, token2)
|
883
|
+
begin
|
884
|
+
token2 = token2.read_pointer.to_i
|
885
|
+
mapping = GENERIC_MAPPING.new
|
886
|
+
privileges = PRIVILEGE_SET.new
|
887
|
+
privileges[:PrivilegeCount] = 0
|
888
|
+
privileges_length = privileges.size
|
889
|
+
|
890
|
+
mapping[:GenericRead] = FILE_GENERIC_READ
|
891
|
+
mapping[:GenericWrite] = FILE_GENERIC_WRITE
|
892
|
+
mapping[:GenericExecute] = FILE_GENERIC_EXECUTE
|
893
|
+
mapping[:GenericAll] = FILE_ALL_ACCESS
|
894
|
+
|
895
|
+
rights_ptr = FFI::MemoryPointer.new(:ulong)
|
896
|
+
rights_ptr.write_ulong(access_rights)
|
897
|
+
|
898
|
+
MapGenericMask(rights_ptr, mapping)
|
899
|
+
rights = rights_ptr.read_ulong
|
900
|
+
|
901
|
+
result_ptr = FFI::MemoryPointer.new(:ulong)
|
902
|
+
privileges_length_ptr = FFI::MemoryPointer.new(:ulong)
|
903
|
+
privileges_length_ptr.write_ulong(privileges_length)
|
904
|
+
granted_access_ptr = FFI::MemoryPointer.new(:ulong)
|
905
|
+
|
906
|
+
bool = AccessCheck(
|
907
|
+
security_ptr,
|
908
|
+
token2,
|
909
|
+
rights,
|
910
|
+
mapping,
|
911
|
+
privileges,
|
912
|
+
privileges_length_ptr,
|
913
|
+
granted_access_ptr,
|
914
|
+
result_ptr
|
915
|
+
)
|
916
|
+
|
917
|
+
if bool
|
918
|
+
check = result_ptr.read_ulong == 1
|
919
|
+
else
|
920
|
+
raise SystemCallError.new('AccessCheck', FFI.errno)
|
921
|
+
end
|
922
|
+
ensure
|
923
|
+
CloseHandle(token2)
|
924
|
+
end
|
925
|
+
end
|
926
|
+
ensure
|
927
|
+
CloseHandle(token)
|
928
|
+
end
|
929
|
+
end
|
930
|
+
end
|
931
|
+
|
932
|
+
check
|
933
|
+
end
|
934
|
+
|
935
|
+
# Returns whether or not the Everyone has given access rights for +path+.
|
936
|
+
#
|
937
|
+
def access_check_world(path, access_rights)
|
938
|
+
wfile = path.wincode
|
939
|
+
check = false
|
940
|
+
size_needed_ptr = FFI::MemoryPointer.new(:ulong)
|
941
|
+
|
942
|
+
flags = DACL_SECURITY_INFORMATION
|
943
|
+
|
944
|
+
# First attempt, get the size needed
|
945
|
+
bool = GetFileSecurity(wfile, flags, nil, 0, size_needed_ptr)
|
946
|
+
|
947
|
+
# If it fails horribly here, assume the answer is no.
|
948
|
+
if !bool && FFI.errno != ERROR_INSUFFICIENT_BUFFER
|
949
|
+
return false
|
950
|
+
end
|
951
|
+
|
952
|
+
size_needed = size_needed_ptr.read_ulong
|
953
|
+
security_ptr = FFI::MemoryPointer.new(size_needed)
|
954
|
+
|
955
|
+
# Second attempt, now with the needed size
|
956
|
+
if GetFileSecurity(wfile, flags, security_ptr, size_needed, size_needed_ptr)
|
957
|
+
present_ptr = FFI::MemoryPointer.new(:ulong)
|
958
|
+
pdacl_ptr = FFI::MemoryPointer.new(:pointer)
|
959
|
+
defaulted_ptr = FFI::MemoryPointer.new(:ulong)
|
960
|
+
|
961
|
+
bool = GetSecurityDescriptorDacl(
|
962
|
+
security_ptr,
|
963
|
+
present_ptr,
|
964
|
+
pdacl_ptr,
|
965
|
+
defaulted_ptr
|
966
|
+
)
|
967
|
+
|
968
|
+
# If it fails, or the dacl isn't present, return false.
|
969
|
+
if !bool || present_ptr.read_ulong == 0
|
970
|
+
return false
|
971
|
+
end
|
972
|
+
|
973
|
+
pdacl = pdacl_ptr.read_pointer
|
974
|
+
psid_ptr = FFI::MemoryPointer.new(:pointer)
|
975
|
+
|
976
|
+
# S-1-1-0 is the well known SID for "Everyone".
|
977
|
+
ConvertStringSidToSid('S-1-1-0', psid_ptr)
|
978
|
+
|
979
|
+
psid = psid_ptr.read_pointer
|
980
|
+
trustee_ptr = FFI::MemoryPointer.new(TRUSTEE)
|
981
|
+
|
982
|
+
BuildTrusteeWithSid(trustee_ptr, psid)
|
983
|
+
|
984
|
+
rights_ptr = FFI::MemoryPointer.new(:ulong)
|
985
|
+
|
986
|
+
if GetEffectiveRightsFromAcl(pdacl, trustee_ptr, rights_ptr) == NO_ERROR
|
987
|
+
rights = rights_ptr.read_ulong
|
988
|
+
check = (rights & access_rights) == access_rights
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
check
|
993
|
+
end
|
994
|
+
end
|