ffi-libfuse 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|