virtfs 0.0.1

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