checkexec.rs
119 lines
| 4.0 KiB
| application/rls-services+xml
|
RustLexer
Arseniy Alekseyev
|
r50789 | use std::fs; | ||
use std::io; | ||||
use std::os::unix::fs::{MetadataExt, PermissionsExt}; | ||||
use std::path::Path; | ||||
const EXECFLAGS: u32 = 0o111; | ||||
fn is_executable(path: impl AsRef<Path>) -> Result<bool, io::Error> { | ||||
let metadata = fs::metadata(path)?; | ||||
let mode = metadata.mode(); | ||||
Ok(mode & EXECFLAGS != 0) | ||||
} | ||||
fn make_executable(path: impl AsRef<Path>) -> Result<(), io::Error> { | ||||
let mode = fs::metadata(path.as_ref())?.mode(); | ||||
fs::set_permissions( | ||||
path, | ||||
fs::Permissions::from_mode((mode & 0o777) | EXECFLAGS), | ||||
)?; | ||||
Ok(()) | ||||
} | ||||
fn copy_mode( | ||||
src: impl AsRef<Path>, | ||||
dst: impl AsRef<Path>, | ||||
) -> Result<(), io::Error> { | ||||
let mode = match fs::symlink_metadata(src) { | ||||
Ok(metadata) => metadata.mode(), | ||||
Err(e) if e.kind() == io::ErrorKind::NotFound => | ||||
// copymode in python has a more complicated handling of FileNotFound | ||||
// error, which we don't need because all it does is applying | ||||
// umask, which the OS already does when we mkdir. | ||||
{ | ||||
return Ok(()) | ||||
} | ||||
Err(e) => return Err(e), | ||||
}; | ||||
fs::set_permissions(dst, fs::Permissions::from_mode(mode))?; | ||||
Ok(()) | ||||
} | ||||
fn check_exec_impl(path: impl AsRef<Path>) -> Result<bool, io::Error> { | ||||
let basedir = path.as_ref().join(".hg"); | ||||
let cachedir = basedir.join("wcache"); | ||||
let storedir = basedir.join("store"); | ||||
if !cachedir.exists() { | ||||
Arseniy Alekseyev
|
r50790 | // we want to create the 'cache' directory, not the '.hg' one. | ||
// Automatically creating '.hg' directory could silently spawn | ||||
// invalid Mercurial repositories. That seems like a bad idea. | ||||
Arseniy Alekseyev
|
r50789 | fs::create_dir(&cachedir) | ||
.and_then(|()| { | ||||
if storedir.exists() { | ||||
copy_mode(&storedir, &cachedir) | ||||
} else { | ||||
copy_mode(&basedir, &cachedir) | ||||
} | ||||
}) | ||||
.ok(); | ||||
} | ||||
let leave_file: bool; | ||||
let checkdir: &Path; | ||||
let checkisexec = cachedir.join("checkisexec"); | ||||
let checknoexec = cachedir.join("checknoexec"); | ||||
if cachedir.is_dir() { | ||||
Arseniy Alekseyev
|
r50790 | // Check if both files already exist in cache and have correct | ||
// permissions. if so, we assume that permissions work. | ||||
// If not, we delete the files and try again. | ||||
Arseniy Alekseyev
|
r50789 | match is_executable(&checkisexec) { | ||
Err(e) if e.kind() == io::ErrorKind::NotFound => (), | ||||
Err(e) => return Err(e), | ||||
Ok(is_exec) => { | ||||
if is_exec { | ||||
let noexec_is_exec = match is_executable(&checknoexec) { | ||||
Err(e) if e.kind() == io::ErrorKind::NotFound => { | ||||
fs::write(&checknoexec, "")?; | ||||
is_executable(&checknoexec)? | ||||
} | ||||
Err(e) => return Err(e), | ||||
Ok(exec) => exec, | ||||
}; | ||||
if !noexec_is_exec { | ||||
// check-exec is exec and check-no-exec is not exec | ||||
return Ok(true); | ||||
} | ||||
fs::remove_file(&checknoexec)?; | ||||
} | ||||
fs::remove_file(&checkisexec)?; | ||||
} | ||||
} | ||||
checkdir = &cachedir; | ||||
leave_file = true; | ||||
} else { | ||||
Arseniy Alekseyev
|
r50790 | // no cache directory (probably because .hg doesn't exist): | ||
// check directly in `path` and don't leave the temp file behind | ||||
Arseniy Alekseyev
|
r50789 | checkdir = path.as_ref(); | ||
leave_file = false; | ||||
}; | ||||
let tmp_file = tempfile::NamedTempFile::new_in(checkdir)?; | ||||
if !is_executable(tmp_file.path())? { | ||||
make_executable(tmp_file.path())?; | ||||
if is_executable(tmp_file.path())? { | ||||
if leave_file { | ||||
tmp_file.persist(checkisexec).ok(); | ||||
} | ||||
return Ok(true); | ||||
} | ||||
} | ||||
Ok(false) | ||||
} | ||||
Arseniy Alekseyev
|
r50790 | /// This function is a rust rewrite of [checkexec] function from [posix.py] | ||
/// Returns true if the filesystem supports execute permissions. | ||||
Arseniy Alekseyev
|
r50789 | pub fn check_exec(path: impl AsRef<Path>) -> bool { | ||
check_exec_impl(path).unwrap_or(false) | ||||
} | ||||