homefs 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/homefs/homefs.rb +283 -37
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e74057ef8ec81a7efa25cb238fa1d960ec7fa727
|
4
|
+
data.tar.gz: 6a9bff9e3760f6d4f35f5a368849e17d7caecb07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60a6183228c6436b89a30581a85e47211b9bcf1a56f6fe4e0c50d63b30929c1aa456266be3e2dfde148cec82dbbe062131f57be173d2875c149125e6f83f42cf
|
7
|
+
data.tar.gz: 0a58275485f48cafdc452161d78cdd030b57e9965b22c15608c443db60842386cbdfa4b5d164e0751387cb5f0d0752689af2a634db8e12bff09c34d94603b061
|
data/lib/homefs/homefs.rb
CHANGED
@@ -1,25 +1,89 @@
|
|
1
1
|
require 'rfusefs'
|
2
2
|
|
3
|
+
# An instance of HomeFS; this implements all FuseFS methods, and ultimately
|
4
|
+
# handles reading, writing, and inspection of files.
|
5
|
+
#
|
6
|
+
# We rely on the context provided to us by FUSE to determine the calling user's
|
7
|
+
# UID and GID. If you want to access an instance of this class directly, you may
|
8
|
+
# set the UID and GID of the current thread like so
|
9
|
+
# Thread.current[:fusefs_reader_uid] = uid
|
10
|
+
# Thread.current[:fusefs_reader_gid] = gid
|
11
|
+
# where 'uid' and 'gid' are the desired UID and GID you wish to emulate.
|
12
|
+
# @author Dylan Frese
|
3
13
|
class HomeFS
|
14
|
+
|
15
|
+
# Creates a new instance of HomeFS. This instance should probably be passed
|
16
|
+
# to FuseFS, as it does nothing on its own. The path is relative to the
|
17
|
+
# calling user's home directory, that is, the user interacting with the
|
18
|
+
# filesystem through open(2), read(2), write(2), etc.
|
19
|
+
# @param [String] path the path, relative to $HOME, to use as the root of
|
20
|
+
# the file system.
|
4
21
|
def initialize(path)
|
5
22
|
@relpath = path
|
6
|
-
@dirlinks = Hash.new
|
7
23
|
read_passwd
|
24
|
+
read_group
|
8
25
|
end
|
9
26
|
|
27
|
+
# Read or re-read /etc/passwd to update the internal table of home
|
28
|
+
# directories for each UID. This method does not try to guess as to which
|
29
|
+
# user IDs are owned by actual users of the system, and which are utility
|
30
|
+
# accounts such as 'lp' or 'pulse'.
|
31
|
+
# @return [void]
|
10
32
|
def read_passwd
|
11
|
-
#
|
33
|
+
# Try to read from getent
|
34
|
+
passwd = `getent passwd`
|
35
|
+
if $? != 0
|
36
|
+
passwd = File.read('/etc/passwd')
|
37
|
+
end
|
38
|
+
|
39
|
+
@homedirs = Hash.new
|
40
|
+
@usernames = Hash.new
|
41
|
+
|
42
|
+
# Here we map each line in passwd to an array,
|
12
43
|
# which Hash interprets as a key, value pair.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
44
|
+
passwd.each_line.map { |line|
|
45
|
+
next if line.strip.empty?
|
46
|
+
values = line.split(':')
|
47
|
+
# Username, UID, home directory
|
48
|
+
[values[0], Integer(values[2]), values[5]]
|
49
|
+
}.reject { |line|
|
50
|
+
line.nil?
|
51
|
+
}.each { |username, uid, home|
|
52
|
+
@homedirs[uid] = home
|
53
|
+
@usernames[username] = uid
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
# Read or re-read /etc/group to update the internal table of group
|
58
|
+
# group membership.
|
59
|
+
# @return [void]
|
60
|
+
def read_group
|
61
|
+
# Try to read from getent
|
62
|
+
group = `getent group`
|
63
|
+
if $? != 0
|
64
|
+
group = File.read('/etc/group')
|
65
|
+
end
|
66
|
+
|
67
|
+
@groups = Hash.new
|
68
|
+
|
69
|
+
group.each_line.map { |line|
|
70
|
+
next if line.strip.empty?
|
71
|
+
_name, _, gid, members = line.split(':')
|
72
|
+
members = members.strip.split(',').map {|member| p member; @usernames[member]}
|
73
|
+
[Integer(gid), members]
|
74
|
+
}.reject { |line|
|
75
|
+
line.nil?
|
76
|
+
}.each { |gid, members|
|
77
|
+
@groups[gid] = members
|
78
|
+
}
|
21
79
|
end
|
22
80
|
|
81
|
+
# Return the path to the root of HomeFS relative to the underlying
|
82
|
+
# filesystem. If _path_ is specified, it is taken to be relative to the
|
83
|
+
# root of HomeFS.
|
84
|
+
# @return [String] path to the root of the HomeFS relative to the underlying
|
85
|
+
# filesystem, or if _path_ is specified, the path to that resource
|
86
|
+
# relative to the underlying filesystem.
|
23
87
|
def homepath(path = nil)
|
24
88
|
uid = FuseFS.reader_uid
|
25
89
|
basepath = @homedirs[uid]
|
@@ -27,85 +91,184 @@ class HomeFS
|
|
27
91
|
# in case.
|
28
92
|
raise Errno::ENOENT if basepath == nil
|
29
93
|
if path
|
30
|
-
|
94
|
+
File.join(basepath, @relpath, path)
|
31
95
|
else
|
32
|
-
|
96
|
+
File.join(basepath, @relpath)
|
33
97
|
end
|
34
98
|
end
|
35
99
|
|
100
|
+
# Check whether the given file has a mode that matches the given mask. If
|
101
|
+
# check_ids is true, the third-least significant digit of the mask is set to
|
102
|
+
# zero if the calling user is not the owner of the file, and the
|
103
|
+
# second-least significant digit of the mask is zeroed if the calling user's
|
104
|
+
# group is not the group of the file. This has the effect of only checking
|
105
|
+
# the owner's permissions if we're the owner, and only checking the group
|
106
|
+
# permissions if we're in that group.
|
107
|
+
#
|
108
|
+
# This method checks if _any_ of the set bits in the mask are set in the
|
109
|
+
# file's mode. It does not check if the mask matches the file's mode, nor
|
110
|
+
# does it check if _all_ set bits in the mask are set in the file's mode.
|
111
|
+
#
|
112
|
+
# The first three least significant digits of the mode, in octal, (that is,
|
113
|
+
# the three rightmost digits) control the read (4), write (2), and execute
|
114
|
+
# (1) permissions of the file. The next digit controls setuid (4), setgid
|
115
|
+
# (2), and the sticky bit (1) of the file.
|
116
|
+
#
|
117
|
+
# It is recommended to see
|
118
|
+
# man chmod
|
119
|
+
# to get a more detailed description about file modes.
|
120
|
+
#
|
121
|
+
# @example Test if we can write to a file
|
122
|
+
# mode_mask("/example", 0222, true)
|
123
|
+
# mode_mask("/example", 0222)
|
124
|
+
# @example Test if a file is executable by anyone
|
125
|
+
# mode_mask("/binary", 0111, false)
|
126
|
+
# @example Test if a directory has the sticky bit set
|
127
|
+
# mode_mask("/tmp", 01000, false)
|
128
|
+
#
|
129
|
+
# @param [String] file the path to the file to check
|
130
|
+
# @param [Integer] mask the mask against which to check the file's mode.
|
131
|
+
# It is recommended you write this in octal (with a leading 0).
|
132
|
+
# @param [Boolean] check_ids if true, only check user and group permissions
|
133
|
+
# if the caller is, respectively, the owner of the file/in the file's
|
134
|
+
# group.
|
135
|
+
# @return [Boolean] whether the mode of _file_ matches the given mask
|
36
136
|
def mode_mask(file, mask, check_ids = true)
|
37
137
|
stat = File.stat(file)
|
38
|
-
# First digit: setuid (4), setgid (2), sticky bit (1)
|
39
|
-
# Second, third, and fourth digits: user, group, and
|
40
|
-
# world permissions for read (4), write (2), execute (1)
|
41
|
-
# (see `man chmod` for more)
|
42
138
|
fmode = stat.mode
|
43
139
|
if check_ids
|
44
140
|
fuid, fgid = stat.uid, stat.gid
|
45
|
-
uid
|
141
|
+
uid = FuseFS.reader_uid
|
46
142
|
# Zero out the third digit (in octal).
|
47
143
|
# We could use a constant here, but this works
|
48
144
|
# for a mask of any length
|
49
145
|
if uid != fuid
|
50
146
|
mask &= ~(mask & 0700)
|
51
147
|
end
|
52
|
-
if
|
148
|
+
if !@groups[fgid].include?(uid)
|
53
149
|
mask &= ~(mask & 0070)
|
54
150
|
end
|
55
151
|
end
|
56
152
|
fmode & mask != 0
|
57
153
|
end
|
58
154
|
|
155
|
+
# Test whether we can write to the given path. If no file exists at _path_,
|
156
|
+
# we test if the parent directory is either writable or has the sticky bit
|
157
|
+
# set.
|
158
|
+
# @param [String] path the path to test
|
159
|
+
# @return [Boolean] whether the given path is writable
|
160
|
+
def writable?(path)
|
161
|
+
return true if mode_mask(path, 0222)
|
162
|
+
if !File.exist?(path)
|
163
|
+
mode_mask(File.dirname(path), 01222)
|
164
|
+
else
|
165
|
+
false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Rename the file or directory at from_path to to_path. This is mostly
|
170
|
+
# equivalent to /bin/mv.
|
171
|
+
# @param [String] from_path the path of the file to be moved
|
172
|
+
# @param [String] to_path the destination of the file
|
173
|
+
# @return [void]
|
174
|
+
# @note This method should usually only be called by FUSE, and not called
|
175
|
+
# directly
|
59
176
|
def rename(from_path, to_path)
|
60
177
|
FileUtils.mv(homepath(from_path), homepath(to_path))
|
61
178
|
end
|
62
179
|
|
180
|
+
# Update the mtime and atime (last-modified and last-accessed time) of the
|
181
|
+
# file at _path_ to the time given by modtime.
|
182
|
+
# @param [String] path the path to the file to touch
|
183
|
+
# @param [Time] modtime the time to update the file's times to
|
184
|
+
# @return [void]
|
185
|
+
# @note This method should usually only be called by FUSE, and not called
|
186
|
+
# directly
|
63
187
|
def touch(path, modtime)
|
64
188
|
File.utime(modtime, modtime, homepath(path))
|
65
189
|
end
|
66
190
|
|
191
|
+
# Remove the directory at the given path. The directory must be empty. This
|
192
|
+
# is mostly equivalent to rmdir(1).
|
193
|
+
# @param [String] path the path to the directory to remove
|
194
|
+
# @return [void]
|
195
|
+
# @note This method should usually only be called by FUSE, and not called
|
196
|
+
# directly
|
67
197
|
def rmdir(path)
|
68
198
|
FileUtils.rmdir(homepath(path))
|
69
199
|
end
|
70
200
|
|
201
|
+
# Test whether we can remove the directory given by _path_.
|
202
|
+
# @param [String] path the path to the directory to test
|
203
|
+
# @return [Boolean] true if the directory given can be removed.
|
204
|
+
# @note This method should usually only be called by FUSE, and not called
|
205
|
+
# directly
|
71
206
|
def can_rmdir?(path)
|
72
|
-
File.writable?(homepath(path))
|
207
|
+
File.writable?(homepath(path)) &&
|
208
|
+
Dir.new(path).to_a.size == 2
|
73
209
|
end
|
74
210
|
|
211
|
+
# Make a new directory at _path_. This is similar to mkdir(1).
|
212
|
+
# @param [String] path the path to the directory to make
|
213
|
+
# @return [void]
|
214
|
+
# @note This method should usually only be called by FUSE, and not called
|
215
|
+
# directly
|
75
216
|
def mkdir(path)
|
76
217
|
FileUtils.mkdir(homepath(path))
|
77
218
|
end
|
78
219
|
|
220
|
+
# Test if we can make a directory at the given path.
|
221
|
+
# @param [String] path the path to test
|
222
|
+
# @return [Boolean] true if we can make a directory at _path_.
|
223
|
+
# @note This method should usually only be called by FUSE, and not called
|
224
|
+
# directly
|
79
225
|
def can_mkdir?(path)
|
80
|
-
writable?(
|
226
|
+
writable?(homepath(path))
|
81
227
|
end
|
82
228
|
|
229
|
+
# Delete the file given by _path_. This is similar to rm(1).
|
230
|
+
# @param [String] path the path to the file to delete
|
231
|
+
# @return [void]
|
232
|
+
# @note This method should usually only be called by FUSE, and not called
|
233
|
+
# directly
|
83
234
|
def delete(path)
|
84
235
|
FileUtils.rm(homepath(path))
|
85
236
|
end
|
86
237
|
|
238
|
+
# Test if we can delete the file given by _path_.
|
239
|
+
# @param [String] path the path to the file to test
|
240
|
+
# @return [void]
|
241
|
+
# @note This method should usually only be called by FUSE, and not called
|
242
|
+
# directly
|
87
243
|
def can_delete?(path)
|
88
244
|
writable?(File.dirname(homepath(path)))
|
89
245
|
end
|
90
246
|
|
247
|
+
# Write the given data to the given path.
|
248
|
+
# @param [String] path the path to write to
|
249
|
+
# @param [String] str a binary string of data to write
|
250
|
+
# @return [void]
|
251
|
+
# @note This method should usually only be called by FUSE, and not called
|
252
|
+
# directly
|
91
253
|
def write_to(path, str)
|
92
254
|
File.open(homepath(path), "wb") {|file| file.write(str) }
|
93
255
|
end
|
94
256
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
257
|
+
# Test whether we can write out to the given path.
|
258
|
+
# @param [String] path the path to test
|
259
|
+
# @return [Boolean] whether we can write to the given path
|
260
|
+
# @note This method should usually only be called by FUSE, and not called
|
261
|
+
# directly
|
99
262
|
def can_write?(path)
|
100
|
-
|
101
|
-
if File.exist?(hpath)
|
102
|
-
writable?(hpath)
|
103
|
-
else
|
104
|
-
# FIXME: We don't support sticky bits
|
105
|
-
writable?(File.dirname(hpath))
|
106
|
-
end
|
263
|
+
writable?(homepath(path))
|
107
264
|
end
|
108
265
|
|
266
|
+
# Get the times (atime, mtime, ctime) for the file at the given path.
|
267
|
+
# @param [String] path the path to the file to get the times for
|
268
|
+
# @return [Array<Time>] an array of size three of the last-accessed time,
|
269
|
+
# the last-modified time, and the creation time for the file
|
270
|
+
# @note This method should usually only be called by FUSE, and not called
|
271
|
+
# directly
|
109
272
|
def times(path)
|
110
273
|
atime = File.atime(homepath(path))
|
111
274
|
mtime = File.mtime(homepath(path))
|
@@ -113,32 +276,73 @@ class HomeFS
|
|
113
276
|
[atime, mtime, ctime]
|
114
277
|
end
|
115
278
|
|
279
|
+
# Test whether the file given by _path_ is executable.
|
280
|
+
# @param [String] path the path to the file to test
|
281
|
+
# @return [Boolean] whether the file at the given path is executable.
|
282
|
+
# @note This method should usually only be called by FUSE, and not called
|
283
|
+
# directly
|
116
284
|
def executable?(path)
|
117
285
|
mode_mask(homepath(path), 0111)
|
118
286
|
end
|
119
287
|
|
288
|
+
# Read the contents of the file given by _path_.
|
289
|
+
# @param [String] path the path of the file to read
|
290
|
+
# @return [String] a binary string of the contents of the file
|
291
|
+
# @note This method should usually only be called by FUSE, and not called
|
292
|
+
# directly
|
120
293
|
def read_file(path)
|
121
294
|
File.open(homepath(path), "rb") do |file|
|
122
295
|
file.read
|
123
296
|
end
|
124
297
|
end
|
125
298
|
|
299
|
+
# List the contents of a directory. This includes '.' and '..' (the current
|
300
|
+
# directory and its parent).
|
301
|
+
# @param [String] path the path to the directory to list
|
302
|
+
# @return [Array<String>] a list of file names of files in the directory
|
303
|
+
# @note This method should usually only be called by FUSE, and not called
|
304
|
+
# directly
|
126
305
|
def contents(path)
|
127
306
|
Dir.new(homepath(path)).to_a
|
128
307
|
end
|
129
308
|
|
309
|
+
# Get the size of the file at _path_
|
310
|
+
# @param [String] path the path to the file or directory to get the size of
|
311
|
+
# @return [Integer] the size, in bytes, of the file
|
312
|
+
# @note This method should usually only be called by FUSE, and not called
|
313
|
+
# directly
|
130
314
|
def size(path)
|
131
315
|
File.size(homepath(path))
|
132
316
|
end
|
133
317
|
|
318
|
+
# Test whether _path_ is the name of a file (i.e., not a directory)
|
319
|
+
# @param [String] path the path to test
|
320
|
+
# @return [Boolean] whether _path_ represents a file
|
321
|
+
# @note This method should usually only be called by FUSE, and not called
|
322
|
+
# directly
|
134
323
|
def file?(path)
|
135
324
|
File.file?(homepath(path))
|
136
325
|
end
|
137
326
|
|
327
|
+
# Test whether _path_ is the name of a directory
|
328
|
+
# @param [String] path the path to test
|
329
|
+
# @return [Boolean] whether _path_ represents a directory
|
330
|
+
# @note This method should usually only be called by FUSE, and not called
|
331
|
+
# directly
|
138
332
|
def directory?(path)
|
139
333
|
File.directory?(homepath(path))
|
140
334
|
end
|
141
335
|
|
336
|
+
# Open a file handle to the file given by _path_, with the mode given by
|
337
|
+
# _mode_. Right now, the supported modes are "rw", "w", and "r", for
|
338
|
+
# read/write, write, and read. If the mode is write or read/write and no
|
339
|
+
# file exists at _path_, on will be created.
|
340
|
+
# @param [String] path the path to the file to open
|
341
|
+
# @param [String] mode the mode to open the file with
|
342
|
+
# @param rfusefs ignored
|
343
|
+
# @return [File] a file handle of the file given by _path_
|
344
|
+
# @note This method should usually only be called by FUSE, and not called
|
345
|
+
# directly
|
142
346
|
def raw_open(path, mode, rfusefs = nil)
|
143
347
|
mode = case mode
|
144
348
|
when "rw" then File::RDWR | File::CREAT | File::BINARY
|
@@ -148,25 +352,67 @@ class HomeFS
|
|
148
352
|
File.open(homepath(path), mode)
|
149
353
|
end
|
150
354
|
|
355
|
+
# Read _size_ bytes, starting at _offset_, from the file handle given by _raw_
|
356
|
+
# (which is returned by a call to _raw_open_). If raw is nil and a path is
|
357
|
+
# given, a file will be opened at _path_, and closed after the data is read.
|
358
|
+
# Otherwise, _path_ is ignored.
|
359
|
+
# @param [String] path the path to the file
|
360
|
+
# @param [Integer] offset the offset, in bytes, from the start of the file
|
361
|
+
# to start reading from.
|
362
|
+
# @param [Integer] size the amount of bytes to read
|
363
|
+
# @param [File] raw the file handle to read from
|
364
|
+
# @return [String] a binary string of the requested data
|
365
|
+
# @note This method should usually only be called by FUSE, and not called
|
366
|
+
# directly
|
151
367
|
def raw_read(path, offset, size, raw = nil)
|
152
|
-
raw
|
153
|
-
|
154
|
-
|
368
|
+
file = raw || raw_open(path, "r")
|
369
|
+
file.seek(offset, :SET)
|
370
|
+
file.read(size)
|
371
|
+
ensure
|
372
|
+
file.close if raw.nil?
|
155
373
|
end
|
156
374
|
|
375
|
+
# Sync writes to the underlying filesystem. If _raw_ is nil, no operation is
|
376
|
+
# performed.
|
377
|
+
# @param path ignored
|
378
|
+
# @param datasync ignored
|
379
|
+
# @param [File] raw the file handle to sync
|
380
|
+
# @return [void]
|
381
|
+
# @note This method should usually only be called by FUSE, and not called
|
382
|
+
# directly
|
157
383
|
def raw_sync(path, datasync, raw = nil)
|
158
384
|
return if raw.nil? # Should we sync anyway?
|
159
385
|
raw.fdatasync
|
160
386
|
end
|
161
387
|
|
388
|
+
# Close the file handle given by _raw_.
|
389
|
+
# @param path ignored
|
390
|
+
# @param [File] raw the file handle to close
|
391
|
+
# @return [void]
|
392
|
+
# @note This method should usually only be called by FUSE, and not called
|
393
|
+
# directly
|
162
394
|
def raw_close(path, raw = nil)
|
163
395
|
return if raw.nil? # ???
|
164
396
|
raw.close
|
165
397
|
end
|
166
398
|
|
399
|
+
# Write _sz_ bytes from _buf_ to the file handle _raw_, starting at _off_.
|
400
|
+
# If no file handle is given, one will be opened at the given path, and
|
401
|
+
# closed after the write is complete.
|
402
|
+
# @param [String] path the path of the file
|
403
|
+
# @param [Integer] off the offset, in bytes, to starting writing to the
|
404
|
+
# file at
|
405
|
+
# @param [Integer] sz the amount of bytes to read from buf
|
406
|
+
# @param [String] buf a binary string containing the data to be written
|
407
|
+
# @param [File] raw the file handle to write to
|
408
|
+
# @return [void]
|
409
|
+
# @note This method should usually only be called by FUSE, and not called
|
410
|
+
# directly
|
167
411
|
def raw_write(path, off, sz, buf, raw = nil)
|
168
|
-
raw
|
169
|
-
|
170
|
-
|
412
|
+
file = raw || File.open(path, "w")
|
413
|
+
file.seek(off, :SET)
|
414
|
+
file.write(buf[0...sz])
|
415
|
+
ensure
|
416
|
+
file.close if raw.nil?
|
171
417
|
end
|
172
418
|
end
|