sys-filesystem 1.5.5 → 1.6.0
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
- checksums.yaml.gz.sig +0 -0
- data/CHANGES.md +7 -0
- data/README.md +1 -1
- data/lib/sys/filesystem.rb +74 -4
- data/lib/sys/unix/sys/filesystem/constants/bsd.rb +42 -0
- data/lib/sys/unix/sys/filesystem/constants/darwin.rb +68 -0
- data/lib/sys/unix/sys/filesystem/constants/dragonfly.rb +3 -0
- data/lib/sys/unix/sys/filesystem/constants/freebsd.rb +100 -0
- data/lib/sys/unix/sys/filesystem/constants/generic.rb +19 -0
- data/lib/sys/unix/sys/filesystem/constants/linux.rb +43 -0
- data/lib/sys/unix/sys/filesystem/constants.rb +12 -70
- data/lib/sys/unix/sys/filesystem/functions.rb +50 -1
- data/lib/sys/unix/sys/filesystem/structs.rb +8 -0
- data/lib/sys/unix/sys/filesystem.rb +209 -44
- data/lib/sys/windows/sys/filesystem/constants/windows.rb +24 -0
- data/lib/sys/windows/sys/filesystem/constants.rb +2 -21
- data/spec/sys_filesystem_shared.rb +1 -1
- data/spec/sys_filesystem_unix_spec.rb +314 -8
- data/sys-filesystem.gemspec +3 -3
- data.tar.gz.sig +0 -0
- metadata +9 -2
- metadata.gz.sig +0 -0
|
@@ -12,32 +12,7 @@ module Sys
|
|
|
12
12
|
extend Sys::Filesystem::Functions
|
|
13
13
|
|
|
14
14
|
private_class_method :new
|
|
15
|
-
|
|
16
|
-
# Readable versions of constant names
|
|
17
|
-
OPT_NAMES = {
|
|
18
|
-
MNT_RDONLY => 'read-only',
|
|
19
|
-
MNT_SYNCHRONOUS => 'synchronous',
|
|
20
|
-
MNT_NOEXEC => 'noexec',
|
|
21
|
-
MNT_NOSUID => 'nosuid',
|
|
22
|
-
MNT_NODEV => 'nodev',
|
|
23
|
-
MNT_UNION => 'union',
|
|
24
|
-
MNT_ASYNC => 'asynchronous',
|
|
25
|
-
MNT_CPROTECT => 'content-protection',
|
|
26
|
-
MNT_EXPORTED => 'exported',
|
|
27
|
-
MNT_QUARANTINE => 'quarantined',
|
|
28
|
-
MNT_LOCAL => 'local',
|
|
29
|
-
MNT_QUOTA => 'quotas',
|
|
30
|
-
MNT_ROOTFS => 'rootfs',
|
|
31
|
-
MNT_DONTBROWSE => 'nobrowse',
|
|
32
|
-
MNT_IGNORE_OWNERSHIP => 'noowners',
|
|
33
|
-
MNT_AUTOMOUNTED => 'automounted',
|
|
34
|
-
MNT_JOURNALED => 'journaled',
|
|
35
|
-
MNT_NOUSERXATTR => 'nouserxattr',
|
|
36
|
-
MNT_DEFWRITE => 'defwrite',
|
|
37
|
-
MNT_NOATIME => 'noatime'
|
|
38
|
-
}.freeze
|
|
39
|
-
|
|
40
|
-
private_constant :OPT_NAMES
|
|
15
|
+
private_class_method(*Sys::Filesystem::Functions.attached_functions.keys)
|
|
41
16
|
|
|
42
17
|
# File used to read mount informtion from.
|
|
43
18
|
if File.exist?('/etc/mtab')
|
|
@@ -105,6 +80,18 @@ module Sys
|
|
|
105
80
|
# The filesystem type, e.g. UFS.
|
|
106
81
|
attr_accessor :base_type
|
|
107
82
|
|
|
83
|
+
# The name of the mounted resource.
|
|
84
|
+
attr_accessor :mount_source
|
|
85
|
+
|
|
86
|
+
# The mount point/directory.
|
|
87
|
+
attr_accessor :mount_point
|
|
88
|
+
|
|
89
|
+
# The type of filesystem mount, e.g. ufs, zfs, nfs, etc.
|
|
90
|
+
attr_accessor :mount_type
|
|
91
|
+
|
|
92
|
+
# A list of comma separated options for the mount, e.g. nosuid, etc.
|
|
93
|
+
attr_accessor :mount_options
|
|
94
|
+
|
|
108
95
|
# The filesystem type
|
|
109
96
|
attr_accessor :filesystem_type
|
|
110
97
|
|
|
@@ -144,6 +131,16 @@ module Sys
|
|
|
144
131
|
@flags = nil
|
|
145
132
|
@name_max = nil
|
|
146
133
|
@base_type = nil
|
|
134
|
+
@mount_source = nil
|
|
135
|
+
@mount_point = nil
|
|
136
|
+
@mount_type = nil
|
|
137
|
+
@mount_options = nil
|
|
138
|
+
@filesystem_type = nil
|
|
139
|
+
@owner = nil
|
|
140
|
+
@sync_reads = nil
|
|
141
|
+
@sync_writes = nil
|
|
142
|
+
@async_reads = nil
|
|
143
|
+
@async_writes = nil
|
|
147
144
|
end
|
|
148
145
|
|
|
149
146
|
# Returns the total space on the partition.
|
|
@@ -268,6 +265,36 @@ module Sys
|
|
|
268
265
|
obj.base_type = fs[:f_basetype].to_s
|
|
269
266
|
end
|
|
270
267
|
|
|
268
|
+
# Keep statvfs as the portable source for capacity/inode data, but use
|
|
269
|
+
# native statfs where available for mount metadata that POSIX statvfs
|
|
270
|
+
# does not expose. On FreeBSD this includes the filesystem type name,
|
|
271
|
+
# mount source/target, native MNT_* flags, owner, and I/O counters.
|
|
272
|
+
if respond_to?(:statfs, true)
|
|
273
|
+
native_fs = Statfs.new
|
|
274
|
+
|
|
275
|
+
if statfs(path, native_fs) < 0
|
|
276
|
+
raise Error, "statfs() function failed: #{strerror(FFI.errno)}"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
obj.flags = native_fs[:f_flags] if native_fs.members.include?(:f_flags)
|
|
280
|
+
obj.name_max = native_fs[:f_namemax] if native_fs.members.include?(:f_namemax)
|
|
281
|
+
obj.base_type = native_fs[:f_fstypename].to_s if native_fs.members.include?(:f_fstypename)
|
|
282
|
+
obj.mount_type = native_fs[:f_fstypename].to_s if native_fs.members.include?(:f_fstypename)
|
|
283
|
+
obj.mount_source = native_fs[:f_mntfromname].to_s if native_fs.members.include?(:f_mntfromname)
|
|
284
|
+
obj.mount_point = native_fs[:f_mntonname].to_s if native_fs.members.include?(:f_mntonname)
|
|
285
|
+
obj.mount_options = decode_mount_options(native_fs[:f_flags]) if native_fs.members.include?(:f_flags)
|
|
286
|
+
obj.filesystem_type = native_fs[:f_type] if native_fs.members.include?(:f_type)
|
|
287
|
+
obj.owner = native_fs[:f_owner] if native_fs.members.include?(:f_owner)
|
|
288
|
+
obj.sync_reads = native_fs[:f_syncreads] if native_fs.members.include?(:f_syncreads)
|
|
289
|
+
obj.async_reads = native_fs[:f_asyncreads] if native_fs.members.include?(:f_asyncreads)
|
|
290
|
+
obj.sync_writes = native_fs[:f_syncwrites] if native_fs.members.include?(:f_syncwrites)
|
|
291
|
+
obj.async_writes = native_fs[:f_asyncwrites] if native_fs.members.include?(:f_asyncwrites)
|
|
292
|
+
|
|
293
|
+
if native_fs.members.include?(:f_fsid)
|
|
294
|
+
obj.filesystem_id = normalize_filesystem_id(native_fs[:f_fsid])
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
271
298
|
# DragonFlyBSD has additional struct members
|
|
272
299
|
if RbConfig::CONFIG['host_os'] =~ /dragonfly/i
|
|
273
300
|
obj.owner = fs[:f_owner]
|
|
@@ -278,9 +305,106 @@ module Sys
|
|
|
278
305
|
obj.async_writes = fs[:f_asyncwrites]
|
|
279
306
|
end
|
|
280
307
|
|
|
308
|
+
enrich_mount_metadata(obj) unless obj.mount_point
|
|
309
|
+
|
|
281
310
|
obj.freeze
|
|
282
311
|
end
|
|
283
312
|
|
|
313
|
+
def self.enrich_mount_metadata(stat)
|
|
314
|
+
mount = mount_for_path(stat.path)
|
|
315
|
+
return unless mount
|
|
316
|
+
|
|
317
|
+
stat.mount_source = mount.name
|
|
318
|
+
stat.mount_point = mount.mount_point
|
|
319
|
+
stat.mount_type = mount.mount_type
|
|
320
|
+
stat.mount_options = mount.options
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
private_class_method :enrich_mount_metadata
|
|
324
|
+
|
|
325
|
+
def self.mount_for_path(path)
|
|
326
|
+
mount_path = mount_point(path)
|
|
327
|
+
mounts.find{ |mount| mount.mount_point == mount_path }
|
|
328
|
+
rescue SystemCallError
|
|
329
|
+
nil
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
private_class_method :mount_for_path
|
|
333
|
+
|
|
334
|
+
def self.normalize_filesystem_id(fsid)
|
|
335
|
+
return fsid unless fsid.respond_to?(:to_a)
|
|
336
|
+
|
|
337
|
+
high, low = fsid.to_a
|
|
338
|
+
((high & 0xffffffff) << 32) | (low & 0xffffffff)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
private_class_method :normalize_filesystem_id
|
|
342
|
+
|
|
343
|
+
def self.zfs_property(dataset, property)
|
|
344
|
+
return nil unless respond_to?(:libzfs_init, true)
|
|
345
|
+
|
|
346
|
+
cache = zfs_property_cache if property == 'casesensitivity'
|
|
347
|
+
key = [dataset, property]
|
|
348
|
+
|
|
349
|
+
return cache[key] if cache&.key?(key)
|
|
350
|
+
|
|
351
|
+
handle = libzfs_init
|
|
352
|
+
return nil if handle.null?
|
|
353
|
+
|
|
354
|
+
zfs_handle = nil
|
|
355
|
+
value = nil
|
|
356
|
+
|
|
357
|
+
begin
|
|
358
|
+
prop = zfs_name_to_prop(property)
|
|
359
|
+
return nil if prop < 0
|
|
360
|
+
|
|
361
|
+
zfs_handle = zfs_open(handle, dataset, 1) # ZFS_TYPE_FILESYSTEM
|
|
362
|
+
return nil if zfs_handle.null?
|
|
363
|
+
|
|
364
|
+
buffer = FFI::MemoryPointer.new(:char, 8192)
|
|
365
|
+
|
|
366
|
+
if zfs_prop_get(zfs_handle, prop, buffer, buffer.size, nil, nil, 0, 0).zero?
|
|
367
|
+
value = buffer.read_string
|
|
368
|
+
end
|
|
369
|
+
ensure
|
|
370
|
+
zfs_close(zfs_handle) if zfs_handle && !zfs_handle.null?
|
|
371
|
+
libzfs_fini(handle)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
cache[key] = value if cache && value
|
|
375
|
+
value
|
|
376
|
+
rescue FFI::NotFoundError, SystemCallError
|
|
377
|
+
nil
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
private_class_method :zfs_property
|
|
381
|
+
|
|
382
|
+
def self.zfs_property_cache
|
|
383
|
+
@zfs_property_cache ||= {}
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
private_class_method :zfs_property_cache
|
|
387
|
+
|
|
388
|
+
def self.decode_mount_options(flags)
|
|
389
|
+
string = ''
|
|
390
|
+
visible_flags = flags & MNT_VISFLAGMASK
|
|
391
|
+
|
|
392
|
+
Constants::MOUNT_OPTION_NAMES.each do |key, val|
|
|
393
|
+
if visible_flags & key > 0
|
|
394
|
+
if string.empty?
|
|
395
|
+
string += val
|
|
396
|
+
else
|
|
397
|
+
string += ", #{val}"
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
visible_flags &= ~key
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
string
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
private_class_method :decode_mount_options
|
|
407
|
+
|
|
284
408
|
# In block form, yields a Sys::Filesystem::Mount object for each mounted
|
|
285
409
|
# filesytem on the host. Otherwise it returns an array of Mount objects.
|
|
286
410
|
#
|
|
@@ -315,21 +439,7 @@ module Sys
|
|
|
315
439
|
obj.mount_point = mnt[:f_mntonname].to_s
|
|
316
440
|
obj.mount_type = mnt[:f_fstypename].to_s
|
|
317
441
|
|
|
318
|
-
|
|
319
|
-
flags = mnt[:f_flags] & MNT_VISFLAGMASK
|
|
320
|
-
|
|
321
|
-
OPT_NAMES.each do |key, val|
|
|
322
|
-
if flags & key > 0
|
|
323
|
-
if string.empty?
|
|
324
|
-
string += val
|
|
325
|
-
else
|
|
326
|
-
string += ", #{val}"
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
flags &= ~key
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
obj.options = string
|
|
442
|
+
obj.options = decode_mount_options(mnt[:f_flags])
|
|
333
443
|
|
|
334
444
|
if block_given?
|
|
335
445
|
yield obj.freeze
|
|
@@ -428,13 +538,68 @@ module Sys
|
|
|
428
538
|
# Sys::Filesystem.mount('/dev/loop0', '/home/you/tmp', 'ext4', Sys::Filesystem::MNT_RDONLY)
|
|
429
539
|
#
|
|
430
540
|
def self.mount(source, target, fstype = 'ext2', flags = 0, data = nil)
|
|
431
|
-
|
|
541
|
+
result =
|
|
542
|
+
if respond_to?(:nmount_c, true)
|
|
543
|
+
iov = nmount_iovec(source, target, fstype, data)
|
|
544
|
+
nmount_c(iov[:pointer], iov[:count], flags)
|
|
545
|
+
elsif RbConfig::CONFIG['host_os'] =~ /linux/i
|
|
546
|
+
mount_c(source, target, fstype, flags, data)
|
|
547
|
+
else
|
|
548
|
+
mount_c(fstype, target, flags, mount_data_pointer(source, data))
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
if result != 0
|
|
432
552
|
raise Error, "mount() function failed: #{strerror(FFI.errno)}"
|
|
433
553
|
end
|
|
434
554
|
|
|
435
555
|
self
|
|
436
556
|
end
|
|
437
557
|
|
|
558
|
+
def self.nmount_iovec(source, target, fstype, data)
|
|
559
|
+
options = {
|
|
560
|
+
'fstype' => fstype,
|
|
561
|
+
'fspath' => target
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
options['from'] = source if source
|
|
565
|
+
|
|
566
|
+
if data
|
|
567
|
+
unless data.respond_to?(:to_hash)
|
|
568
|
+
raise ArgumentError, 'data must be a Hash of nmount option names and values'
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
data.to_hash.each{ |key, value| options[key.to_s] = value }
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
strings = options.flat_map do |key, value|
|
|
575
|
+
[FFI::MemoryPointer.from_string(key), FFI::MemoryPointer.from_string(value.to_s)]
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
pointer = FFI::MemoryPointer.new(Iovec, strings.length)
|
|
579
|
+
|
|
580
|
+
strings.each_with_index do |string, index|
|
|
581
|
+
iov = Iovec.new(pointer + (index * Iovec.size))
|
|
582
|
+
iov[:iov_base] = string
|
|
583
|
+
iov[:iov_len] = string.size
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
{ pointer: pointer, count: strings.length, strings: strings }
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
private_class_method :nmount_iovec
|
|
590
|
+
|
|
591
|
+
def self.mount_data_pointer(source, data)
|
|
592
|
+
if data
|
|
593
|
+
FFI::MemoryPointer.from_string(data.to_s)
|
|
594
|
+
elsif source
|
|
595
|
+
FFI::MemoryPointer.from_string(source.to_s)
|
|
596
|
+
else
|
|
597
|
+
FFI::Pointer::NULL
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
private_class_method :mount_data_pointer
|
|
602
|
+
|
|
438
603
|
# Removes the attachment of the (topmost) filesystem mounted on target.
|
|
439
604
|
# You may also specify bitwise OR'd +flags+ to control the precise behavior.
|
|
440
605
|
# The possible flags on Linux are:
|
|
@@ -446,8 +611,8 @@ module Sys
|
|
|
446
611
|
#
|
|
447
612
|
# * UMOUNT_NOFOLLOW - Don't dereference the target if it's a symbolic link.
|
|
448
613
|
#
|
|
449
|
-
#
|
|
450
|
-
#
|
|
614
|
+
# On BSD platforms, MNT_FORCE may be used to force an unmount. FreeBSD also
|
|
615
|
+
# supports MNT_BYFSID for unmounting by filesystem id.
|
|
451
616
|
#
|
|
452
617
|
# Typically this method requires admin privileges.
|
|
453
618
|
#
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sys
|
|
4
|
+
class Filesystem
|
|
5
|
+
module Constants
|
|
6
|
+
MAXPATH = 260
|
|
7
|
+
|
|
8
|
+
CASE_SENSITIVE_SEARCH = 0x00000001
|
|
9
|
+
CASE_PRESERVED_NAMES = 0x00000002
|
|
10
|
+
UNICODE_ON_DISK = 0x00000004
|
|
11
|
+
PERSISTENT_ACLS = 0x00000008
|
|
12
|
+
FILE_COMPRESSION = 0x00000010
|
|
13
|
+
VOLUME_QUOTAS = 0x00000020
|
|
14
|
+
SUPPORTS_SPARSE_FILES = 0x00000040
|
|
15
|
+
SUPPORTS_REPARSE_POINTS = 0x00000080
|
|
16
|
+
SUPPORTS_REMOTE_STORAGE = 0x00000100
|
|
17
|
+
VOLUME_IS_COMPRESSED = 0x00008000
|
|
18
|
+
SUPPORTS_OBJECT_IDS = 0x00010000
|
|
19
|
+
SUPPORTS_ENCRYPTION = 0x00020000
|
|
20
|
+
NAMED_STREAMS = 0x00040000
|
|
21
|
+
READ_ONLY_VOLUME = 0x00080000
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
class Filesystem
|
|
3
|
-
module Constants
|
|
4
|
-
MAXPATH = 260
|
|
1
|
+
# frozen_string_literal: true
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
CASE_PRESERVED_NAMES = 0x00000002
|
|
8
|
-
UNICODE_ON_DISK = 0x00000004
|
|
9
|
-
PERSISTENT_ACLS = 0x00000008
|
|
10
|
-
FILE_COMPRESSION = 0x00000010
|
|
11
|
-
VOLUME_QUOTAS = 0x00000020
|
|
12
|
-
SUPPORTS_SPARSE_FILES = 0x00000040
|
|
13
|
-
SUPPORTS_REPARSE_POINTS = 0x00000080
|
|
14
|
-
SUPPORTS_REMOTE_STORAGE = 0x00000100
|
|
15
|
-
VOLUME_IS_COMPRESSED = 0x00008000
|
|
16
|
-
SUPPORTS_OBJECT_IDS = 0x00010000
|
|
17
|
-
SUPPORTS_ENCRYPTION = 0x00020000
|
|
18
|
-
NAMED_STREAMS = 0x00040000
|
|
19
|
-
READ_ONLY_VOLUME = 0x00080000
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
3
|
+
require_relative 'constants/windows'
|
|
@@ -4,7 +4,7 @@ require 'sys-filesystem'
|
|
|
4
4
|
|
|
5
5
|
RSpec.shared_examples Sys::Filesystem do
|
|
6
6
|
example 'version number is set to the expected value' do
|
|
7
|
-
expect(Sys::Filesystem::VERSION).to eq('1.
|
|
7
|
+
expect(Sys::Filesystem::VERSION).to eq('1.6.0')
|
|
8
8
|
expect(Sys::Filesystem::VERSION).to be_frozen
|
|
9
9
|
end
|
|
10
10
|
|