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