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.
@@ -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
- string = ''
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
- if mount_c(source, target, fstype, flags, data) != 0
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
- # Note that BSD platforms may support different flags. Please see the man
450
- # pages for details.
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
- module Sys
2
- class Filesystem
3
- module Constants
4
- MAXPATH = 260
1
+ # frozen_string_literal: true
5
2
 
6
- CASE_SENSITIVE_SEARCH = 0x00000001
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.5.5')
7
+ expect(Sys::Filesystem::VERSION).to eq('1.6.0')
8
8
  expect(Sys::Filesystem::VERSION).to be_frozen
9
9
  end
10
10