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