]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/bins.rs
Rollup merge of #102936 - notriddle:notriddle/nav-sum, r=Dylan-DPC
[rust.git] / src / tools / tidy / src / bins.rs
1 //! Tidy check to ensure that there are no binaries checked into the source tree
2 //! by accident.
3 //!
4 //! In the past we've accidentally checked in test binaries and such which add a
5 //! huge amount of bloat to the Git history, so it's good to just ensure we
6 //! don't do that again.
7
8 pub use os_impl::*;
9
10 // All files are executable on Windows, so just check on Unix.
11 #[cfg(windows)]
12 mod os_impl {
13     use std::path::Path;
14
15     pub fn check_filesystem_support(_sources: &[&Path], _output: &Path) -> bool {
16         return false;
17     }
18
19     pub fn check(_path: &Path, _bad: &mut bool) {}
20 }
21
22 #[cfg(unix)]
23 mod os_impl {
24     use crate::walk::{filter_dirs, walk_no_read};
25     use std::fs;
26     use std::os::unix::prelude::*;
27     use std::path::Path;
28     use std::process::{Command, Stdio};
29
30     enum FilesystemSupport {
31         Supported,
32         Unsupported,
33         ReadOnlyFs,
34     }
35
36     use FilesystemSupport::*;
37
38     fn is_executable(path: &Path) -> std::io::Result<bool> {
39         Ok(path.metadata()?.mode() & 0o111 != 0)
40     }
41
42     pub fn check_filesystem_support(sources: &[&Path], output: &Path) -> bool {
43         // We want to avoid false positives on filesystems that do not support the
44         // executable bit. This occurs on some versions of Window's linux subsystem,
45         // for example.
46         //
47         // We try to create the temporary file first in the src directory, which is
48         // the preferred location as it's most likely to be on the same filesystem,
49         // and then in the output (`build`) directory if that fails. Sometimes we
50         // see the source directory mounted as read-only which means we can't
51         // readily create a file there to test.
52         //
53         // See #36706 and #74753 for context.
54
55         fn check_dir(dir: &Path) -> FilesystemSupport {
56             let path = dir.join("tidy-test-file");
57             match fs::File::create(&path) {
58                 Ok(file) => {
59                     let exec = is_executable(&path).unwrap_or(false);
60                     std::mem::drop(file);
61                     std::fs::remove_file(&path).expect("Deleted temp file");
62                     // If the file is executable, then we assume that this
63                     // filesystem does not track executability, so skip this check.
64                     return if exec { Unsupported } else { Supported };
65                 }
66                 Err(e) => {
67                     // If the directory is read-only or we otherwise don't have rights,
68                     // just don't run this check.
69                     //
70                     // 30 is the "Read-only filesystem" code at least in one CI
71                     //    environment.
72                     if e.raw_os_error() == Some(30) {
73                         eprintln!("tidy: Skipping binary file check, read-only filesystem");
74                         return ReadOnlyFs;
75                     }
76
77                     panic!("unable to create temporary file `{:?}`: {:?}", path, e);
78                 }
79             };
80         }
81
82         for &source_dir in sources {
83             match check_dir(source_dir) {
84                 Unsupported => return false,
85                 ReadOnlyFs => {
86                     return match check_dir(output) {
87                         Supported => true,
88                         _ => false,
89                     };
90                 }
91                 _ => {}
92             }
93         }
94
95         return true;
96     }
97
98     #[cfg(unix)]
99     pub fn check(path: &Path, bad: &mut bool) {
100         use std::ffi::OsStr;
101
102         const ALLOWED: &[&str] = &["configure", "x"];
103
104         walk_no_read(
105             path,
106             &mut |path| {
107                 filter_dirs(path)
108                     || path.ends_with("src/etc")
109                     // This is a list of directories that we almost certainly
110                     // don't need to walk. A future PR will likely want to
111                     // remove these in favor of crate::walk_no_read using git
112                     // ls-files to discover the paths we should check, which
113                     // would naturally ignore all of these directories. It's
114                     // also likely faster than walking the directory tree
115                     // directly (since git is just reading from a couple files
116                     // to produce the results).
117                     || path.ends_with("target")
118                     || path.ends_with("build")
119                     || path.ends_with(".git")
120             },
121             &mut |entry| {
122                 let file = entry.path();
123                 let extension = file.extension();
124                 let scripts = ["py", "sh", "ps1"];
125                 if scripts.into_iter().any(|e| extension == Some(OsStr::new(e))) {
126                     return;
127                 }
128
129                 if t!(is_executable(&file), file) {
130                     let rel_path = file.strip_prefix(path).unwrap();
131                     let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/");
132
133                     if ALLOWED.contains(&git_friendly_path.as_str()) {
134                         return;
135                     }
136
137                     let output = Command::new("git")
138                         .arg("ls-files")
139                         .arg(&git_friendly_path)
140                         .current_dir(path)
141                         .stderr(Stdio::null())
142                         .output()
143                         .unwrap_or_else(|e| {
144                             panic!("could not run git ls-files: {e}");
145                         });
146                     let path_bytes = rel_path.as_os_str().as_bytes();
147                     if output.status.success() && output.stdout.starts_with(path_bytes) {
148                         tidy_error!(bad, "binary checked into source: {}", file.display());
149                     }
150                 }
151             },
152         )
153     }
154 }