Class: HomeFS
- Inherits:
-
Object
- Object
- HomeFS
- Defined in:
- lib/homefs/homefs.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. If you want to access an instance of this class directly, you may set the UID and GID of the current thread like so
Thread.current[:fusefs_reader_uid] = uid
Thread.current[:fusefs_reader_gid] = gid
where ‘uid’ and ‘gid’ are the desired UID and GID you wish to emulate.
Instance Method Summary collapse
-
#can_delete?(path) ⇒ void
Test if we can delete the file given by path.
-
#can_mkdir?(path) ⇒ Boolean
Test if we can make a directory at the given path.
-
#can_rmdir?(path) ⇒ Boolean
Test whether we can remove the directory given by path.
-
#can_write?(path) ⇒ Boolean
Test whether we can write out to the given path.
-
#contents(path) ⇒ Array<String>
List the contents of a directory.
-
#delete(path) ⇒ void
Delete the file given by path.
-
#directory?(path) ⇒ Boolean
Test whether path is the name of a directory.
-
#executable?(path) ⇒ Boolean
Test whether the file given by path is executable.
-
#file?(path) ⇒ Boolean
Test whether path is the name of a file (i.e., not a directory).
-
#homepath(path = nil) ⇒ String
Return the path to the root of HomeFS relative to the underlying filesystem.
-
#initialize(path) ⇒ HomeFS
constructor
Creates a new instance of HomeFS.
-
#mkdir(path) ⇒ void
Make a new directory at path.
-
#mode_mask(file, mask, check_ids = true) ⇒ Boolean
Check whether the given file has a mode that matches the given mask.
-
#raw_close(path, raw = nil) ⇒ void
Close the file handle given by raw.
-
#raw_open(path, mode, rfusefs = nil) ⇒ File
Open a file handle to the file given by path, with the mode given by mode.
-
#raw_read(path, offset, size, raw = nil) ⇒ String
Read size bytes, starting at offset, from the file handle given by raw (which is returned by a call to raw_open).
-
#raw_sync(path, datasync, raw = nil) ⇒ void
Sync writes to the underlying filesystem.
-
#raw_write(path, off, sz, buf, raw = nil) ⇒ void
Write sz bytes from buf to the file handle raw, starting at off.
-
#read_file(path) ⇒ String
Read the contents of the file given by path.
-
#read_group ⇒ void
Read or re-read /etc/group to update the internal table of group group membership.
-
#read_passwd ⇒ void
Read or re-read /etc/passwd to update the internal table of home directories for each UID.
-
#rename(from_path, to_path) ⇒ void
Rename the file or directory at from_path to to_path.
-
#rmdir(path) ⇒ void
Remove the directory at the given path.
-
#size(path) ⇒ Integer
Get the size of the file at path.
-
#times(path) ⇒ Array<Time>
Get the times (atime, mtime, ctime) for the file at the given path.
-
#touch(path, modtime) ⇒ void
Update the mtime and atime (last-modified and last-accessed time) of the file at path to the time given by modtime.
-
#writable?(path) ⇒ Boolean
Test whether we can write to the given path.
-
#write_to(path, str) ⇒ void
Write the given data to the given path.
Constructor Details
#initialize(path) ⇒ HomeFS
Creates a new instance of HomeFS. This instance should probably be passed to FuseFS, 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.
21 22 23 24 25 |
# File 'lib/homefs/homefs.rb', line 21 def initialize(path) @relpath = path read_passwd read_group end |
Instance Method Details
#can_delete?(path) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Test if we can delete the file given by path.
243 244 245 |
# File 'lib/homefs/homefs.rb', line 243 def can_delete?(path) writable?(File.dirname(homepath(path))) end |
#can_mkdir?(path) ⇒ Boolean
This method should usually only be called by FUSE, and not called directly
Test if we can make a directory at the given path.
225 226 227 |
# File 'lib/homefs/homefs.rb', line 225 def can_mkdir?(path) writable?(homepath(path)) end |
#can_rmdir?(path) ⇒ Boolean
This method should usually only be called by FUSE, and not called directly
Test whether we can remove the directory given by path.
206 207 208 209 |
# File 'lib/homefs/homefs.rb', line 206 def can_rmdir?(path) File.writable?(homepath(path)) && Dir.new(path).to_a.size == 2 end |
#can_write?(path) ⇒ Boolean
This method should usually only be called by FUSE, and not called directly
Test whether we can write out to the given path.
262 263 264 |
# File 'lib/homefs/homefs.rb', line 262 def can_write?(path) writable?(homepath(path)) end |
#contents(path) ⇒ Array<String>
This method should usually only be called by FUSE, and not called directly
List the contents of a directory. This includes ‘.’ and ‘..’ (the current directory and its parent).
305 306 307 |
# File 'lib/homefs/homefs.rb', line 305 def contents(path) Dir.new(homepath(path)).to_a end |
#delete(path) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Delete the file given by path. This is similar to rm(1).
234 235 236 |
# File 'lib/homefs/homefs.rb', line 234 def delete(path) FileUtils.rm(homepath(path)) end |
#directory?(path) ⇒ Boolean
This method should usually only be called by FUSE, and not called directly
Test whether path is the name of a directory
332 333 334 |
# File 'lib/homefs/homefs.rb', line 332 def directory?(path) File.directory?(homepath(path)) end |
#executable?(path) ⇒ Boolean
This method should usually only be called by FUSE, and not called directly
Test whether the file given by path is executable.
284 285 286 |
# File 'lib/homefs/homefs.rb', line 284 def executable?(path) mode_mask(homepath(path), 0111) end |
#file?(path) ⇒ Boolean
This method should usually only be called by FUSE, and not called directly
Test whether path is the name of a file (i.e., not a directory)
323 324 325 |
# File 'lib/homefs/homefs.rb', line 323 def file?(path) File.file?(homepath(path)) end |
#homepath(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.
87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/homefs/homefs.rb', line 87 def homepath(path = nil) uid = FuseFS.reader_uid 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 |
#mkdir(path) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Make a new directory at path. This is similar to mkdir(1).
216 217 218 |
# File 'lib/homefs/homefs.rb', line 216 def mkdir(path) FileUtils.mkdir(homepath(path)) end |
#mode_mask(file, mask, check_ids = true) ⇒ Boolean
Check whether the given file has a mode that matches the given mask. If check_ids is true, the third-least significant digit of the mask is set to zero if the calling user is not the owner of the file, and the second-least significant digit of the mask is zeroed if the calling user’s group is not the group of the file. This has the effect of only checking the owner’s permissions if we’re the owner, and only checking the group permissions if we’re in that group.
This method checks if any of the set bits in the mask are set in the file’s mode. It does not check if the mask matches the file’s mode, nor does it check if all set bits in the mask are set in the file’s mode.
The first three least significant digits of the mode, in octal, (that is, the three rightmost digits) control the read (4), write (2), and execute (1) permissions of the file. The next digit controls setuid (4), setgid (2), and the sticky bit (1) of the file.
It is recommended to see
man chmod
to get a more detailed description about file modes.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/homefs/homefs.rb', line 136 def mode_mask(file, mask, check_ids = true) stat = File.stat(file) fmode = stat.mode if check_ids fuid, fgid = stat.uid, stat.gid uid = FuseFS.reader_uid # Zero out the third digit (in octal). # We could use a constant here, but this works # for a mask of any length if uid != fuid mask &= ~(mask & 0700) end if !@groups[fgid].include?(uid) mask &= ~(mask & 0070) end end fmode & mask != 0 end |
#raw_close(path, raw = nil) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Close the file handle given by raw.
394 395 396 397 |
# File 'lib/homefs/homefs.rb', line 394 def raw_close(path, raw = nil) return if raw.nil? # ??? raw.close end |
#raw_open(path, mode, rfusefs = nil) ⇒ File
This method should usually only be called by FUSE, and not called directly
Open a file handle to the file given by path, with the mode given by mode. Right now, the supported modes are “rw”, “w”, and “r”, for read/write, write, and read. If the mode is write or read/write and no file exists at path, on will be created.
346 347 348 349 350 351 352 353 |
# File 'lib/homefs/homefs.rb', line 346 def raw_open(path, mode, rfusefs = nil) mode = case mode when "rw" then File::RDWR | File::CREAT | File::BINARY when "r" then File::RDONLY | File::BINARY when "w" then File::WRONLY | File::CREAT | File::BINARY end File.open(homepath(path), mode) end |
#raw_read(path, offset, size, raw = nil) ⇒ String
This method should usually only be called by FUSE, and not called directly
Read size bytes, starting at offset, from the file handle given by raw (which is returned by a call to raw_open). If raw is nil and a path is given, a file will be opened at path, and closed after the data is read. Otherwise, path is ignored.
367 368 369 370 371 372 373 |
# File 'lib/homefs/homefs.rb', line 367 def raw_read(path, offset, size, raw = nil) file = raw || raw_open(path, "r") file.seek(offset, :SET) file.read(size) ensure file.close if raw.nil? end |
#raw_sync(path, datasync, raw = nil) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Sync writes to the underlying filesystem. If raw is nil, no operation is performed.
383 384 385 386 |
# File 'lib/homefs/homefs.rb', line 383 def raw_sync(path, datasync, raw = nil) return if raw.nil? # Should we sync anyway? raw.fdatasync end |
#raw_write(path, off, sz, buf, raw = nil) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Write sz bytes from buf to the file handle raw, starting at off. If no file handle is given, one will be opened at the given path, and closed after the write is complete.
411 412 413 414 415 416 417 |
# File 'lib/homefs/homefs.rb', line 411 def raw_write(path, off, sz, buf, raw = nil) file = raw || File.open(path, "w") file.seek(off, :SET) file.write(buf[0...sz]) ensure file.close if raw.nil? end |
#read_file(path) ⇒ String
This method should usually only be called by FUSE, and not called directly
Read the contents of the file given by path.
293 294 295 296 297 |
# File 'lib/homefs/homefs.rb', line 293 def read_file(path) File.open(homepath(path), "rb") do |file| file.read end end |
#read_group ⇒ void
This method returns an undefined value.
Read or re-read /etc/group to update the internal table of group group membership.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/homefs/homefs.rb', line 60 def read_group # Try to read from getent group = `getent group` if $? != 0 group = File.read('/etc/group') end @groups = Hash.new group.each_line.map { |line| next if line.strip.empty? _name, _, gid, members = line.split(':') members = members.strip.split(',').map {|member| p member; @usernames[member]} [Integer(gid), members] }.reject { |line| line.nil? }.each { |gid, members| @groups[gid] = members } 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 |
#rename(from_path, to_path) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Rename the file or directory at from_path to to_path. This is mostly equivalent to /bin/mv.
176 177 178 |
# File 'lib/homefs/homefs.rb', line 176 def rename(from_path, to_path) FileUtils.mv(homepath(from_path), homepath(to_path)) end |
#rmdir(path) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Remove the directory at the given path. The directory must be empty. This is mostly equivalent to rmdir(1).
197 198 199 |
# File 'lib/homefs/homefs.rb', line 197 def rmdir(path) FileUtils.rmdir(homepath(path)) end |
#size(path) ⇒ Integer
This method should usually only be called by FUSE, and not called directly
Get the size of the file at path
314 315 316 |
# File 'lib/homefs/homefs.rb', line 314 def size(path) File.size(homepath(path)) end |
#times(path) ⇒ Array<Time>
This method should usually only be called by FUSE, and not called directly
Get the times (atime, mtime, ctime) for the file at the given path.
272 273 274 275 276 277 |
# File 'lib/homefs/homefs.rb', line 272 def times(path) atime = File.atime(homepath(path)) mtime = File.mtime(homepath(path)) ctime = File.ctime(homepath(path)) [atime, mtime, ctime] end |
#touch(path, modtime) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Update the mtime and atime (last-modified and last-accessed time) of the file at path to the time given by modtime.
187 188 189 |
# File 'lib/homefs/homefs.rb', line 187 def touch(path, modtime) File.utime(modtime, modtime, homepath(path)) end |
#writable?(path) ⇒ Boolean
Test whether we can write to the given path. If no file exists at path, we test if the parent directory is either writable or has the sticky bit set.
160 161 162 163 164 165 166 167 |
# File 'lib/homefs/homefs.rb', line 160 def writable?(path) return true if mode_mask(path, 0222) if !File.exist?(path) mode_mask(File.dirname(path), 01222) else false end end |
#write_to(path, str) ⇒ void
This method should usually only be called by FUSE, and not called directly
This method returns an undefined value.
Write the given data to the given path.
253 254 255 |
# File 'lib/homefs/homefs.rb', line 253 def write_to(path, str) File.open(homepath(path), "wb") {|file| file.write(str) } end |