Class: HomeFS
- Inherits:
-
Object
- Object
- HomeFS
- Defined in:
- lib/homefs/homefs.rb,
lib/homefs/debug.rb
Overview
An instance of HomeFS; this implements all FuseFS methods, and ultimately handles reading, writing, and inspection of files.
We rely on the context provided to us by FUSE to determine the calling user’s UID and GID.
Currently, HomeFS does not support POSIX locking (which has little effect in practice), and special files (those created by mknod/FIFOs). We do supported extended filesystem attributes, but only if you have the ‘ffi-xattr’ gem.
Defined Under Namespace
Classes: Wrapper
Instance Method Summary collapse
-
#access(context, path, mode) ⇒ void
abstract
Check access permissions.
-
#chmod(context, path, mode) ⇒ void
abstract
Change file permissions.
-
#chown(context, path, uid, gid) ⇒ void
abstract
Change file ownership.
-
#create(context, path, mode, ffi) ⇒ void
abstract
Create and open a file If the file does not exist, first create it with the specified mode, and then open it.
-
#drop_priv(uid, gid) { ... } ⇒ Object
If HomeFS is running as root, drop_priv sets the effective user ID and effective group ID to uid and gid, respectively, and then calls the block passed to the method.
-
#fgetattr(context, path, ffi) ⇒ Stat
abstract
Get attributes of an open file.
-
#flush(context, path, ffi) ⇒ void
abstract
Possibly flush cached data BIG NOTE: This is not equivalent to fsync().
-
#fsync(context, path, datasync, ffi) ⇒ void
abstract
Synchronize file contents.
-
#ftruncate(context, path, size, ffi) ⇒ void
abstract
Change the size of an open file.
-
#getattr(context, path) ⇒ Stat
abstract
Get file attributes.
-
#homedir(uid) ⇒ String
Get the home directory of the user with the UID uid.
-
#homepath(uid, path = nil) ⇒ String
Return the path to the root of HomeFS relative to the underlying filesystem.
-
#init(info) ⇒ void
abstract
Called when filesystem is initialised.
-
#initialize(path, options = Hash.new) {|uid| ... } ⇒ HomeFS
constructor
Creates a new instance of HomeFS.
-
#link(context, from, to) ⇒ void
abstract
Create a hard link to file.
- #mkdir(context, path, mode) ⇒ Object
-
#open(context, path, ffi) ⇒ void
abstract
File open operation file open flags etc.
-
#opendir(context, path, name) ⇒ void
abstract
Open directory Unless the ‘default_permissions’ mount option is given, this method should check if opendir is permitted for this directory.
-
#read(context, path, size, offset, ffi) ⇒ String
abstract
Read data from an open file.
- #readdir(context, path, filler, offset, ffi) ⇒ Object
-
#readlink(context, path, size) ⇒ String
abstract
Resolve target of symbolic link.
-
#release(context, path, ffi) ⇒ void
abstract
Release an open file Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped.
- #rename(context, from, to) ⇒ Object
-
#symlink(context, to, from) ⇒ void
abstract
Create a symbolic link Create a symbolic link named “from” which, when evaluated, will lead to “to”.
-
#truncate(context, path, offset) ⇒ void
abstract
Change the size of a file.
-
#unlink(context, path) ⇒ void
abstract
Remove a file.
-
#utimens(context, path, actime, modtime) ⇒ void
abstract
Change access/modification times of a file.
-
#write(context, path, data, offset, ffi) ⇒ Integer
abstract
Write data to an open file.
Constructor Details
#initialize(path, options = Hash.new) {|uid| ... } ⇒ HomeFS
Creates a new instance of HomeFS. This instance should probably be passed to RFuse, as it does nothing on its own. The path is relative to the calling user’s home directory, that is, the user interacting with the filesystem through open(2), read(2), write(2), etc.
This method may accept a block. If a block is given, it is expected to return the base path for the given UID. If no block is given, this is the home directory for that UID instead. the file system.
29 30 31 32 33 34 |
# File 'lib/homefs/homefs.rb', line 29 def initialize(path, = Hash.new, &blk) @relpath = path @baseblock = blk || proc do |uid| homedir(uid) end end |
Instance Method Details
#access(context, path, mode) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Check access permissions
97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/homefs/homefs.rb', line 97 def access(context, path, mode) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do access = true check_read = (mode & 4 != 0) check_write = (mode & 2 != 0) check_execute = (mode & 1 != 0) access &&= File.readable?(hpath) if check_read access &&= File.writable?(hpath) if check_write access &&= File.executable?(hpath) if check_execute access end end |
#chmod(context, path, mode) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Change file permissions
122 123 124 125 126 127 128 |
# File 'lib/homefs/homefs.rb', line 122 def chmod(context, path, mode) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do File.chmod(mode, hpath) end end |
#chown(context, path, uid, gid) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Change file ownership
141 142 143 144 145 146 147 |
# File 'lib/homefs/homefs.rb', line 141 def chown(context, path, uid, gid) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do File.chown(mode, hpath) end end |
#create(context, path, mode, ffi) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Create and open a file If the file does not exist, first create it with the specified mode,
and then open it.
162 163 164 165 166 167 168 |
# File 'lib/homefs/homefs.rb', line 162 def create(context, path, mode, ffi) uid = context.uid hpath = homepath(uid, path) ffi.fh = drop_priv(uid, context.gid) do File.new(hpath, File::CREAT | File::WRONLY, mode) end end |
#drop_priv(uid, gid) { ... } ⇒ Object
If HomeFS is running as root, drop_priv sets the effective user ID and effective group ID to uid and gid, respectively, and then calls the block passed to the method. Before returning, drop_priv that the EUID and EGID are each set back to 0.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/homefs/homefs.rb', line 71 def drop_priv(uid, gid) if Process::Sys.getuid == 0 begin Process::Sys.setegid(gid) Process::Sys.seteuid(uid) ret = yield ensure Process::Sys.seteuid(0) Process::Sys.setegid(0) end else ret = yield end return ret end |
#fgetattr(context, path, ffi) ⇒ Stat
This method should usually only be called by FUSE, and not called directly
Get attributes of an open file
180 181 182 |
# File 'lib/homefs/homefs.rb', line 180 def fgetattr(context, path, ffi) ffi.fh.lstat end |
#flush(context, path, ffi) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Possibly flush cached data BIG NOTE: This is not equivalent to fsync(). It’s not a request to sync dirty data. Flush is called on each close() of a file descriptor. So if a filesystem wants to return write errors in close() and the file has cached dirty data, this is a good place to write back data and return any errors. Since many applications ignore close() errors this is not always useful.
NOTE: The flush() method may be called more than once for each open(). This happens if more than one file descriptor refers to an opened file due to dup(), dup2() or fork() calls. It is not possible to determine if a flush is final, so each flush should be treated equally. Multiple write-flush sequences are relatively rare, so this shouldn’t be a problem.
Filesystems shouldn’t assume that flush will always be called after some writes, or that if will be called at all.
210 211 |
# File 'lib/homefs/homefs.rb', line 210 def flush(context, path, ffi) # We don't do any caching end |
#fsync(context, path, datasync, ffi) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Synchronize file contents
225 226 227 228 229 230 231 |
# File 'lib/homefs/homefs.rb', line 225 def fsync(context, path, datasync, ffi) if datasync ffi.fh.fdatasync else ffi.fh.fsync end end |
#ftruncate(context, path, size, ffi) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Change the size of an open file
244 245 246 |
# File 'lib/homefs/homefs.rb', line 244 def ftruncate(context, path, size, ffi) ffi.fh.truncate(size) end |
#getattr(context, path) ⇒ Stat
This method should usually only be called by FUSE, and not called directly
Get file attributes. Similar to stat(). The ‘st_dev’ and ‘st_blksize’ fields are ignored. The ‘st_ino’ field is ignored except if the ‘use_ino’ mount option is given.
260 261 262 263 264 265 266 |
# File 'lib/homefs/homefs.rb', line 260 def getattr(context, path) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do File.lstat(hpath) end end |
#homedir(uid) ⇒ String
Get the home directory of the user with the UID uid.
39 40 41 |
# File 'lib/homefs/homefs.rb', line 39 def homedir(uid) Etc.getpwuid(uid).dir end |
#homepath(uid, path = nil) ⇒ String
Return the path to the root of HomeFS relative to the underlying filesystem. If path is specified, it is taken to be relative to the root of HomeFS.
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/homefs/homefs.rb', line 51 def homepath(uid, path = nil) basepath = @baseblock[uid] # basepath shouldn't ever be nil, but fail gracefully just # in case. raise Errno::ENOENT if basepath == nil if path File.join(basepath, @relpath, path) else File.join(basepath, @relpath) end end |
#init(info) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Called when filesystem is initialised
276 277 |
# File 'lib/homefs/homefs.rb', line 276 def init(context, info) end |
#link(context, from, to) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Create a hard link to file
289 290 291 292 293 294 295 |
# File 'lib/homefs/homefs.rb', line 289 def link(context, from, to) uid = context.uid hfrom = homepath(uid, from) drop_priv(uid, context.gid) do File.link(hfrom, to) end end |
#mkdir(context, path, mode) ⇒ Object
This method should usually only be called by FUSE, and not called directly
300 301 302 303 304 305 306 |
# File 'lib/homefs/homefs.rb', line 300 def mkdir(context, path, mode) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do Dir.mkdir(hpath, mode) end end |
#open(context, path, ffi) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
File open operation file open flags etc. The fh attribute may be used to store an arbitrary filehandle object which will be passed to all subsequent operations on this file
322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/homefs/homefs.rb', line 322 def open(context, path, ffi) uid = context.uid hpath = homepath(uid, path) # We pass the flags straight into File.open because the constants # in RFuse::Fcntl and File::Constants have the same values, because # they both have the same values as the open syscall. If this were # not the case, we'd have the map the values of RFuse::Fcntl to # their equivalents in File::Constants drop_priv(uid, context.gid) do ffi.fh = File.open(hpath, ffi.flags) end end |
#opendir(context, path, name) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Open directory Unless the ‘default_permissions’ mount option is given, this method should check if opendir is permitted for this directory. Optionally opendir may also return an arbitrary filehandle in the fuse_file_info structure, which will be available to #readdir, #fsyncdir, #releasedir.
351 352 353 354 355 356 357 |
# File 'lib/homefs/homefs.rb', line 351 def opendir(context, path, ffi) uid = context.uid hpath = homepath(uid, path) ffi.fh = drop_priv(uid, context.gid) do Dir.new(hpath) end end |
#read(context, path, size, offset, ffi) ⇒ String
This method should usually only be called by FUSE, and not called directly
Read data from an open file
372 373 374 375 376 377 378 379 |
# File 'lib/homefs/homefs.rb', line 372 def read(context, path, size, offset, ffi) if offset < 0 ffi.fh.seek(offset, :END) else ffi.fh.seek(offset, :SET) end ffi.fh.read(size) || '' end |
#readdir(context, path, filler, offset, ffi) ⇒ Object
This method should usually only be called by FUSE, and not called directly
384 385 386 387 388 389 390 391 392 |
# File 'lib/homefs/homefs.rb', line 384 def readdir(context, path, filler, offset, ffi) filler.push(".", nil, 0) filler.push("..", nil, 0) ffi.fh.pos = offset ffi.fh.each do |filename| next if filename == '.' || filename == '..' filler.push(filename, nil, 0) end end |
#readlink(context, path, size) ⇒ String
This method should usually only be called by FUSE, and not called directly
Resolve target of symbolic link
405 406 407 408 |
# File 'lib/homefs/homefs.rb', line 405 def readlink(context, path, size) # Is it okay that we return the 'real' path here? File.readlink(homepath(context.uid, path))[0 ... size] end |
#release(context, path, ffi) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Release an open file Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped.
For every #open call there will be exactly one #release call with the same flags and file descriptor. It is possible to have a file opened more than once, in which case only the last release will mean, that no more reads/writes will happen on the file.
426 427 428 |
# File 'lib/homefs/homefs.rb', line 426 def release(context, path, ffi) ffi.fh.close end |
#rename(context, from, to) ⇒ Object
This method should usually only be called by FUSE, and not called directly
433 434 435 436 437 438 439 440 |
# File 'lib/homefs/homefs.rb', line 433 def rename(context, from, to) uid = context.uid hfrom = homepath(uid, from) hto = homepath(uid, to) drop_priv(uid, context.gid) do FileUtils.mv(hfrom, hto, :force => true) end end |
#symlink(context, to, from) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Create a symbolic link Create a symbolic link named “from” which, when evaluated, will lead
to "to".
454 455 456 457 458 459 460 |
# File 'lib/homefs/homefs.rb', line 454 def symlink(context, to, from) uid = context.uid hfrom = homepath(uid, from) drop_priv(uid, context.gid) do File.symlink(to, hfrom) end end |
#truncate(context, path, offset) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Change the size of a file
472 473 474 475 476 477 478 |
# File 'lib/homefs/homefs.rb', line 472 def truncate(context, path, offset) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do File.truncate(hpath, offset) end end |
#unlink(context, path) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Remove a file
489 490 491 492 493 494 495 |
# File 'lib/homefs/homefs.rb', line 489 def unlink(context, path) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do File.unlink(hpath) end end |
#utimens(context, path, actime, modtime) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Change access/modification times of a file
508 509 510 511 512 513 514 |
# File 'lib/homefs/homefs.rb', line 508 def utimens(context, path, actime, modtime) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do File.utime(actime, modtime, hpath) end end |
#write(context, path, data, offset, ffi) ⇒ Integer
This method should usually only be called by FUSE, and not called directly
Write data to an open file
529 530 531 532 533 534 535 536 |
# File 'lib/homefs/homefs.rb', line 529 def write(context, path, data, offset, ffi) if offset < 0 ffi.fh.seek(offset, :END) else ffi.fh.seek(offset, :SET) end ffi.fh.write(data) end |