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.
-
#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) ⇒ 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.
-
#read_passwd ⇒ void
Read or re-read /etc/passwd to update the internal table of home directories for each UID.
- #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) ⇒ 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. the file system.
22 23 24 25 |
# File 'lib/homefs/homefs.rb', line 22 def initialize(path, = Hash.new) @relpath = path read_passwd 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
111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/homefs/homefs.rb', line 111 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
136 137 138 139 140 141 142 |
# File 'lib/homefs/homefs.rb', line 136 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
155 156 157 158 159 160 161 |
# File 'lib/homefs/homefs.rb', line 155 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.
176 177 178 179 180 181 182 |
# File 'lib/homefs/homefs.rb', line 176 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.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/homefs/homefs.rb', line 85 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
194 195 196 |
# File 'lib/homefs/homefs.rb', line 194 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.
224 225 |
# File 'lib/homefs/homefs.rb', line 224 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
239 240 241 242 243 244 245 |
# File 'lib/homefs/homefs.rb', line 239 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
258 259 260 |
# File 'lib/homefs/homefs.rb', line 258 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.
274 275 276 277 278 279 280 |
# File 'lib/homefs/homefs.rb', line 274 def getattr(context, path) uid = context.uid hpath = homepath(uid, path) drop_priv(uid, context.gid) do File.lstat(hpath) end 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.
65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/homefs/homefs.rb', line 65 def homepath(uid, path = nil) basepath = @homedirs[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
290 291 |
# File 'lib/homefs/homefs.rb', line 290 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
303 304 305 306 307 308 309 |
# File 'lib/homefs/homefs.rb', line 303 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
314 315 316 317 318 319 320 |
# File 'lib/homefs/homefs.rb', line 314 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
336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/homefs/homefs.rb', line 336 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.
365 366 367 368 369 370 371 |
# File 'lib/homefs/homefs.rb', line 365 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
386 387 388 389 390 391 392 393 |
# File 'lib/homefs/homefs.rb', line 386 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 |
#read_passwd ⇒ void
This method returns an undefined value.
Read or re-read /etc/passwd to update the internal table of home directories for each UID. This method does not try to guess as to which user IDs are owned by actual users of the system, and which are utility accounts such as ‘lp’ or ‘pulse’.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/homefs/homefs.rb', line 32 def read_passwd # Try to read from getent passwd = `getent passwd` if $? != 0 passwd = File.read('/etc/passwd') end @homedirs = Hash.new @usernames = Hash.new # Here we map each line in passwd to an array, # which Hash interprets as a key, value pair. passwd.each_line.map { |line| next if line.strip.empty? values = line.split(':') # Username, UID, home directory [values[0], Integer(values[2]), values[5]] }.reject { |line| line.nil? }.each { |username, uid, home| @homedirs[uid] = home @usernames[username] = uid } end |
#readdir(context, path, filler, offset, ffi) ⇒ Object
This method should usually only be called by FUSE, and not called directly
398 399 400 401 402 403 404 405 406 |
# File 'lib/homefs/homefs.rb', line 398 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
419 420 421 422 |
# File 'lib/homefs/homefs.rb', line 419 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.
440 441 442 |
# File 'lib/homefs/homefs.rb', line 440 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
447 448 449 450 451 452 453 454 |
# File 'lib/homefs/homefs.rb', line 447 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".
468 469 470 471 472 473 474 |
# File 'lib/homefs/homefs.rb', line 468 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
486 487 488 489 490 491 492 |
# File 'lib/homefs/homefs.rb', line 486 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
503 504 505 506 507 508 509 |
# File 'lib/homefs/homefs.rb', line 503 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
522 523 524 525 526 527 528 |
# File 'lib/homefs/homefs.rb', line 522 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
543 544 545 546 547 548 549 550 |
# File 'lib/homefs/homefs.rb', line 543 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 |