|
1 use std::fs; |
|
2 use std::io; |
|
3 use std::os::unix::fs::{MetadataExt, PermissionsExt}; |
|
4 use std::path::Path; |
|
5 |
|
6 // This is a rust rewrite of [checkexec] function from [posix.py] |
|
7 |
|
8 const EXECFLAGS: u32 = 0o111; |
|
9 |
|
10 fn is_executable(path: impl AsRef<Path>) -> Result<bool, io::Error> { |
|
11 let metadata = fs::metadata(path)?; |
|
12 let mode = metadata.mode(); |
|
13 Ok(mode & EXECFLAGS != 0) |
|
14 } |
|
15 |
|
16 fn make_executable(path: impl AsRef<Path>) -> Result<(), io::Error> { |
|
17 let mode = fs::metadata(path.as_ref())?.mode(); |
|
18 fs::set_permissions( |
|
19 path, |
|
20 fs::Permissions::from_mode((mode & 0o777) | EXECFLAGS), |
|
21 )?; |
|
22 Ok(()) |
|
23 } |
|
24 |
|
25 fn copy_mode( |
|
26 src: impl AsRef<Path>, |
|
27 dst: impl AsRef<Path>, |
|
28 ) -> Result<(), io::Error> { |
|
29 let mode = match fs::symlink_metadata(src) { |
|
30 Ok(metadata) => metadata.mode(), |
|
31 Err(e) if e.kind() == io::ErrorKind::NotFound => |
|
32 // copymode in python has a more complicated handling of FileNotFound |
|
33 // error, which we don't need because all it does is applying |
|
34 // umask, which the OS already does when we mkdir. |
|
35 { |
|
36 return Ok(()) |
|
37 } |
|
38 Err(e) => return Err(e), |
|
39 }; |
|
40 fs::set_permissions(dst, fs::Permissions::from_mode(mode))?; |
|
41 Ok(()) |
|
42 } |
|
43 |
|
44 fn check_exec_impl(path: impl AsRef<Path>) -> Result<bool, io::Error> { |
|
45 let basedir = path.as_ref().join(".hg"); |
|
46 let cachedir = basedir.join("wcache"); |
|
47 let storedir = basedir.join("store"); |
|
48 |
|
49 if !cachedir.exists() { |
|
50 fs::create_dir(&cachedir) |
|
51 .and_then(|()| { |
|
52 if storedir.exists() { |
|
53 copy_mode(&storedir, &cachedir) |
|
54 } else { |
|
55 copy_mode(&basedir, &cachedir) |
|
56 } |
|
57 }) |
|
58 .ok(); |
|
59 } |
|
60 |
|
61 let leave_file: bool; |
|
62 let checkdir: &Path; |
|
63 let checkisexec = cachedir.join("checkisexec"); |
|
64 let checknoexec = cachedir.join("checknoexec"); |
|
65 if cachedir.is_dir() { |
|
66 match is_executable(&checkisexec) { |
|
67 Err(e) if e.kind() == io::ErrorKind::NotFound => (), |
|
68 Err(e) => return Err(e), |
|
69 Ok(is_exec) => { |
|
70 if is_exec { |
|
71 let noexec_is_exec = match is_executable(&checknoexec) { |
|
72 Err(e) if e.kind() == io::ErrorKind::NotFound => { |
|
73 fs::write(&checknoexec, "")?; |
|
74 is_executable(&checknoexec)? |
|
75 } |
|
76 Err(e) => return Err(e), |
|
77 Ok(exec) => exec, |
|
78 }; |
|
79 if !noexec_is_exec { |
|
80 // check-exec is exec and check-no-exec is not exec |
|
81 return Ok(true); |
|
82 } |
|
83 fs::remove_file(&checknoexec)?; |
|
84 } |
|
85 fs::remove_file(&checkisexec)?; |
|
86 } |
|
87 } |
|
88 checkdir = &cachedir; |
|
89 leave_file = true; |
|
90 } else { |
|
91 checkdir = path.as_ref(); |
|
92 leave_file = false; |
|
93 }; |
|
94 |
|
95 let tmp_file = tempfile::NamedTempFile::new_in(checkdir)?; |
|
96 if !is_executable(tmp_file.path())? { |
|
97 make_executable(tmp_file.path())?; |
|
98 if is_executable(tmp_file.path())? { |
|
99 if leave_file { |
|
100 tmp_file.persist(checkisexec).ok(); |
|
101 } |
|
102 return Ok(true); |
|
103 } |
|
104 } |
|
105 |
|
106 Ok(false) |
|
107 } |
|
108 |
|
109 pub fn check_exec(path: impl AsRef<Path>) -> bool { |
|
110 check_exec_impl(path).unwrap_or(false) |
|
111 } |