blob: 2dcbd8e9b38a2beaa7acb0ac8b7f514f651eb920 [file] [log] [blame] [edit]
//! 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;
use crate::diagnostics::TidyCtx;
pub fn check_filesystem_support(_sources: &[&Path], _output: &Path) -> bool {
return false;
}
pub fn check(_path: &Path, _tidy_ctx: TidyCtx) {}
}
#[cfg(unix)]
mod os_impl {
use std::fs;
use std::os::unix::prelude::*;
use std::path::Path;
use std::process::{Command, Stdio};
use crate::walk::{filter_dirs, walk_no_read};
enum FilesystemSupport {
Supported,
Unsupported,
ReadOnlyFs,
}
use FilesystemSupport::*;
use crate::diagnostics::TidyCtx;
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);
drop(file);
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.
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 matches!(check_dir(output), Supported),
_ => {}
}
}
true
}
// FIXME: check when rust-installer test sh files will be removed,
// and then remove them from exclude list
const RI_EXCLUSION_LIST: &[&str] = &[
"src/tools/rust-installer/test/image1/bin/program",
"src/tools/rust-installer/test/image1/bin/program2",
"src/tools/rust-installer/test/image1/bin/bad-bin",
"src/tools/rust-installer/test/image2/bin/oldprogram",
"src/tools/rust-installer/test/image3/bin/cargo",
];
fn filter_rust_installer_no_so_bins(path: &Path) -> bool {
RI_EXCLUSION_LIST.iter().any(|p| path.ends_with(p))
}
#[cfg(unix)]
pub fn check(path: &Path, tidy_ctx: TidyCtx) {
let mut check = tidy_ctx.start_check("bins");
use std::ffi::OsStr;
const ALLOWED: &[&str] = &["configure", "x"];
for p in RI_EXCLUSION_LIST {
if !path.join(Path::new(p)).exists() {
check.error(format!("rust-installer test bins missed: {p}"));
}
}
// FIXME: we don't need to look at all binaries, only files that have been modified in this branch
// (e.g. using `git ls-files`).
walk_no_read(
&[path],
|path, _is_dir| {
filter_dirs(path)
|| path.ends_with("src/etc")
|| filter_rust_installer_no_so_bins(path)
},
&mut |entry| {
let file = entry.path();
let extension = file.extension();
let scripts = ["py", "sh", "ps1", "woff2"];
if scripts.into_iter().any(|e| extension == Some(OsStr::new(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) {
check.error(format!("binary checked into source: {}", file.display()));
}
}
},
)
}
}