ffi-libfuse 0.0.1.pre
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 +7 -0
- data/.yardopts +1 -0
- data/README.md +100 -0
- data/lib/ffi/accessors.rb +145 -0
- data/lib/ffi/devt.rb +30 -0
- data/lib/ffi/flock.rb +47 -0
- data/lib/ffi/gnu_extensions.rb +115 -0
- data/lib/ffi/libfuse/ackbar.rb +112 -0
- data/lib/ffi/libfuse/adapter/context.rb +37 -0
- data/lib/ffi/libfuse/adapter/debug.rb +89 -0
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
- data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
- data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
- data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
- data/lib/ffi/libfuse/adapter/safe.rb +58 -0
- data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
- data/lib/ffi/libfuse/adapter.rb +79 -0
- data/lib/ffi/libfuse/callbacks.rb +61 -0
- data/lib/ffi/libfuse/fuse2.rb +159 -0
- data/lib/ffi/libfuse/fuse3.rb +162 -0
- data/lib/ffi/libfuse/fuse_args.rb +166 -0
- data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
- data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
- data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
- data/lib/ffi/libfuse/fuse_common.rb +249 -0
- data/lib/ffi/libfuse/fuse_config.rb +205 -0
- data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
- data/lib/ffi/libfuse/fuse_context.rb +79 -0
- data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
- data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
- data/lib/ffi/libfuse/fuse_operations.rb +870 -0
- data/lib/ffi/libfuse/fuse_opt.rb +54 -0
- data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
- data/lib/ffi/libfuse/fuse_version.rb +43 -0
- data/lib/ffi/libfuse/job_pool.rb +53 -0
- data/lib/ffi/libfuse/main.rb +200 -0
- data/lib/ffi/libfuse/test/operations.rb +56 -0
- data/lib/ffi/libfuse/test.rb +3 -0
- data/lib/ffi/libfuse/thread_pool.rb +147 -0
- data/lib/ffi/libfuse/version.rb +8 -0
- data/lib/ffi/libfuse.rb +24 -0
- data/lib/ffi/ruby_object.rb +95 -0
- data/lib/ffi/stat/constants.rb +29 -0
- data/lib/ffi/stat/native.rb +50 -0
- data/lib/ffi/stat/time_spec.rb +137 -0
- data/lib/ffi/stat.rb +96 -0
- data/lib/ffi/stat_vfs.rb +81 -0
- data/lib/ffi/struct_array.rb +39 -0
- data/lib/ffi/struct_wrapper.rb +100 -0
- data/sample/memory_fs.rb +189 -0
- data/sample/no_fs.rb +69 -0
- metadata +165 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
# A self-expanding and self-limiting ThreadPool
|
8
|
+
#
|
9
|
+
# The first thread is created on ThreadPool.new and additional threads are added through ThreadPool.busy
|
10
|
+
# called from within a worker iteration
|
11
|
+
#
|
12
|
+
# A pool thread will end when
|
13
|
+
#
|
14
|
+
# * a worker iteration returns false or nil
|
15
|
+
# * a worker thread raises an exception (silently for StopIteration)
|
16
|
+
# * max_idle_threads is exceeded
|
17
|
+
class ThreadPool
|
18
|
+
class << self
|
19
|
+
# Starts a new thread if the current thread is a thread pool member and there are no other idle threads in the
|
20
|
+
# pool. The thread is marked as busy for the duration of the yield.
|
21
|
+
#
|
22
|
+
# If the current thread is not a pool member then simply yields
|
23
|
+
def busy(&block)
|
24
|
+
if (tp = Thread.current[:tp])
|
25
|
+
tp.busy(&block)
|
26
|
+
elsif block_given?
|
27
|
+
yield
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [ThreadGroup] the enclosed thread group to which the pool's threads are added
|
33
|
+
attr_reader :group
|
34
|
+
|
35
|
+
# Create a new ThreadPool
|
36
|
+
#
|
37
|
+
# @param [Integer] max_idle The maximum number of idle threads (>= 0)
|
38
|
+
# @param [Integer] max_active The maximum number of active threads (> 0)
|
39
|
+
# @param [String] name A prefix used to set Thread.name for pool threads
|
40
|
+
# @param [Proc] worker The worker called repeatedly within each pool thread
|
41
|
+
# @see ThreadPool.busy
|
42
|
+
def initialize(max_idle: nil, max_active: nil, name: nil, &worker)
|
43
|
+
raise ArgumentError, "max_active #{max_active} must be > 0" if max_active && !max_active.positive?
|
44
|
+
raise ArgumentError, "max_idle: #{max_idle} must be >= 0" if max_idle&.negative?
|
45
|
+
raise ArgumentError, 'must have worker block but none given' unless worker
|
46
|
+
|
47
|
+
@max_idle = max_idle
|
48
|
+
@max_active = max_active
|
49
|
+
@name = name
|
50
|
+
@worker = worker
|
51
|
+
@mutex = Mutex.new
|
52
|
+
@size = 0
|
53
|
+
@busy = 0
|
54
|
+
@idle_death = Set.new
|
55
|
+
@completed = Queue.new
|
56
|
+
@group = ThreadGroup.new.add(synchronize { start_thread }).enclose
|
57
|
+
end
|
58
|
+
|
59
|
+
# Join the ThreadPool optionally handling thread completion
|
60
|
+
# @return [void]
|
61
|
+
# @yield (thread, error = nil)
|
62
|
+
# @yieldparam [Thread] thread a Thread that has finished
|
63
|
+
# @yieldparam [StandardError] error if thread raised an exception
|
64
|
+
def join
|
65
|
+
while (t = @completed.pop)
|
66
|
+
begin
|
67
|
+
t.join
|
68
|
+
yield t if block_given?
|
69
|
+
rescue StandardError => e
|
70
|
+
yield t, e if block_given?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
def busy
|
78
|
+
mark_busy
|
79
|
+
yield if block_given?
|
80
|
+
ensure
|
81
|
+
ensure_not_busy if block_given?
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Array<Thread>,Array<Thread>] busy,idle threads
|
85
|
+
def list
|
86
|
+
group.list.partition { |t| t[:tp_busy] }
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def mark_busy
|
92
|
+
return if Thread.current[:tp_busy]
|
93
|
+
|
94
|
+
Thread.current[:tp_busy] = true
|
95
|
+
synchronize { start_thread if (@busy += 1) == @size }
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_reader :mutex, :worker, :name
|
99
|
+
|
100
|
+
def start_thread
|
101
|
+
return if @max_active && @size >= @max_active
|
102
|
+
|
103
|
+
@size += 1
|
104
|
+
Thread.new { worker_thread }
|
105
|
+
end
|
106
|
+
|
107
|
+
def worker_thread
|
108
|
+
Thread.current.name = "#{name}-#{Thread.current.object_id.to_s(16)}" if name
|
109
|
+
Thread.current[:tp] = self
|
110
|
+
loop while invoke && !idle_limit_exceeded?
|
111
|
+
ensure
|
112
|
+
@completed << Thread.current
|
113
|
+
@completed.close if decrement_size.zero?
|
114
|
+
end
|
115
|
+
|
116
|
+
def invoke
|
117
|
+
worker.call
|
118
|
+
ensure
|
119
|
+
ensure_not_busy
|
120
|
+
end
|
121
|
+
|
122
|
+
def ensure_not_busy
|
123
|
+
return unless Thread.current[:tp_busy]
|
124
|
+
|
125
|
+
Thread.current[:tp_busy] = false
|
126
|
+
synchronize { @busy -= 1 }
|
127
|
+
end
|
128
|
+
|
129
|
+
def decrement_size
|
130
|
+
synchronize do
|
131
|
+
@idle_death.delete(Thread.current)
|
132
|
+
@size -= 1
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def idle_limit_exceeded?
|
137
|
+
return false unless @max_idle
|
138
|
+
|
139
|
+
synchronize { (@size - @busy - @idle_death.size - 1) > @max_idle && @idle_death << Thread.current }
|
140
|
+
end
|
141
|
+
|
142
|
+
def synchronize(&block)
|
143
|
+
@mutex.synchronize(&block)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/ffi/libfuse.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'libfuse/fuse_version'
|
4
|
+
require_relative 'libfuse/fuse2' if FFI::Libfuse::FUSE_MAJOR_VERSION == 2
|
5
|
+
require_relative 'libfuse/fuse3' if FFI::Libfuse::FUSE_MAJOR_VERSION == 3
|
6
|
+
require_relative 'libfuse/main'
|
7
|
+
require_relative 'libfuse/adapter'
|
8
|
+
require_relative 'devt'
|
9
|
+
|
10
|
+
module FFI
|
11
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
12
|
+
module Libfuse
|
13
|
+
class << self
|
14
|
+
# Filesystem entry point
|
15
|
+
# @note This main function defaults to single-threaded operation by injecting the '-s' option. Pass `$0,*ARGV`
|
16
|
+
# if your filesystem can usefully support multi-threaded operation.
|
17
|
+
#
|
18
|
+
# @see Main.fuse_main
|
19
|
+
def fuse_main(*argv, operations:, args: argv.any? ? argv : [$0, '-s', *ARGV], private_data: nil)
|
20
|
+
Main.fuse_main(args: args, operations: operations, private_data: private_data) || -1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'weakref'
|
4
|
+
require 'ffi'
|
5
|
+
|
6
|
+
module FFI
|
7
|
+
# FFI::DataConverter to pass ruby objects as void*
|
8
|
+
#
|
9
|
+
# Objects are held in a global map of object id to WeakRef of the object.
|
10
|
+
#
|
11
|
+
# ie caller that puts a RubyObject into a library is responsible for keeping that object from the GC.
|
12
|
+
#
|
13
|
+
# Note this relies on the GVL to be threadsafe(ish)
|
14
|
+
module RubyObject
|
15
|
+
# Convert to cast ruby object *long to arbitrary integer type
|
16
|
+
# @!visibility private
|
17
|
+
class CastAsInt
|
18
|
+
include DataConverter
|
19
|
+
attr_reader :native_type
|
20
|
+
|
21
|
+
def initialize(int_type)
|
22
|
+
@native_type = FFI.find_type(int_type)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_native(obj, context)
|
26
|
+
return 0 if obj.nil?
|
27
|
+
|
28
|
+
RubyObject.to_native(obj, context).get(:long, 0)
|
29
|
+
end
|
30
|
+
|
31
|
+
def from_native(object_id, _context)
|
32
|
+
return nil if object_id.zero?
|
33
|
+
|
34
|
+
_ptr, obj = RubyObject.cache[object_id]
|
35
|
+
obj
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
extend FFI::DataConverter
|
40
|
+
native_type FFI::Type::POINTER
|
41
|
+
|
42
|
+
# rubocop:disable Lint/HashCompareByIdentity
|
43
|
+
class << self
|
44
|
+
# Store a ruby object reference in an integer type
|
45
|
+
# @param [FFI::Type] int_type
|
46
|
+
def by_object_id(int_type = :long)
|
47
|
+
CastAsInt.new(int_type)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @!visibility private
|
51
|
+
|
52
|
+
# convert pointer to ruby object
|
53
|
+
def to_native(obj, _context)
|
54
|
+
return FFI::Pointer::NULL if obj.nil?
|
55
|
+
|
56
|
+
# return previous pointer if we've stored this object already
|
57
|
+
# not threadsafe!
|
58
|
+
ptr, _ref = cache[obj.object_id] ||= store(obj)
|
59
|
+
# Keep the pointer address as a type safety check, so we don't read memory we didn't store
|
60
|
+
cache[ptr.address.object_id] = true
|
61
|
+
ptr
|
62
|
+
end
|
63
|
+
|
64
|
+
def from_native(ptr, _context)
|
65
|
+
return nil if ptr.null?
|
66
|
+
raise TypeError, "No RubyObject stored at #{ptr.address}" unless cache.key?(ptr.address.object_id)
|
67
|
+
|
68
|
+
_ptr, obj = cache[ptr.get(:long, 0)]
|
69
|
+
obj
|
70
|
+
end
|
71
|
+
|
72
|
+
def cache
|
73
|
+
@cache ||= {}
|
74
|
+
end
|
75
|
+
|
76
|
+
def finalizer(*keys)
|
77
|
+
proc { keys.each { cache.delete(key) } }
|
78
|
+
end
|
79
|
+
|
80
|
+
def store(obj)
|
81
|
+
ptr = FFI::MemoryPointer.new(:long)
|
82
|
+
ptr.put(:long, 0, obj.object_id)
|
83
|
+
|
84
|
+
unless obj.frozen?
|
85
|
+
# Clean the cache when obj is finalised
|
86
|
+
ObjectSpace.define_finalizer(obj, finalizer(obj.object_id, ptr.address.object_id))
|
87
|
+
obj = WeakRef.new(obj)
|
88
|
+
end
|
89
|
+
|
90
|
+
[ptr, obj]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# rubocop:enable Lint/HashCompareByIdentity
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
class Stat
|
5
|
+
# File type mask
|
6
|
+
S_IFMT = 0o170000
|
7
|
+
|
8
|
+
# FIFO
|
9
|
+
S_IFIFO = 0o010000
|
10
|
+
|
11
|
+
# Character device
|
12
|
+
S_IFCHR = 0o020000
|
13
|
+
|
14
|
+
# Directory
|
15
|
+
S_IFDIR = 0o040000
|
16
|
+
|
17
|
+
# Block device
|
18
|
+
S_IFBLK = 0o060000
|
19
|
+
|
20
|
+
# Regular file
|
21
|
+
S_IFREG = 0o100000
|
22
|
+
|
23
|
+
# Symbolic link
|
24
|
+
S_IFLNK = 0o120000
|
25
|
+
|
26
|
+
# Socket
|
27
|
+
S_IFSOCK = 0o140000
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require_relative 'time_spec'
|
5
|
+
|
6
|
+
module FFI
|
7
|
+
class Stat
|
8
|
+
# Native (and naked) stat from stat.h
|
9
|
+
# @!visibility private
|
10
|
+
class Native < Struct
|
11
|
+
case Platform::NAME
|
12
|
+
|
13
|
+
when 'x86_64-linux'
|
14
|
+
layout :st_dev, :dev_t,
|
15
|
+
:st_ino, :ino_t,
|
16
|
+
:st_nlink, :nlink_t,
|
17
|
+
:st_mode, :mode_t,
|
18
|
+
:st_uid, :uid_t,
|
19
|
+
:st_gid, :gid_t,
|
20
|
+
:__pad0, :int,
|
21
|
+
:st_rdev, :dev_t,
|
22
|
+
:st_size, :off_t,
|
23
|
+
:st_blksize, :blksize_t,
|
24
|
+
:st_blocks, :blkcnt_t,
|
25
|
+
:st_atimespec, TimeSpec,
|
26
|
+
:st_mtimespec, TimeSpec,
|
27
|
+
:st_ctimespec, TimeSpec
|
28
|
+
|
29
|
+
when 'x65_64-darwin'
|
30
|
+
layout :st_dev, :dev_t,
|
31
|
+
:st_ino, :uint32,
|
32
|
+
:st_mode, :mode_t,
|
33
|
+
:st_nlink, :nlink_t,
|
34
|
+
:st_uid, :uid_t,
|
35
|
+
:st_gid, :gid_t,
|
36
|
+
:st_rdev, :dev_t,
|
37
|
+
:st_atimespec, TimeSpec,
|
38
|
+
:st_mtimespec, TimeSpec,
|
39
|
+
:st_ctimespec, TimeSpec,
|
40
|
+
:st_size, :off_t,
|
41
|
+
:st_blocks, :blkcnt_t,
|
42
|
+
:st_blksize, :blksize_t,
|
43
|
+
:st_flags, :uint32,
|
44
|
+
:st_gen, :uint32
|
45
|
+
else
|
46
|
+
raise NotImplementedError, "FFI::Stat not implemented for FFI::Platform #{Platform::NAME}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require 'ffi/struct_array'
|
5
|
+
|
6
|
+
module FFI
|
7
|
+
class Stat
|
8
|
+
# Timespec from stat.h
|
9
|
+
class TimeSpec < FFI::Struct
|
10
|
+
extend StructArray
|
11
|
+
|
12
|
+
# Special nsec value representing the current time - see utimensat(2)
|
13
|
+
UTIME_NOW = (1 << 30) - 1
|
14
|
+
# Special nsec value representing a request to omit setting this time - see utimensat(2)
|
15
|
+
UTIME_OMIT = (1 << 30) - 2
|
16
|
+
|
17
|
+
# A fixed TimeSpec representing the current time
|
18
|
+
def self.now
|
19
|
+
@now ||= new.set_time(0, UTIME_NOW)
|
20
|
+
end
|
21
|
+
|
22
|
+
# A fixed TimeSpec representing a request to omit setting this time
|
23
|
+
def self.omit
|
24
|
+
@omit ||= new.set_time(0, UTIME_OMIT)
|
25
|
+
end
|
26
|
+
|
27
|
+
layout(
|
28
|
+
tv_sec: :time_t,
|
29
|
+
tv_nsec: :long
|
30
|
+
)
|
31
|
+
|
32
|
+
# @!attribute [r] tv_sec
|
33
|
+
# @return [Integer] number of seconds since epoch
|
34
|
+
def tv_sec
|
35
|
+
self[:tv_sec]
|
36
|
+
end
|
37
|
+
alias sec tv_sec
|
38
|
+
|
39
|
+
# @!attribute [r] tv_nsec
|
40
|
+
# @return [Integer] additional number of nanoseconds
|
41
|
+
def tv_nsec
|
42
|
+
self[:tv_nsec]
|
43
|
+
end
|
44
|
+
alias nsec tv_nsec
|
45
|
+
|
46
|
+
# @overload set_time(time)
|
47
|
+
# @param [Time] time
|
48
|
+
# @return [TimeSpec] self
|
49
|
+
# @overload set_time(sec,nsec=0)
|
50
|
+
# @param [Integer] sec number of (nano/micro)seconds from epoch, precision depending on nsec
|
51
|
+
# @param [Symbol|Integer] nsec
|
52
|
+
# - :nsec to treat sec as number of nanoseconds since epoch
|
53
|
+
# - :usec to treat sec as number of microseconds since epoch
|
54
|
+
# - Integer to treat sec as number of seconds since epoch, and nsec as additional nanoseconds
|
55
|
+
#
|
56
|
+
# @return [TimeSpec] self
|
57
|
+
def set_time(sec, nsec = 0)
|
58
|
+
return set_time(sec.to_i, sec.nsec) if sec.is_a?(Time)
|
59
|
+
|
60
|
+
case nsec
|
61
|
+
when :nsec
|
62
|
+
return set_time(sec / (10**9), sec % (10**9))
|
63
|
+
when :usec
|
64
|
+
return set_time(sec / (10**6), sec % (10**6))
|
65
|
+
when Integer
|
66
|
+
self[:tv_sec] = sec
|
67
|
+
self[:tv_nsec] = nsec
|
68
|
+
else
|
69
|
+
raise ArgumentError, "Invalid nsec=#{nsec}"
|
70
|
+
end
|
71
|
+
|
72
|
+
self
|
73
|
+
end
|
74
|
+
alias :time= set_time
|
75
|
+
|
76
|
+
# @return [Boolean] true if this value represents the special value {UTIME_NOW}
|
77
|
+
def now?
|
78
|
+
tv_nsec == UTIME_NOW
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Boolean] true if this value represents the special value {UTIME_OMIT}
|
82
|
+
def omit?
|
83
|
+
tv_nsec == UTIME_OMIT
|
84
|
+
end
|
85
|
+
|
86
|
+
# Convert to Time
|
87
|
+
# @param [Time|nil] now
|
88
|
+
# optional value to use if {now?} is true. If not set then Time.now will be used
|
89
|
+
# @return [nil] if {omit?} is true
|
90
|
+
# @return [Time] this value as ruby Time in UTC
|
91
|
+
def time(now = nil)
|
92
|
+
return nil if omit?
|
93
|
+
return (now || Time.now).utc if now?
|
94
|
+
|
95
|
+
Time.at(sec, nsec, :nsec, in: 0).utc
|
96
|
+
end
|
97
|
+
|
98
|
+
# Convert to Integer
|
99
|
+
# @param [Time|nil] now
|
100
|
+
# optional value to use if {now?} is true. If not set then Time.now will be used
|
101
|
+
# @return [nil] if {omit?} is true
|
102
|
+
# @return [Integer] number of nanoseconds since epoch
|
103
|
+
def nanos(now = nil)
|
104
|
+
return nil if omit?
|
105
|
+
|
106
|
+
t = now? ? (now || Time.now) : self
|
107
|
+
t.tv_sec * 10**9 + t.tv_nsec
|
108
|
+
end
|
109
|
+
|
110
|
+
# Convert to Float
|
111
|
+
# @param [Time|nil] now
|
112
|
+
# optional value to use if {now?} is true. If not set then Time.now will be used
|
113
|
+
# @return [nil] if {omit?} is true
|
114
|
+
# @return [Float] seconds and fractional seconds since epoch
|
115
|
+
def to_f(now = nil)
|
116
|
+
return nil if omit?
|
117
|
+
|
118
|
+
t = now? ? (now || Time.now) : self
|
119
|
+
t.tv_sec.to_f + t.tv_nsec.to_f / (10**9)
|
120
|
+
end
|
121
|
+
|
122
|
+
# @!visibility private
|
123
|
+
def inspect
|
124
|
+
ns =
|
125
|
+
case nsec
|
126
|
+
when UTIME_NOW
|
127
|
+
'NOW'
|
128
|
+
when UTIME_OMIT
|
129
|
+
'OMIT'
|
130
|
+
else
|
131
|
+
nsec
|
132
|
+
end
|
133
|
+
"#{self.class.name}:#{sec}.#{ns}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|