1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use super::procfs::set_permissions_through_proc_self_fd;
use crate::fs::{errors, open, OpenOptions, Permissions};
use posish::fs::{fchmod, Mode};
use std::{
fs, io,
os::unix::fs::{OpenOptionsExt, PermissionsExt},
path::Path,
sync::atomic::{AtomicBool, Ordering::Relaxed},
};
pub(crate) fn set_permissions_impl(
start: &fs::File,
path: &Path,
perm: Permissions,
) -> io::Result<()> {
// Record whether we've seen an `EBADF` from an `fchmod` on an `O_PATH`
// file descriptor, meaning we're on a Linux that doesn't support it.
static FCHMOD_PATH_BADF: AtomicBool = AtomicBool::new(false);
let std_perm = perm.into_std(start)?;
if !FCHMOD_PATH_BADF.load(Relaxed) {
// First try to open the path with `O_PATH`; if that succeeds, it'll give
// us a few options. Use `read(true)` even though we don't need `read`
// permissions, because Rust's libstd requires an access mode, and Linux
// ignores `O_RDONLY` with `O_PATH`.
// TODO: Add tests with no-read no-permissions.
let opath_result = open(
start,
path,
OpenOptions::new().read(true).custom_flags(libc::O_PATH),
);
// If `O_PATH` worked, try to use `fchmod` on it.
if let Ok(file) = opath_result {
match set_file_permissions(&file, std_perm.clone()) {
Ok(()) => return Ok(()),
Err(err) => match err.raw_os_error() {
// If it fails with `EBADF`, `fchmod` didn't like `O_PATH`,
// so proceed to the fallback strategies below.
Some(libc::EBADF) => FCHMOD_PATH_BADF.store(true, Relaxed),
_ => return Err(err),
},
}
}
}
// Then try `fchmod` with a normal handle. Normal handles need some kind of
// access, so first try read.
match open(start, path, OpenOptions::new().read(true)) {
Ok(file) => return set_file_permissions(&file, std_perm),
Err(err) => match err.raw_os_error() {
Some(libc::EACCES) => (),
_ => return Err(err),
},
}
// Next try write.
match open(start, path, OpenOptions::new().write(true)) {
Ok(file) => return set_file_permissions(&file, std_perm),
Err(err) => match err.raw_os_error() {
Some(libc::EACCES) | Some(libc::EISDIR) => (),
_ => return Err(err),
},
}
// If neither of those worked, turn to `/proc`.
set_permissions_through_proc_self_fd(start, path, std_perm)
}
/// Like `file.set_permissions(perm)`, but without depending on libc's
/// `fchmod`, since some libc implementations such as musl emulate `O_PATH`
/// support by emulating it with /proc.
fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> {
let mode = Mode::from_bits(perm.mode()).ok_or_else(errors::invalid_flags)?;
fchmod(file, mode)
}