summaryrefslogtreecommitdiffstats
path: root/src/tools/tidy/src/bins.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/tidy/src/bins.rs')
-rw-r--r--src/tools/tidy/src/bins.rs151
1 files changed, 151 insertions, 0 deletions
diff --git a/src/tools/tidy/src/bins.rs b/src/tools/tidy/src/bins.rs
new file mode 100644
index 000000000..9615c4db6
--- /dev/null
+++ b/src/tools/tidy/src/bins.rs
@@ -0,0 +1,151 @@
+//! Tidy check to ensure that there are no binaries checked into the source tree
+//! by accident.
+//!
+//! In the past we've accidentally checked in test binaries and such which add a
+//! huge amount of bloat to the Git history, so it's good to just ensure we
+//! don't do that again.
+
+pub use os_impl::*;
+
+// All files are executable on Windows, so just check on Unix.
+#[cfg(windows)]
+mod os_impl {
+ use std::path::Path;
+
+ pub fn check_filesystem_support(_sources: &[&Path], _output: &Path) -> bool {
+ return false;
+ }
+
+ pub fn check(_path: &Path, _bad: &mut bool) {}
+}
+
+#[cfg(unix)]
+mod os_impl {
+ use std::fs;
+ use std::os::unix::prelude::*;
+ use std::path::Path;
+ use std::process::{Command, Stdio};
+
+ enum FilesystemSupport {
+ Supported,
+ Unsupported,
+ ReadOnlyFs,
+ }
+
+ use FilesystemSupport::*;
+
+ fn is_executable(path: &Path) -> std::io::Result<bool> {
+ Ok(path.metadata()?.mode() & 0o111 != 0)
+ }
+
+ pub fn check_filesystem_support(sources: &[&Path], output: &Path) -> bool {
+ // We want to avoid false positives on filesystems that do not support the
+ // executable bit. This occurs on some versions of Window's linux subsystem,
+ // for example.
+ //
+ // We try to create the temporary file first in the src directory, which is
+ // the preferred location as it's most likely to be on the same filesystem,
+ // and then in the output (`build`) directory if that fails. Sometimes we
+ // see the source directory mounted as read-only which means we can't
+ // readily create a file there to test.
+ //
+ // See #36706 and #74753 for context.
+
+ fn check_dir(dir: &Path) -> FilesystemSupport {
+ let path = dir.join("tidy-test-file");
+ match fs::File::create(&path) {
+ Ok(file) => {
+ let exec = is_executable(&path).unwrap_or(false);
+ std::mem::drop(file);
+ std::fs::remove_file(&path).expect("Deleted temp file");
+ // If the file is executable, then we assume that this
+ // filesystem does not track executability, so skip this check.
+ return if exec { Unsupported } else { Supported };
+ }
+ Err(e) => {
+ // If the directory is read-only or we otherwise don't have rights,
+ // just don't run this check.
+ //
+ // 30 is the "Read-only filesystem" code at least in one CI
+ // environment.
+ if e.raw_os_error() == Some(30) {
+ eprintln!("tidy: Skipping binary file check, read-only filesystem");
+ return ReadOnlyFs;
+ }
+
+ panic!("unable to create temporary file `{:?}`: {:?}", path, e);
+ }
+ };
+ }
+
+ for &source_dir in sources {
+ match check_dir(source_dir) {
+ Unsupported => return false,
+ ReadOnlyFs => {
+ return match check_dir(output) {
+ Supported => true,
+ _ => false,
+ };
+ }
+ _ => {}
+ }
+ }
+
+ return true;
+ }
+
+ #[cfg(unix)]
+ pub fn check(path: &Path, bad: &mut bool) {
+ const ALLOWED: &[&str] = &["configure"];
+
+ crate::walk_no_read(
+ path,
+ &mut |path| {
+ crate::filter_dirs(path)
+ || path.ends_with("src/etc")
+ // This is a list of directories that we almost certainly
+ // don't need to walk. A future PR will likely want to
+ // remove these in favor of crate::walk_no_read using git
+ // ls-files to discover the paths we should check, which
+ // would naturally ignore all of these directories. It's
+ // also likely faster than walking the directory tree
+ // directly (since git is just reading from a couple files
+ // to produce the results).
+ || path.ends_with("target")
+ || path.ends_with("build")
+ || path.ends_with(".git")
+ },
+ &mut |entry| {
+ let file = entry.path();
+ let filename = file.file_name().unwrap().to_string_lossy();
+ let extensions = [".py", ".sh"];
+ if extensions.iter().any(|e| filename.ends_with(e)) {
+ return;
+ }
+
+ if t!(is_executable(&file), file) {
+ let rel_path = file.strip_prefix(path).unwrap();
+ let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/");
+
+ if ALLOWED.contains(&git_friendly_path.as_str()) {
+ return;
+ }
+
+ let output = Command::new("git")
+ .arg("ls-files")
+ .arg(&git_friendly_path)
+ .current_dir(path)
+ .stderr(Stdio::null())
+ .output()
+ .unwrap_or_else(|e| {
+ panic!("could not run git ls-files: {e}");
+ });
+ let path_bytes = rel_path.as_os_str().as_bytes();
+ if output.status.success() && output.stdout.starts_with(path_bytes) {
+ tidy_error!(bad, "binary checked into source: {}", file.display());
+ }
+ }
+ },
+ )
+ }
+}