virtfs 0.0.1

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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +8 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +154 -0
  8. data/Rakefile +5 -0
  9. data/lib/virtfs-nativefs-thick.rb +1 -0
  10. data/lib/virtfs-nativefs-thin.rb +1 -0
  11. data/lib/virtfs.rb +38 -0
  12. data/lib/virtfs/activation.rb +97 -0
  13. data/lib/virtfs/block_io.rb +140 -0
  14. data/lib/virtfs/byte_range.rb +71 -0
  15. data/lib/virtfs/context.rb +300 -0
  16. data/lib/virtfs/context_manager.rb +175 -0
  17. data/lib/virtfs/context_switch_class_methods.rb +96 -0
  18. data/lib/virtfs/delegate_module.rb +40 -0
  19. data/lib/virtfs/dir_instance_delegate.rb +3 -0
  20. data/lib/virtfs/exception.rb +13 -0
  21. data/lib/virtfs/file_instance_delegate.rb +3 -0
  22. data/lib/virtfs/file_modes_and_options.rb +293 -0
  23. data/lib/virtfs/find_class_methods.rb +106 -0
  24. data/lib/virtfs/io_buffer.rb +133 -0
  25. data/lib/virtfs/io_instance_delegate.rb +3 -0
  26. data/lib/virtfs/kernel.rb +146 -0
  27. data/lib/virtfs/nativefs/thick.rb +30 -0
  28. data/lib/virtfs/nativefs/thick/dir_class_methods.rb +38 -0
  29. data/lib/virtfs/nativefs/thick/file_class_methods.rb +178 -0
  30. data/lib/virtfs/nativefs/thin.rb +32 -0
  31. data/lib/virtfs/nativefs/thin/dir.rb +30 -0
  32. data/lib/virtfs/nativefs/thin/dir_class_methods.rb +41 -0
  33. data/lib/virtfs/nativefs/thin/file.rb +112 -0
  34. data/lib/virtfs/nativefs/thin/file_class_methods.rb +181 -0
  35. data/lib/virtfs/protofs/protofs.rb +7 -0
  36. data/lib/virtfs/protofs/protofs_base.rb +12 -0
  37. data/lib/virtfs/protofs/protofs_dir.rb +13 -0
  38. data/lib/virtfs/protofs/protofs_dir_class.rb +31 -0
  39. data/lib/virtfs/protofs/protofs_file.rb +27 -0
  40. data/lib/virtfs/protofs/protofs_file_class.rb +136 -0
  41. data/lib/virtfs/stat.rb +100 -0
  42. data/lib/virtfs/thin_dir_delegator.rb +79 -0
  43. data/lib/virtfs/thin_file_delegator.rb +77 -0
  44. data/lib/virtfs/thin_io_delegator_methods.rb +301 -0
  45. data/lib/virtfs/thin_io_delegator_methods_bufferio.rb +337 -0
  46. data/lib/virtfs/v_dir.rb +238 -0
  47. data/lib/virtfs/v_file.rb +480 -0
  48. data/lib/virtfs/v_io.rb +243 -0
  49. data/lib/virtfs/v_pathname.rb +128 -0
  50. data/lib/virtfs/version.rb +3 -0
  51. data/spec/activate_spec.rb +202 -0
  52. data/spec/chroot_spec.rb +120 -0
  53. data/spec/context_manager_class_spec.rb +246 -0
  54. data/spec/context_manager_instance_spec.rb +255 -0
  55. data/spec/context_spec.rb +335 -0
  56. data/spec/data/UTF-16LE-data.txt +0 -0
  57. data/spec/data/UTF-8-data.txt +212 -0
  58. data/spec/dir_class_spec.rb +506 -0
  59. data/spec/dir_instance_spec.rb +208 -0
  60. data/spec/file_class_spec.rb +2106 -0
  61. data/spec/file_instance_spec.rb +154 -0
  62. data/spec/file_modes_and_options_spec.rb +1556 -0
  63. data/spec/find_spec.rb +142 -0
  64. data/spec/io_bufferio_size_shared_examples.rb +371 -0
  65. data/spec/io_bufferio_size_spec.rb +861 -0
  66. data/spec/io_bufferio_spec.rb +801 -0
  67. data/spec/io_class_spec.rb +145 -0
  68. data/spec/io_instance_spec.rb +516 -0
  69. data/spec/kernel_spec.rb +285 -0
  70. data/spec/mount_spec.rb +186 -0
  71. data/spec/nativefs_local_root_spec.rb +132 -0
  72. data/spec/path_spec.rb +39 -0
  73. data/spec/spec_helper.rb +126 -0
  74. data/tasks/rspec.rake +3 -0
  75. data/tasks/yard.rake +7 -0
  76. data/test/UTF-8-demo.txt +212 -0
  77. data/test/bench.rb +18 -0
  78. data/test/bio_internal_test.rb +45 -0
  79. data/test/delegate_io.rb +31 -0
  80. data/test/delegate_module.rb +62 -0
  81. data/test/encode_test.rb +42 -0
  82. data/test/enoent_test.rb +30 -0
  83. data/test/namespace_test.rb +42 -0
  84. data/test/read_block_valid_encoding.rb +44 -0
  85. data/test/read_test.rb +78 -0
  86. data/test/stream_readers.rb +46 -0
  87. data/test/utf-16-demo.txt +0 -0
  88. data/test/utf8_to_utf16.rb +77 -0
  89. data/test/wrapper_test.rb +34 -0
  90. data/virtfs.gemspec +29 -0
  91. metadata +230 -0
@@ -0,0 +1,71 @@
1
+ module VirtFS
2
+ # ByteRange utility class, encapsulate a range of bytes as given
3
+ # by their first / last offsets
4
+ class ByteRange
5
+ attr_accessor :first, :last
6
+
7
+ def initialize(first = nil, last = nil)
8
+ set(first, last)
9
+ end
10
+
11
+ def empty?
12
+ @first.nil? || @last.nil?
13
+ end
14
+
15
+ def length
16
+ return 0 if empty?
17
+ @last - @first + 1
18
+ end
19
+
20
+ def include?(obj)
21
+ return false if empty?
22
+ return (obj.first >= @first && obj.last <= @last) if obj.is_a?(self.class)
23
+ obj >= @first && obj <= @last
24
+ end
25
+
26
+ def adjacent?(*args)
27
+ return false if empty?
28
+ nrange = range_arg(args)
29
+ nrange.first == @last + 1 || nrange.last == @first - 1
30
+ end
31
+
32
+ def overlap?(*args)
33
+ return false if empty?
34
+ nrange = range_arg(args)
35
+ include?(nrange.first) || include?(nrange.last) || nrange.include?(@first) || nrange.include?(@last)
36
+ end
37
+
38
+ def contiguous?(*args)
39
+ nrange = range_arg(args)
40
+ adjacent?(nrange) || overlap?(nrange)
41
+ end
42
+
43
+ def expand(*args)
44
+ nrange = range_arg(args)
45
+ @first = nrange.first if empty? || nrange.first < @first
46
+ @last = nrange.last if empty? || nrange.last > @last
47
+ end
48
+
49
+ def clear
50
+ set(nil, nil)
51
+ end
52
+
53
+ def set(first, last)
54
+ @first = first
55
+ @last = last
56
+ end
57
+
58
+ private
59
+
60
+ def range_arg(args)
61
+ case args.length
62
+ when 1
63
+ return args[0]
64
+ when 2
65
+ return self.class.new(args[0], args[1])
66
+ else
67
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 1..2)"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,300 @@
1
+ module VirtFS
2
+ # FS-specific state under which FS calls occur.
3
+ #
4
+ # VirtFS maps an independent context instance to each
5
+ # Ruby thread group, and internally switches to it before
6
+ # dispatching target FS calls from that thread.
7
+ # This class implements the core functionality behind the FS context
8
+ class Context
9
+ attr_reader :key
10
+
11
+ def initialize
12
+ @mount_points = []
13
+ @fs_lookup = {}
14
+ @mount_mutex = Mutex.new
15
+ @dir_mutex = Mutex.new
16
+ @saved_root = nil
17
+ @saved_cwd = nil
18
+ @root = VfsRealFile::SEPARATOR
19
+ @cwd = @root
20
+ @key = nil
21
+ end
22
+
23
+ # Set key used to uniquely identify the context
24
+ #
25
+ # @param val [String] context identifier
26
+ # @raise [RuntimeError] if key already assigned
27
+ def key=(val)
28
+ @dir_mutex.synchronize do
29
+ raise "Context already assigned to key: #{@key}" if !@key.nil? && !val.nil?
30
+ @key = val
31
+ end
32
+ end
33
+
34
+ # Mount the specified FS instance at the specified mount point.
35
+ # This registers specified fs to be accessed through the specified mount
36
+ # point via internal mechanisms. After this point any calls to this mount
37
+ # point through VirtFS under this context will be mapped through the specified
38
+ # fs instance
39
+ #
40
+ # @param fs_instance [VirtFS::FS] instance of VirtFS implementation corresponding
41
+ # to filesystem to mount
42
+ # @param mount_point [String] path which to mount filesystem under
43
+ #
44
+ # @raise [SystemCallError] if mount point cannot be resolved
45
+ # @raise [RuntimeError] if mount point is being used
46
+ def mount(fs_instance, mount_point)
47
+ mp_display = mount_point
48
+
49
+ raise "mount: invalid filesystem object #{fs_instance.class.name}" unless fs_instance.respond_to?(:mount_point)
50
+ raise "mount: filesystem is busy" if fs_instance.mount_point
51
+
52
+ begin
53
+ mount_point = full_path(mount_point, true, *cwd_root)
54
+ rescue Errno::ENOENT
55
+ raise SystemCallError.new(mp_display, Errno::ENOENT::Errno)
56
+ end
57
+ mount_point += VfsRealFile::SEPARATOR unless mount_point.end_with?(VfsRealFile::SEPARATOR)
58
+
59
+ @mount_mutex.synchronize do
60
+ raise "mount: mount point #{mp_display} is busy" if @fs_lookup[mount_point]
61
+ fs_instance.mount_point = mount_point
62
+ @fs_lookup[mount_point] = fs_instance
63
+ @mount_points.push(mount_point).sort_by!(&:length).reverse!
64
+ end
65
+ nil
66
+ end
67
+
68
+ # Unmount the FS mounted at the specified mount point
69
+ #
70
+ # @param mount_point [String] mount point to unmount
71
+ # @raise [RuntimeError] if mount point is not mounted
72
+ def umount(mount_point)
73
+ mount_point = full_path(mount_point, true, *cwd_root)
74
+ @mount_mutex.synchronize do
75
+ mp_display = mount_point
76
+ mount_point += VfsRealFile::SEPARATOR unless mount_point.end_with?(VfsRealFile::SEPARATOR)
77
+ raise "umount: nothing mounted on #{mp_display}" unless @fs_lookup[mount_point]
78
+ @fs_lookup.delete(mount_point).umount
79
+ @mount_points.delete(mount_point)
80
+ end
81
+ nil
82
+ end
83
+
84
+ # @return [Array<String>] array of mount points
85
+ def mount_points
86
+ @mount_mutex.synchronize do
87
+ @mount_points.collect do |p|
88
+ if p == VfsRealFile::SEPARATOR
89
+ VfsRealFile::SEPARATOR
90
+ else
91
+ p.chomp(VfsRealFile::SEPARATOR)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def fs_on(mount_point)
98
+ mp = full_path(mount_point, true, *cwd_root)
99
+ mp += VfsRealFile::SEPARATOR unless mp.end_with?(VfsRealFile::SEPARATOR)
100
+ @mount_mutex.synchronize do
101
+ @fs_lookup[mp]
102
+ end
103
+ end
104
+
105
+ # @return [Boolean] indicating if mount point is mounted
106
+ def mounted?(mount_point)
107
+ !fs_on(mount_point).nil?
108
+ end
109
+
110
+ # Change virtual file system root, after which all root calls to mount point will
111
+ # be mapped to specified dir
112
+ #
113
+ # @param dir [String] new dir to assign as virtfs context root
114
+ # @raise [SystemCallError] if specified dir does not exist
115
+ def chroot(dir)
116
+ raise SystemCallError.new(dir, Errno::ENOENT::Errno) unless dir_exist?(dir)
117
+ @dir_mutex.synchronize do
118
+ @root = full_path(dir, true, @cwd, @root)
119
+ @cwd = VfsRealFile::SEPARATOR
120
+ end
121
+ 0
122
+ end
123
+
124
+ # Invoke block with the specified root, restoring before returning
125
+ #
126
+ # @see chroot
127
+ def with_root(dir)
128
+ raise SystemCallError.new(dir, Errno::ENOENT::Errno) unless dir_exist?(dir)
129
+ @dir_mutex.synchronize do
130
+ raise "Cannot nest with_root blocks" unless @saved_root.nil?
131
+ @saved_root = @root
132
+ @saved_cwd = @cwd
133
+ @root = full_path(dir, true, @cwd, @root)
134
+ @cwd = VfsRealFile::SEPARATOR
135
+ end
136
+ begin
137
+ yield
138
+ ensure
139
+ @dir_mutex.synchronize do
140
+ @root = @saved_root
141
+ @cwd = @saved_cwd
142
+ @saved_root = nil
143
+ @saved_cwd = nil
144
+ end
145
+ end
146
+ end
147
+
148
+ # @return [Boolean] indicating if specified dir exists
149
+ def dir_exist?(dir)
150
+ begin
151
+ fs, p = path_lookup(dir)
152
+ rescue Errno::ENOENT
153
+ return false
154
+ end
155
+ VirtFS.fs_call(fs) { dir_exist?(p) }
156
+ end
157
+
158
+ # Change virtual filesystem working directory
159
+ #
160
+ # @param dir [String] new dir to assign to virtfs cwd
161
+ def chdir(dir)
162
+ fs = path = nil
163
+ @dir_mutex.synchronize do
164
+ nwd = remove_root(local_path(dir, @cwd, @root), @root)
165
+ fs, path = mount_lookup(nwd)
166
+ @cwd = nwd
167
+ end
168
+ fs.dir_chdir(path) if fs.respond_to?(:dir_chdir)
169
+ end
170
+
171
+ # @return [String] current filesystem working directory
172
+ def getwd
173
+ @cwd
174
+ end
175
+
176
+ # Expand symbolic links and perform mount indirection look up.
177
+ #
178
+ # @param path [String] path to lookup
179
+ # @param raise_full_path [Boolean] indicates if error should be raised if lookup fails
180
+ # @param include_last [Boolean] indicates if last path component should be returned
181
+ #
182
+ # @raise [RunTimeError] if path could not be looked up and raise_full_path is true
183
+ # @raise [SystemCallError] if path could not be looked up
184
+ #
185
+ # @api private
186
+ # @see #mount_lookup
187
+ # @see #expand_links
188
+ #
189
+ def path_lookup(path, raise_full_path = false, include_last = true)
190
+ mount_lookup(full_path(path, include_last, *cwd_root))
191
+ rescue Errno::ENOENT
192
+ raise if raise_full_path
193
+ # so we report the original path.
194
+ raise SystemCallError.new(path, Errno::ENOENT::Errno)
195
+ end
196
+
197
+ # Expand symbolic links in the path.
198
+ # This must be done here, because a symlink in one file system
199
+ # can point to a file in another filesystem.
200
+ #
201
+ # @api private
202
+ # @param p [String] path to lookup
203
+ # @param include_last [Boolean] indicates if last path component should be returned
204
+ def expand_links(p, include_last = true)
205
+ cp = VfsRealFile::SEPARATOR
206
+ components = p.split(VfsRealFile::SEPARATOR)
207
+ components.shift if components[0] == "" # root
208
+ last_component = components.pop unless include_last
209
+
210
+ #
211
+ # For each component of the path, check to see
212
+ # if it's a symbolic link. If so, expand it
213
+ # relative to its base directory.
214
+ #
215
+ components.each do |c|
216
+ ncp = VfsRealFile.join(cp, c)
217
+ #
218
+ # Each file system knows how to check for,
219
+ # and read, its own links.
220
+ #
221
+ fs, lp = mount_lookup(ncp)
222
+ if fs.file_symlink?(lp)
223
+ sl = fs.file_readlink(lp)
224
+ cp = sl[0, 1] == VfsRealFile::SEPARATOR ? sl : VfsRealFile.join(cp, sl)
225
+ else
226
+ cp = ncp
227
+ end
228
+ end
229
+ return cp if include_last
230
+ VfsRealFile.join(cp, last_component.to_s)
231
+ end
232
+
233
+ # Helper to change virtual filesystem working directory to filesystem root
234
+ # @api private
235
+ #
236
+ def cwd_root
237
+ @dir_mutex.synchronize do
238
+ return @cwd, @root
239
+ end
240
+ end
241
+
242
+ def restore_cwd_root(cwd, root)
243
+ @dir_mutex.synchronize do
244
+ @cwd = cwd if cwd
245
+ @root = root if root
246
+ end
247
+ end
248
+
249
+ private
250
+
251
+ def local_path(path, cwd, root)
252
+ lpath = path || cwd
253
+ lpath = VfsRealFile.join(cwd, path) if Pathname(path).relative?
254
+ lpath = VirtFS.normalize_path(lpath)
255
+ apply_root(lpath, root)
256
+ end
257
+
258
+ def full_path(path, include_last, cwd, root)
259
+ expand_links(local_path(path, cwd, root), include_last)
260
+ end
261
+
262
+ #
263
+ # Mount indirection look up.
264
+ # Given a path, return its corresponding file system
265
+ # and the part of the path relative to that file system.
266
+ # It assumes symbolic links have already been expanded.
267
+ # @api private
268
+ #
269
+ def mount_lookup(path) # private
270
+ spath = "#{path}#{VfsRealFile::SEPARATOR}"
271
+ @mount_mutex.synchronize do
272
+ @mount_points.each do |mp|
273
+ next if mp.length > spath.length
274
+ next unless spath.start_with?(mp)
275
+ return @fs_lookup[mp], path if mp == VfsRealFile::SEPARATOR # root
276
+ return @fs_lookup[mp], VfsRealFile::SEPARATOR if mp == spath # path is the mount point
277
+ return @fs_lookup[mp], path.sub(mp, VfsRealFile::SEPARATOR)
278
+ end
279
+ end
280
+ raise SystemCallError.new(path, Errno::ENOENT::Errno)
281
+ end
282
+
283
+ def under_root?(path, root)
284
+ return true if path == root
285
+ path.start_with?(root + VfsRealFile::SEPARATOR)
286
+ end
287
+
288
+ def apply_root(path, root)
289
+ return path if root == VfsRealFile::SEPARATOR
290
+ VfsRealFile.join(root, path)
291
+ end
292
+
293
+ def remove_root(path, root)
294
+ return path if root == VfsRealFile::SEPARATOR
295
+ return VfsRealFile::SEPARATOR if path == root
296
+ return path unless under_root?(path, root)
297
+ path.sub(root, "")
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,175 @@
1
+ module VirtFS
2
+ # Central/Global VirtFS context manager.
3
+ # Provides a singleton context management interface for use in VirtFS.
4
+ #
5
+ # Contexts are recorded here on a per-thread-group basis
6
+ class ContextManager
7
+ attr_reader :thread_group
8
+
9
+ @context_managers = {}
10
+ @context_manager_mutex = Mutex.new
11
+
12
+ def self.my_thread_group
13
+ Thread.current.group || ThreadGroup::Default
14
+ end
15
+
16
+ def self.current
17
+ @context_manager_mutex.synchronize do
18
+ @context_managers[my_thread_group] ||= ContextManager.new(my_thread_group)
19
+ end
20
+ end
21
+
22
+ def self.current!
23
+ @context_manager_mutex.synchronize do
24
+ @context_managers[my_thread_group] || raise(VirtFS::NoContextError.new)
25
+ end
26
+ end
27
+
28
+ def self.context
29
+ current.current_context
30
+ end
31
+
32
+ def self.context!
33
+ current!.current_context
34
+ end
35
+
36
+ def self.managers
37
+ @context_manager_mutex.synchronize do
38
+ @context_managers.dup
39
+ end
40
+ end
41
+
42
+ def self.manager_for(tgroup)
43
+ raise ArgumentError, "value must be a ThreadGroup object" unless tgroup.is_a?(ThreadGroup)
44
+ @context_manager_mutex.synchronize do
45
+ @context_managers[tgroup]
46
+ end
47
+ end
48
+
49
+ def self.new_manager_for(tgroup)
50
+ raise ArgumentError, "value must be a ThreadGroup object" unless tgroup.is_a?(ThreadGroup)
51
+ @context_manager_mutex.synchronize do
52
+ @context_managers[tgroup] = ContextManager.new(tgroup)
53
+ end
54
+ end
55
+
56
+ def self.remove_manager_for(tgroup)
57
+ raise ArgumentError, "value must be a ThreadGroup object" unless tgroup.is_a?(ThreadGroup)
58
+ @context_manager_mutex.synchronize do
59
+ @context_managers.delete(tgroup)
60
+ end
61
+ end
62
+
63
+ def self.reset_all
64
+ @context_manager_mutex.synchronize do
65
+ @context_managers = {}
66
+ end
67
+ end
68
+
69
+ def initialize(thread_group)
70
+ @thread_group = thread_group
71
+ @context_mutex = Mutex.new
72
+ reset
73
+ end
74
+
75
+ def [](key)
76
+ @context_mutex.synchronize do
77
+ @contexts[key]
78
+ end
79
+ end
80
+
81
+ #
82
+ # Change context without saving current context state.
83
+ #
84
+ def []=(key, context)
85
+ raise ArgumentError, "Context must be a VirtFS::Context object" if context && !context.is_a?(Context)
86
+ raise ArgumentError, "Cannot change the default context" if key == :default
87
+ @context_mutex.synchronize do
88
+ if context.nil?
89
+ ctx = @contexts.delete(key)
90
+ ctx.key = nil
91
+ return ctx
92
+ end
93
+ raise "Context for given key already exists" if @contexts[key]
94
+ context.key = key
95
+ @contexts[key] = context
96
+ end
97
+ end
98
+
99
+ def activated?
100
+ @context_mutex.synchronize do
101
+ !@saved_context.nil?
102
+ end
103
+ end
104
+
105
+ #
106
+ # Save the current context state and change context.
107
+ #
108
+ def activate!(key)
109
+ @context_mutex.synchronize do
110
+ raise "Context already activated" if @saved_context
111
+ raise "Context for given key doesn't exist" unless (ctx = @contexts[key])
112
+ @saved_context = @current_context
113
+ @current_context = ctx
114
+ @saved_context # returns the pre-activation context.
115
+ end
116
+ end
117
+
118
+ #
119
+ # Restore the context state saved by activate!
120
+ #
121
+ def deactivate!
122
+ @context_mutex.synchronize do
123
+ raise "Context not activated" unless @saved_context
124
+ ret = @current_context
125
+ @current_context = @saved_context
126
+ @saved_context = nil
127
+ ret # returns the pre-deactivated context.
128
+ end
129
+ end
130
+
131
+ def current_context
132
+ @context_mutex.synchronize do
133
+ @current_context
134
+ end
135
+ end
136
+
137
+ def current_context=(key)
138
+ @context_mutex.synchronize do
139
+ raise "Context for given key doesn't exist" unless (ctx = @contexts[key])
140
+ @current_context = ctx
141
+ end
142
+ end
143
+
144
+ def reset
145
+ @context_mutex.synchronize do
146
+ @contexts = {}
147
+ @saved_context = nil
148
+ @contexts[:default] = Context.new
149
+ @current_context = @contexts[:default]
150
+ end
151
+ end
152
+
153
+ def with(key)
154
+ activate!(key)
155
+ begin
156
+ yield
157
+ ensure
158
+ deactivate!
159
+ end
160
+ end
161
+
162
+ def without
163
+ if !activated?
164
+ yield
165
+ else
166
+ begin
167
+ saved_context = deactivate!
168
+ yield
169
+ ensure
170
+ activate!(saved_context.key)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end