1 // Copyright 2018 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy
5 // of the License at:
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations
13 // under the License.
14
15 use fuse;
16 use nix::{errno, fcntl, sys};
17 use nix::sys::time::TimeValLike;
18 use nodes::{KernelError, NodeResult};
19 use std::fs;
20 use std::io;
21 use std::os::unix::fs::{FileTypeExt, MetadataExt, OpenOptionsExt, PermissionsExt};
22 use std::path::Path;
23 use std::time::{SystemTime, UNIX_EPOCH};
24 use time::Timespec;
25
26 /// Fixed point in time to use when we fail to interpret file system supplied timestamps.
27 const BAD_TIME: Timespec = Timespec { sec: 0, nsec: 0 };
28
29 /// Converts a system time as represented in a fs::Metadata object to a Timespec.
30 ///
31 /// `path` is the file from which the timestamp was originally extracted and `name` represents the
32 /// metadata field the timestamp corresponds to; both are used for debugging purposes only.
33 ///
34 /// If the given system time is missing, or if it is invalid, logs a warning and returns a fixed
35 /// time. It is reasonable for these details to be missing because the backing file systems do
36 /// not always implement all possible file timestamps.
system_time_to_timespec(path: &Path, name: &str, time: &io::Result<SystemTime>) -> Timespec37 fn system_time_to_timespec(path: &Path, name: &str, time: &io::Result<SystemTime>) -> Timespec {
38 match time {
39 Ok(time) => match time.duration_since(UNIX_EPOCH) {
40 Ok(duration) => Timespec::new(duration.as_secs() as i64,
41 duration.subsec_nanos() as i32),
42 Err(e) => {
43 warn!("File system returned {} {:?} for {:?} that's before the Unix epoch: {}",
44 name, time, path, e);
45 BAD_TIME
46 }
47 },
48 Err(e) => {
49 debug!("File system did not return a {} timestamp for {:?}: {}", name, path, e);
50 BAD_TIME
51 },
52 }
53 }
54
55 /// Converts a `time::Timespec` object into a `sys::time::TimeVal`.
56 // TODO(jmmv): Consider upstreaming this function or a constructor for TimeVal that takes the two
57 // components separately.
timespec_to_timeval(spec: Timespec) -> sys::time::TimeVal58 pub fn timespec_to_timeval(spec: Timespec) -> sys::time::TimeVal {
59 use sys::time::TimeVal;
60 TimeVal::seconds(spec.sec) + TimeVal::nanoseconds(spec.nsec.into())
61 }
62
63 /// Converts a `sys::time::TimeVal` object into a `time::Timespec`.
64 // TODO(jmmv): Consider upstreaming this function as a TimeVal method.
timeval_to_timespec(val: sys::time::TimeVal) -> Timespec65 pub fn timeval_to_timespec(val: sys::time::TimeVal) -> Timespec {
66 let usec = if val.tv_usec() > sys::time::suseconds_t::from(std::i32::MAX) {
67 warn!("Cannot represent too-long usec quantity {} in timespec; using 0", val.tv_usec());
68 0
69 } else {
70 val.tv_usec() as i32
71 };
72 Timespec::new((val.tv_sec() as sys::time::time_t).into(), usec)
73 }
74
75 /// Converts a `sys::time::TimeVal` object into a `sys::time::TimeSpec`.
timeval_to_nix_timespec(val: sys::time::TimeVal) -> sys::time::TimeSpec76 pub fn timeval_to_nix_timespec(val: sys::time::TimeVal) -> sys::time::TimeSpec {
77 let usec = if val.tv_usec() > sys::time::suseconds_t::from(std::i32::MAX) {
78 warn!("Cannot represent too-long usec quantity {} in timespec; using 0", val.tv_usec());
79 0
80 } else {
81 val.tv_usec() as i64
82 };
83 sys::time::TimeSpec::nanoseconds((val.tv_sec() as i64) * 1_000_000_000 + usec)
84 }
85
86 /// Converts a file type as returned by the file system to a FUSE file type.
87 ///
88 /// `path` is the file from which the file type was originally extracted and is only for debugging
89 /// purposes.
90 ///
91 /// If the given file type cannot be mapped to a FUSE file type (because we don't know about that
92 /// type or, most likely, because the file type is bogus), logs a warning and returns a regular
93 /// file type with the assumption that most operations should work on it.
filetype_fs_to_fuse(path: &Path, fs_type: fs::FileType) -> fuse::FileType94 pub fn filetype_fs_to_fuse(path: &Path, fs_type: fs::FileType) -> fuse::FileType {
95 if fs_type.is_block_device() {
96 fuse::FileType::BlockDevice
97 } else if fs_type.is_char_device() {
98 fuse::FileType::CharDevice
99 } else if fs_type.is_dir() {
100 fuse::FileType::Directory
101 } else if fs_type.is_fifo() {
102 fuse::FileType::NamedPipe
103 } else if fs_type.is_file() {
104 fuse::FileType::RegularFile
105 } else if fs_type.is_socket() {
106 fuse::FileType::Socket
107 } else if fs_type.is_symlink() {
108 fuse::FileType::Symlink
109 } else {
110 warn!("File system returned invalid file type {:?} for {:?}", fs_type, path);
111 fuse::FileType::RegularFile
112 }
113 }
114
115 /// Converts metadata attributes supplied by the file system to a FUSE file attributes tuple.
116 ///
117 /// `inode` is the value of the FUSE inode (not the value of the inode supplied within `attr`) to
118 /// fill into the returned file attributes. `path` is the file from which the attributes were
119 /// originally extracted and is only for debugging purposes. `nlink` is the number of links to
120 /// expose, which is a sandboxfs-internal property and does not match the on-disk value included
121 /// in `attr`.
122 ///
123 /// Any errors encountered along the conversion process are logged and the corresponding field is
124 /// replaced by a reasonable value that should work. In other words: all errors are swallowed.
attr_fs_to_fuse(path: &Path, inode: u64, nlink: u32, attr: &fs::Metadata) -> fuse::FileAttr125 pub fn attr_fs_to_fuse(path: &Path, inode: u64, nlink: u32, attr: &fs::Metadata) -> fuse::FileAttr {
126 let len = if attr.is_dir() {
127 2 // TODO(jmmv): Reevaluate what directory sizes should be.
128 } else {
129 attr.len()
130 };
131
132 // TODO(https://github.com/bazelbuild/sandboxfs/issues/43): Using the underlying ctimes is
133 // slightly wrong because the ctimes track changes to the inodes. In most cases, operations
134 // that flow via sandboxfs will affect the underlying ctime and propagate through here, which is
135 // fine, but other operations are purely in-memory. To properly handle those cases, we should
136 // have our own ctime handling.
137 let ctime = Timespec { sec: attr.ctime(), nsec: attr.ctime_nsec() as i32 };
138
139 let perm = match attr.permissions().mode() {
140 // TODO(https://github.com/rust-lang/rust/issues/51577): Drop :: prefix.
141 mode if mode > u32::from(::std::u16::MAX) => {
142 warn!("File system returned mode {} for {:?}, which is too large; set to 0400",
143 mode, path);
144 0o400
145 },
146 mode => (mode as u16) & !(sys::stat::SFlag::S_IFMT.bits() as u16),
147 };
148
149 let rdev = match attr.rdev() {
150 // TODO(https://github.com/rust-lang/rust/issues/51577): Drop :: prefix.
151 rdev if rdev > u64::from(::std::u32::MAX) => {
152 warn!("File system returned rdev {} for {:?}, which is too large; set to 0",
153 rdev, path);
154 0
155 },
156 rdev => rdev as u32,
157 };
158
159 fuse::FileAttr {
160 ino: inode,
161 kind: filetype_fs_to_fuse(path, attr.file_type()),
162 nlink: nlink,
163 size: len,
164 blocks: 0, // TODO(jmmv): Reevaluate what blocks should be.
165 atime: system_time_to_timespec(path, "atime", &attr.accessed()),
166 mtime: system_time_to_timespec(path, "mtime", &attr.modified()),
167 ctime: ctime,
168 crtime: system_time_to_timespec(path, "crtime", &attr.created()),
169 perm: perm,
170 uid: attr.uid(),
171 gid: attr.gid(),
172 rdev: rdev,
173 flags: 0,
174 }
175 }
176
177 /// Converts a set of `flags` bitmask to an `fs::OpenOptions`.
178 ///
179 /// `allow_writes` indicates whether the file to be opened supports writes or not. If the flags
180 /// don't match this condition, then this returns an error.
flags_to_openoptions(flags: u32, allow_writes: bool) -> NodeResult<fs::OpenOptions>181 pub fn flags_to_openoptions(flags: u32, allow_writes: bool) -> NodeResult<fs::OpenOptions> {
182 let flags = flags as i32;
183 let oflag = fcntl::OFlag::from_bits_truncate(flags);
184
185 let mut options = fs::OpenOptions::new();
186 options.read(true);
187 if oflag.contains(fcntl::OFlag::O_WRONLY) | oflag.contains(fcntl::OFlag::O_RDWR) {
188 if !allow_writes {
189 return Err(KernelError::from_errno(errno::Errno::EPERM));
190 }
191 if oflag.contains(fcntl::OFlag::O_WRONLY) {
192 options.read(false);
193 }
194 options.write(true);
195 }
196 options.custom_flags(flags);
197 Ok(options)
198 }
199
200 /// Asserts that two FUSE file attributes are equal.
201 //
202 // TODO(jmmv): Remove once rust-fuse 0.4 is released as it will derive Eq for FileAttr.
fileattrs_eq(attr1: &fuse::FileAttr, attr2: &fuse::FileAttr) -> bool203 pub fn fileattrs_eq(attr1: &fuse::FileAttr, attr2: &fuse::FileAttr) -> bool {
204 attr1.ino == attr2.ino
205 && attr1.kind == attr2.kind
206 && attr1.nlink == attr2.nlink
207 && attr1.size == attr2.size
208 && attr1.blocks == attr2.blocks
209 && attr1.atime == attr2.atime
210 && attr1.mtime == attr2.mtime
211 && attr1.ctime == attr2.ctime
212 && attr1.crtime == attr2.crtime
213 && attr1.perm == attr2.perm
214 && attr1.uid == attr2.uid
215 && attr1.gid == attr2.gid
216 && attr1.rdev == attr2.rdev
217 && attr1.flags == attr2.flags
218 }
219
220 #[cfg(test)]
221 mod tests {
222 use super::*;
223
224 use nix::{errno, unistd};
225 use nix::sys::time::TimeValLike;
226 use std::fs::File;
227 use std::io::{Read, Write};
228 use std::os::unix;
229 use std::time::Duration;
230 use tempfile::tempdir;
231 use testutils;
232
233 /// Creates a file at `path` with the given `content` and closes it.
create_file(path: &Path, content: &str)234 fn create_file(path: &Path, content: &str) {
235 let mut file = File::create(path).expect("Test file creation failed");
236 let written = file.write(content.as_bytes()).expect("Test file data write failed");
237 assert_eq!(content.len(), written, "Test file wasn't fully written");
238 }
239
240 #[test]
test_timespec_to_timeval()241 fn test_timespec_to_timeval() {
242 let spec = Timespec { sec: 123, nsec: 45000 };
243 let val = timespec_to_timeval(spec);
244 assert_eq!(123, val.tv_sec());
245 assert_eq!(45, val.tv_usec());
246 }
247
248 #[test]
test_timeval_to_timespec()249 fn test_timeval_to_timespec() {
250 let val = sys::time::TimeVal::seconds(654) + sys::time::TimeVal::nanoseconds(123_456);
251 let spec = timeval_to_timespec(val);
252 assert_eq!(654, spec.sec);
253 assert_eq!(123, spec.nsec);
254 }
255
256 #[test]
test_timeval_to_nix_timespec()257 fn test_timeval_to_nix_timespec() {
258 let val = sys::time::TimeVal::seconds(654) + sys::time::TimeVal::nanoseconds(123_456);
259 let spec = timeval_to_nix_timespec(val);
260 assert_eq!(654, spec.tv_sec());
261 assert_eq!(123, spec.tv_nsec());
262 }
263
264 #[test]
test_system_time_to_timespec_ok()265 fn test_system_time_to_timespec_ok() {
266 let sys_time = SystemTime::UNIX_EPOCH + Duration::new(12345, 6789);
267 let timespec = system_time_to_timespec(
268 &Path::new("irrelevant"), "irrelevant", &Ok(sys_time));
269 assert_eq!(Timespec { sec: 12345, nsec: 6789 }, timespec);
270 }
271
272 #[test]
test_system_time_to_timespec_bad()273 fn test_system_time_to_timespec_bad() {
274 let sys_time = SystemTime::UNIX_EPOCH - Duration::new(1, 0);
275 let timespec = system_time_to_timespec(
276 &Path::new("irrelevant"), "irrelevant", &Ok(sys_time));
277 assert_eq!(BAD_TIME, timespec);
278 }
279
280 #[test]
test_system_time_to_timespec_missing()281 fn test_system_time_to_timespec_missing() {
282 let timespec = system_time_to_timespec(
283 &Path::new("irrelevant"), "irrelevant",
284 &Err(io::Error::from_raw_os_error(errno::Errno::ENOENT as i32)));
285 assert_eq!(BAD_TIME, timespec);
286 }
287
288 #[test]
test_filetype_fs_to_fuse()289 fn test_filetype_fs_to_fuse() {
290 let files = testutils::AllFileTypes::new();
291 for (exp_type, path) in files.entries {
292 let fs_type = fs::symlink_metadata(&path).unwrap().file_type();
293 assert_eq!(exp_type, filetype_fs_to_fuse(&path, fs_type));
294 }
295 }
296
297 #[test]
test_attr_fs_to_fuse_directory()298 fn test_attr_fs_to_fuse_directory() {
299 let dir = tempdir().unwrap();
300 let path = dir.path().join("root");
301 fs::create_dir(&path).unwrap();
302 fs::create_dir(path.join("subdir1")).unwrap();
303 fs::create_dir(path.join("subdir2")).unwrap();
304
305 fs::set_permissions(&path, fs::Permissions::from_mode(0o750)).unwrap();
306 sys::stat::utimes(&path, &sys::time::TimeVal::seconds(12345),
307 &sys::time::TimeVal::seconds(678)).unwrap();
308
309 let exp_attr = fuse::FileAttr {
310 ino: 1234, // Ensure underlying inode is not propagated.
311 kind: fuse::FileType::Directory,
312 nlink: 56, // TODO(jmmv): Should this account for subdirs?
313 size: 2,
314 blocks: 0,
315 atime: Timespec { sec: 12345, nsec: 0 },
316 mtime: Timespec { sec: 678, nsec: 0 },
317 ctime: BAD_TIME,
318 crtime: BAD_TIME,
319 perm: 0o750,
320 uid: unistd::getuid().as_raw(),
321 gid: unistd::getgid().as_raw(),
322 rdev: 0,
323 flags: 0,
324 };
325
326 let mut attr = attr_fs_to_fuse(&path, 1234, 56, &fs::symlink_metadata(&path).unwrap());
327 // We cannot really make any useful assertions on ctime and crtime as these cannot be
328 // modified and may not be queryable, so stub them out.
329 attr.ctime = BAD_TIME;
330 attr.crtime = BAD_TIME;
331 // Ignore rdev too
332 attr.rdev = 0;
333 // XXX: gid is always 0 when the tests run on tmpfs(5)
334 attr.gid = exp_attr.gid;
335 assert!(fileattrs_eq(&exp_attr, &attr));
336 }
337
338 #[test]
test_attr_fs_to_fuse_regular()339 fn test_attr_fs_to_fuse_regular() {
340 let dir = tempdir().unwrap();
341 let path = dir.path().join("file");
342
343 let content = "Some text\n";
344 create_file(&path, content);
345
346 fs::set_permissions(&path, fs::Permissions::from_mode(0o640)).unwrap();
347 sys::stat::utimes(&path, &sys::time::TimeVal::seconds(54321),
348 &sys::time::TimeVal::seconds(876)).unwrap();
349
350 let exp_attr = fuse::FileAttr {
351 ino: 42, // Ensure underlying inode is not propagated.
352 kind: fuse::FileType::RegularFile,
353 nlink: 50,
354 size: content.len() as u64,
355 blocks: 0,
356 atime: Timespec { sec: 54321, nsec: 0 },
357 mtime: Timespec { sec: 876, nsec: 0 },
358 ctime: BAD_TIME,
359 crtime: BAD_TIME,
360 perm: 0o640,
361 uid: unistd::getuid().as_raw(),
362 gid: unistd::getgid().as_raw(),
363 rdev: 0,
364 flags: 0,
365 };
366
367 let mut attr = attr_fs_to_fuse(&path, 42, 50, &fs::symlink_metadata(&path).unwrap());
368 // We cannot really make any useful assertions on ctime and crtime as these cannot be
369 // modified and may not be queryable, so stub them out.
370 attr.ctime = BAD_TIME;
371 attr.crtime = BAD_TIME;
372 // Ignore rdev too
373 attr.rdev = 0;
374 // XXX: gid is always 0 when the tests run on tmpfs(5)
375 attr.gid = exp_attr.gid;
376 assert!(fileattrs_eq(&exp_attr, &attr));
377 }
378
379 #[test]
test_flags_to_openoptions_rdonly()380 fn test_flags_to_openoptions_rdonly() {
381 let dir = tempdir().unwrap();
382 let path = dir.path().join("file");
383 create_file(&path, "original content");
384
385 let flags = fcntl::OFlag::O_RDONLY.bits() as u32;
386 let openoptions = flags_to_openoptions(flags, false).unwrap();
387 let mut file = openoptions.open(&path).unwrap();
388
389 write!(file, "foo").expect_err("Write to read-only file succeeded");
390
391 let mut buf = String::new();
392 file.read_to_string(&mut buf).expect("Read from read-only file failed");
393 assert_eq!("original content", buf);
394 }
395
396 #[test]
test_flags_to_openoptions_wronly()397 fn test_flags_to_openoptions_wronly() {
398 let dir = tempdir().unwrap();
399 let path = dir.path().join("file");
400 create_file(&path, "");
401
402 let flags = fcntl::OFlag::O_WRONLY.bits() as u32;
403 flags_to_openoptions(flags, false).expect_err("Writability permission not respected");
404 let openoptions = flags_to_openoptions(flags, true).unwrap();
405 let mut file = openoptions.open(&path).unwrap();
406
407 let mut buf = String::new();
408 file.read_to_string(&mut buf).expect_err("Read from write-only file succeeded");
409
410 write!(file, "foo").expect("Write to write-only file failed");
411 }
412
413 #[test]
test_flags_to_openoptions_rdwr()414 fn test_flags_to_openoptions_rdwr() {
415 let dir = tempdir().unwrap();
416 let path = dir.path().join("file");
417 create_file(&path, "some content");
418
419 let flags = fcntl::OFlag::O_RDWR.bits() as u32;
420 flags_to_openoptions(flags, false).expect_err("Writability permission not respected");
421 let openoptions = flags_to_openoptions(flags, true).unwrap();
422 let mut file = openoptions.open(&path).unwrap();
423
424 let mut buf = String::new();
425 file.read_to_string(&mut buf).expect("Read from read/write file failed");
426
427 write!(file, "foo").expect("Write to read/write file failed");
428 }
429
430 #[test]
test_flags_to_openoptions_custom()431 fn test_flags_to_openoptions_custom() {
432 let dir = tempdir().unwrap();
433 create_file(&dir.path().join("file"), "");
434 let path = dir.path().join("link");
435 unix::fs::symlink("file", &path).unwrap();
436
437 {
438 let flags = fcntl::OFlag::O_RDONLY.bits() as u32;
439 let openoptions = flags_to_openoptions(flags, true).unwrap();
440 openoptions.open(&path).expect("Failed to open symlink target; test setup bogus");
441 }
442
443 let flags = (fcntl::OFlag::O_RDONLY | fcntl::OFlag::O_NOFOLLOW).bits() as u32;
444 let openoptions = flags_to_openoptions(flags, true).unwrap();
445 openoptions.open(&path).expect_err("Open of symlink succeeded");
446 }
447 }
448