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 failure::Fallible;
16 use nix::errno::Errno;
17 use nix::unistd;
18 use nix::sys::{self, signal};
19 use signal_hook;
20 use std::cmp;
21 use std::fs;
22 use std::io::{self, Read};
23 use std::os::unix::io as unix_io;
24 use std::path::{Path, PathBuf};
25 use std::process;
26 use std::sync::Arc;
27 use std::sync::atomic::{AtomicBool, Ordering};
28 use std::sync::mpsc;
29 use std::time;
30 use std::thread;
31 
32 /// Converts a `nix::Error` that we expect to carry an errno to an `io::Error`.
nix_to_io_error(err: nix::Error) -> io::Error33 fn nix_to_io_error(err: nix::Error) -> io::Error {
34     match err {
35         nix::Error::Sys(errno) => io::Error::from_raw_os_error(errno as i32),
36         e => panic!("Did not expect to get an error without an errno from a nix call: {:?}", e),
37     }
38 }
39 
40 /// A scope-owned file with support for multiple non-owner readers in different threads.
41 ///
42 /// A `ShareableFile` object owns the file passed to it at construction time and will close the
43 /// underlying file handle when the object is dropped.
44 ///
45 /// Concurrent views into this same file, obtained via the `reader` method, do not own the
46 /// file handle.  Such views must accept the fact that the handle can be closed at any time, a
47 /// condition that is simply exposed as if the handle reached EOF.  Concurrent views can safely be
48 /// moved across threads.
49 pub struct ShareableFile {
50     /// Underlying file descriptor shared across all views of this file.
51     fd: unix_io::RawFd,
52 
53     /// Write ends of the pipes used by `ShareableFileReader` to know if the file has been closed.
54     watchers: Vec<unix_io::RawFd>,
55 
56     /// Whether the file descriptor has already been closed or not.
57     ///
58     /// In principle, we don't need this field: concurrent readers of this file will get their
59     /// selects abruptly terminated with an error and we should be able to rely on `EBADF` to tell
60     /// that this happened because of us closing the file descriptor.  But it seems better to track
61     /// our own closed condition so that we can distinguish actual errors from expected errors.
62     closed: Arc<AtomicBool>,
63 }
64 
65 impl ShareableFile {
66     /// Constructs a new `ShareableFile` from an open file and takes ownership of it.
from(file: fs::File) -> ShareableFile67     pub fn from(file: fs::File) -> ShareableFile {
68         use std::os::unix::io::IntoRawFd;
69         ShareableFile {
70             fd: file.into_raw_fd(),
71             watchers: vec!(),
72             closed: Arc::from(AtomicBool::new(false)),
73         }
74     }
75 
76     /// Returns an unowned view of the file.
77     ///
78     /// Users of this file must accept that the file can be closed at any time by the owner.
reader(&mut self) -> io::Result<ShareableFileReader>79     pub fn reader(&mut self) -> io::Result<ShareableFileReader> {
80         let (notifier, watcher) = unistd::pipe().map_err(nix_to_io_error)?;
81         self.watchers.push(watcher);
82         Ok(ShareableFileReader {
83             fd: self.fd,
84             notifier: notifier,
85             closed: self.closed.clone(),
86         })
87     }
88 }
89 
90 impl Drop for ShareableFile {
drop(&mut self)91     fn drop(&mut self) {
92         debug!("Closing ShareableFile with fd {}", self.fd);
93 
94         // We are about to touch file handles used by other threads.  If those threads are blocked
95         // on a select call, the call may return an error on some systems due to the closed file
96         // descriptors.  If those threads are about to issue a read after a select, the read will
97         // fail with a bad file descriptor.  Prepare them about these potential failure conditions
98         // before we actually touch anything.
99         self.closed.store(true, Ordering::SeqCst);
100 
101         for watcher in &self.watchers {
102             if let Err(e) = unistd::write(*watcher, &[0]) {
103                 // This write to a pipe we control really should not have failed.  If it did there
104                 // is not much we can do other than log an error.  We may get stuck threads on exit
105                 // though...
106                 //
107                 // TODO(jmmv): We expect the write to fail if we had any ShareableFileReader that
108                 // has already been dropped, as that drop would have closed the read end of the
109                 // pipe.  Note that this means that, if we create/drop ShareableFileReaders non-stop
110                 // for this ShareableFile, we'll exhaust the open file descriptors because we are
111                 // leaking the write ends of these pipes.  This should be fixed, but it's not a big
112                 // deal because we don't do this in sandboxfs.
113                 if e.as_errno().expect("Must have been a system error") != Errno::EPIPE {
114                     warn!("Failed to tell ShareableFileReader with handle {} of close: {}",
115                         *watcher, e)
116                 }
117             }
118             if let Err(e) = unistd::close(*watcher) {
119                 // Closing should really not have failed, but if it did, it does not hurt and there
120                 // is nothing we can do anyway.
121                 warn!("Failed to close pipe write end with handle {}: {}", *watcher, e)
122             }
123         }
124 
125         if let Err(e) = unistd::close(self.fd) {
126             warn!("Failed to close fd {}: {}", self.fd, e);
127         }
128     }
129 }
130 
131 /// A non-owned view of a `ShareableFile`.
132 pub struct ShareableFileReader {
133     /// Underlying file descriptor shared across all views of this file.
134     fd: unix_io::RawFd,
135 
136     /// Read end of the pipe used by `ShareableFile` to tell us that the file has been closed.
137     notifier: unix_io::RawFd,
138 
139     /// Whether the file descriptor has already been closed or not.  See description in
140     /// `ShareableFile` for more details.
141     closed: Arc<AtomicBool>,
142 }
143 
144 impl Drop for ShareableFileReader {
drop(&mut self)145     fn drop(&mut self) {
146         if let Err(e) = unistd::close(self.notifier) {
147             warn!("Failed to close fd {}: {}", self.notifier, e);
148         }
149     }
150 }
151 
152 impl Read for ShareableFileReader {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>153     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
154         let mut read_set = sys::select::FdSet::new();
155         read_set.insert(self.fd);
156         read_set.insert(self.notifier);
157         let result = sys::select::select(None, Some(&mut read_set), None, None, None)
158             .and_then(|ready_count| {
159                 debug_assert!(ready_count <= 2);
160                 if read_set.contains(self.notifier) {
161                     // The file has been closed by the owner.  There is no point in attempting to
162                     // read from the fd even if there were data in it.
163                     Ok(0)
164                 } else {
165                     assert!(read_set.contains(self.fd));
166                     unistd::read(self.fd, buf)
167                 }
168             })
169             .map_err(nix_to_io_error);
170 
171         match result {
172             Ok(read_count) => Ok(read_count),
173             err => {
174                 if self.closed.load(Ordering::SeqCst) {
175                     Ok(0)  // Simulate EOF due to close in another thread.
176                 } else {
177                     err
178                 }
179             },
180         }
181     }
182 }
183 
184 /// List of termination signals that cause the mount point to be correctly unmounted.
185 static CAPTURED_SIGNALS: [signal::Signal; 4] = [
186     signal::Signal::SIGHUP,
187     signal::Signal::SIGTERM,
188     signal::Signal::SIGINT,
189     signal::Signal::SIGQUIT,
190 ];
191 
192 /// Two-phase installer for `SignalsHandler`, which is responsible for unmounting a file system.
193 ///
194 /// Installing the signals is tricky business because of a potential race: if the signal handler is
195 /// installed and a signal arrives *before* the mount point has been configured, the unmounting will
196 /// not succeed, which means we will enter the server loop and lose the signal.  Conversely, if we
197 /// did this backwards, we could receive a signal after the mount point has been configured but
198 /// before we install the signal handler, which means we'd terminate but leak the mount point.
199 ///
200 /// To solve this, we must block signals while the mount point is being set up.  We achieve this by
201 /// exposing an interface that forces the caller to take two steps before it can obtain the actual
202 /// `SignalsHandler` object: the caller must call `prepare()` before mounting the file system and
203 /// then call `install()` once the file system is ready to serve.
204 ///
205 /// Keeping this logic as a separate `SignalsInstaller` object, instead of trying to expose a "safe
206 /// mount function" helps ensure restoration of the original signal mask in all cases because this
207 /// type does so at drop time.
208 pub struct SignalsInstaller {
209     /// Signal mask to restore at drop time.
210     old_sigset: signal::SigSet,
211 }
212 
213 impl SignalsInstaller {
214     /// Blocks signals in preparation to mount the file system.
prepare() -> SignalsInstaller215     pub fn prepare() -> SignalsInstaller {
216         let mut old_sigset = signal::SigSet::empty();
217         let mut sigset = signal::SigSet::empty();
218         for signal in CAPTURED_SIGNALS.iter() {
219             sigset.add(*signal);
220         }
221         signal::pthread_sigmask(
222             signal::SigmaskHow::SIG_BLOCK, Some(&sigset), Some(&mut old_sigset))
223             .expect("pthread_sigmask is not expected to fail");
224         SignalsInstaller { old_sigset }
225     }
226 
227     /// Installs all signal handlers to unmount the given `mount_point`.
install(self, mount_point: PathBuf) -> Fallible<SignalsHandler>228     pub fn install(self, mount_point: PathBuf) -> Fallible<SignalsHandler> {
229         let (signal_sender, signal_receiver) = mpsc::channel();
230 
231         let mut signums = vec!();
232         for signal in CAPTURED_SIGNALS.iter() {
233             signums.push(*signal as i32);
234         }
235         let signals = signal_hook::iterator::Signals::new(&signums)?;
236 
237         std::thread::spawn(move || SignalsHandler::handler(&signals, mount_point, &signal_sender));
238 
239         Ok(SignalsHandler { signal_receiver })
240 
241         // Consumes self which causes the original signal mask to be restored and thus unblocks
242         // signals.
243     }
244 }
245 
246 impl Drop for SignalsInstaller {
drop(&mut self)247     fn drop(&mut self) {
248         signal::pthread_sigmask(signal::SigmaskHow::SIG_SETMASK, Some(&self.old_sigset), None)
249             .expect("pthread_sigmask is not expected to fail and we cannot correctly clean up");
250     }
251 }
252 
253 /// Unmounts a file system by shelling out to the correct unmount tool.
254 ///
255 /// Doing this in-process is very difficult because of differences across systems and the fact that
256 /// neither `nix` nor `libc` currently expose any of the unmounting functionality.
unmount(path: &Path) -> Fallible<()>257 fn unmount(path: &Path) -> Fallible<()> {
258     #[cfg(not(any(target_os = "linux")))]
259     fn run_unmount(path: &Path) -> io::Result<process::Output> {
260         process::Command::new("umount").arg(path).output()
261     }
262 
263     #[cfg(any(target_os = "linux"))]
264     fn run_unmount(path: &Path) -> io::Result<process::Output> {
265         process::Command::new("fusermount").arg("-u").arg(path).output()
266     }
267 
268     let output = run_unmount(path)?;
269     if output.status.success() {
270         Ok(())
271     } else {
272         Err(format_err!("stdout: {}, stderr: {}",
273             String::from_utf8_lossy(&output.stdout).trim(),
274             String::from_utf8_lossy(&output.stderr).trim()))
275     }
276 }
277 
278 /// Tries to unmount the given file system indefinitely.
279 ///
280 /// If unmounting fails, it is probably because the file system is busy.  We don't know but it
281 /// doesn't matter: we have entered a terminal status: we do this at exit time so we'll keep trying
282 /// to unclog things while telling the user what's going on.  They are the ones that have to fix
283 /// this situation.
retry_unmount<P: AsRef<Path>>(mount_point: P)284 fn retry_unmount<P: AsRef<Path>>(mount_point: P) {
285     let mut backoff = time::Duration::from_millis(10);
286     let goal = time::Duration::from_secs(1);
287     'retry: loop {
288         match unmount(mount_point.as_ref()) {
289             Ok(()) => break 'retry,
290             Err(e) => {
291                 if backoff >= goal {
292                     warn!("Unmounting file system failed with '{}'; will retry in {:?}",
293                         e, backoff);
294                 }
295                 thread::sleep(backoff);
296                 if backoff < goal {
297                     backoff = cmp::min(goal, backoff * 2);
298                 }
299             },
300         }
301     }
302 }
303 
304 /// Maintains state and allows interaction with the installed signal handler.
305 ///
306 /// The signal handler is responsible for unmounting the file system, which in turn causes the
307 /// FUSE serve loop to either never start or to finish execution if it was already running.
308 pub struct SignalsHandler {
309     /// Channel used to receive, on the main thread, the number of the signal that was captured.
310     // TODO(https://github.com/vorner/signal-hook/pull/8): Replace i32 with SigNo once merged.
311     signal_receiver: mpsc::Receiver<i32>,
312 }
313 
314 impl SignalsHandler {
315     /// Returns the signal that was caught, if any.
316     ///
317     /// This is *not* racy when used after the file system has been unmounted (i.e. once the FUSE
318     /// loop terminates).
caught(&self) -> Option<i32>319     pub fn caught(&self) -> Option<i32> {
320         match self.signal_receiver.try_recv() {
321             Ok(signo) => Some(signo),
322             Err(_) => None,
323         }
324     }
325 
326     /// The signal handler.
327     ///
328     /// This blocks until the receipt of the first signal and then ignores the rest.
329     ///
330     /// Upon receipt of a signal from `signals`, the handler first updates `signal_sender` with the
331     /// number of the received signal and then attempts to unmount `mount_point` indefinitely to
332     /// unblock the main FUSE loop.
handler(signals: &signal_hook::iterator::Signals, mount_point: PathBuf, signal_sender: &mpsc::Sender<i32>)333     fn handler(signals: &signal_hook::iterator::Signals, mount_point: PathBuf,
334         signal_sender: &mpsc::Sender<i32>) {
335         let signo = signals.forever().next().unwrap();
336         if let Err(e) = signal_sender.send(signo) {
337             warn!("Failed to propagate signal to main thread; will get stuck exiting: {}", e);
338         }
339         info!("Caught signal {}; unmounting {}", signo, mount_point.display());
340         retry_unmount(mount_point);
341 
342         // It'd be nice if we could just "drop(signals)" here and then send the same received signal
343         // to ourselves so that the program terminated with the correct exit status.  Unfortunately,
344         // "drop(signals)" unregisters our hooks from the signal handlers installed by signal-hook
345         // but it does not actually return the signal handlers to their original values.  This is a
346         // limitation of the signal-hook crate.  Instead, we need the "caught" hack above to let the
347         // main thread return an error code instead.
348     }
349 }
350 
351 #[cfg(test)]
352 mod tests {
353     use nix::sys;
354     use std::io::{Read, Write};
355     use std::thread;
356     use super::*;
357     use tempfile;
358 
359     #[test]
test_shareable_file_clones_share_descriptor_and_only_one_owns()360     fn test_shareable_file_clones_share_descriptor_and_only_one_owns() {
361         let dir = tempfile::tempdir().unwrap();
362         let path = dir.path().join("file");
363         fs::File::create(&path).unwrap().write_all(b"ABCDEFG").unwrap();
364 
365         fn read_one_byte(input: &mut impl Read) -> u8 {
366             let mut buffer = [0];
367             input.read_exact(&mut buffer).unwrap();
368             buffer[0]
369         }
370 
371         let mut file = ShareableFile::from(fs::File::open(&path).unwrap());
372         let mut reader1 = file.reader().unwrap();
373         let mut reader2 = file.reader().unwrap();
374         assert_eq!(b'A', read_one_byte(&mut reader1));
375         drop(reader1);  // Make sure dropping a non-owner copy doesn't close the file handle.
376         assert_eq!(b'B', read_one_byte(&mut reader2));
377         drop(file);  // Closes the file descriptor so the readers should now not be able to read.
378         let mut buffer = [0];
379         assert_eq!(0, reader2.read(&mut buffer).expect("Expected 0 byte count as EOF after close"));
380     }
381 
try_shareable_file_close_unblocks_reads_without_error()382     fn try_shareable_file_close_unblocks_reads_without_error() {
383         let dir = tempfile::tempdir().unwrap();
384         let path = dir.path().join("pipe");
385         unistd::mkfifo(&path, sys::stat::Mode::S_IRUSR | sys::stat::Mode::S_IWUSR).unwrap();
386 
387         // We need to open the FIFO for writes so that the open for reads doesn't block.  But we
388         // have to do this open in a separate thread because it will block too.
389         let writer_handle = {
390             let path = path.clone();
391             thread::spawn(move || fs::File::create(path))
392         };
393 
394         // This will block until the thread we spawned above opens the file for writing.
395         let mut file = ShareableFile::from(fs::File::open(&path).unwrap());
396 
397         let (reader_ready_tx, reader_ready_rx) = mpsc::channel();
398         let reader_handle = {
399             let mut file = file.reader().unwrap();
400             thread::spawn(move || {
401                 let mut buffer = [0];
402                 reader_ready_tx.send(()).unwrap();
403                 file.read(&mut buffer)
404             })
405         };
406 
407         // This is racy: there is no guarantee that the reader thread is now blocked on the read
408         // syscall although there is a high likelihood that it is (and our explicit yield makes this
409         // even more likely, especially on single-core systems).  If we experience the race, the
410         // test will silently pass even if there is a problem in the ShareableFile.
411         reader_ready_rx.recv().unwrap();
412         thread::sleep(time::Duration::from_millis(1));
413 
414         drop(file);  // This should unblock the reader thread and let the join complete.
415         reader_handle.join().unwrap().expect("Read didn't return success on EOF-like condition");
416 
417         // We have already verified that the reader can be asynchronously terminated without the
418         // write handle causing interference.  Retrieve the write end of the FIFO and close it.
419         let writer = writer_handle.join().unwrap().expect("Write didn't finish successfully");
420         drop(writer);
421     }
422 
423     #[test]
test_shareable_file_close_unblocks_reads_without_error()424     fn test_shareable_file_close_unblocks_reads_without_error() {
425         // This test exercises a threading condition that cannot be reproduced in a non-racy manner.
426         // If we are subject to the race condition, the test will falsely pass; otherwise it will
427         // correctly fail.  Running this same test a few times ensures that we have higher chances
428         // of catching a problem.
429         for _ in 0..10 {
430             try_shareable_file_close_unblocks_reads_without_error()
431         }
432     }
433 }
434