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 check_filesystem_support(_sources: &[&Path], _output: &Path) -> bool15 pub fn check_filesystem_support(_sources: &[&Path], _output: &Path) -> bool { 16 return false; 17 } 18 check(_path: &Path, _bad: &mut bool)19 pub fn check(_path: &Path, _bad: &mut bool) {} 20 } 21 22 #[cfg(unix)] 23 mod os_impl { 24 use std::fs; 25 use std::os::unix::prelude::*; 26 use std::path::Path; 27 use std::process::{Command, Stdio}; 28 29 enum FilesystemSupport { 30 Supported, 31 Unsupported, 32 ReadOnlyFs, 33 } 34 35 use FilesystemSupport::*; 36 is_executable(path: &Path) -> std::io::Result<bool>37 fn is_executable(path: &Path) -> std::io::Result<bool> { 38 Ok(path.metadata()?.mode() & 0o111 != 0) 39 } 40 check_filesystem_support(sources: &[&Path], output: &Path) -> bool41 pub fn check_filesystem_support(sources: &[&Path], output: &Path) -> bool { 42 // We want to avoid false positives on filesystems that do not support the 43 // executable bit. This occurs on some versions of Window's linux subsystem, 44 // for example. 45 // 46 // We try to create the temporary file first in the src directory, which is 47 // the preferred location as it's most likely to be on the same filesystem, 48 // and then in the output (`build`) directory if that fails. Sometimes we 49 // see the source directory mounted as read-only which means we can't 50 // readily create a file there to test. 51 // 52 // See #36706 and #74753 for context. 53 54 fn check_dir(dir: &Path) -> FilesystemSupport { 55 let path = dir.join("tidy-test-file"); 56 match fs::File::create(&path) { 57 Ok(file) => { 58 let exec = is_executable(&path).unwrap_or(false); 59 std::mem::drop(file); 60 std::fs::remove_file(&path).expect("Deleted temp file"); 61 // If the file is executable, then we assume that this 62 // filesystem does not track executability, so skip this check. 63 return if exec { Unsupported } else { Supported }; 64 } 65 Err(e) => { 66 // If the directory is read-only or we otherwise don't have rights, 67 // just don't run this check. 68 // 69 // 30 is the "Read-only filesystem" code at least in one CI 70 // environment. 71 if e.raw_os_error() == Some(30) { 72 eprintln!("tidy: Skipping binary file check, read-only filesystem"); 73 return ReadOnlyFs; 74 } 75 76 panic!("unable to create temporary file `{:?}`: {:?}", path, e); 77 } 78 }; 79 } 80 81 for &source_dir in sources { 82 match check_dir(source_dir) { 83 Unsupported => return false, 84 ReadOnlyFs => { 85 return match check_dir(output) { 86 Supported => true, 87 _ => false, 88 }; 89 } 90 _ => {} 91 } 92 } 93 94 return true; 95 } 96 97 #[cfg(unix)] check(path: &Path, bad: &mut bool)98 pub fn check(path: &Path, bad: &mut bool) { 99 crate::walk_no_read( 100 path, 101 &mut |path| crate::filter_dirs(path) || path.ends_with("src/etc"), 102 &mut |entry| { 103 let file = entry.path(); 104 let filename = file.file_name().unwrap().to_string_lossy(); 105 let extensions = [".py", ".sh"]; 106 if extensions.iter().any(|e| filename.ends_with(e)) { 107 return; 108 } 109 110 if t!(is_executable(&file), file) { 111 let rel_path = file.strip_prefix(path).unwrap(); 112 let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/"); 113 let output = Command::new("git") 114 .arg("ls-files") 115 .arg(&git_friendly_path) 116 .current_dir(path) 117 .stderr(Stdio::null()) 118 .output() 119 .unwrap_or_else(|e| { 120 panic!("could not run git ls-files: {}", e); 121 }); 122 let path_bytes = rel_path.as_os_str().as_bytes(); 123 if output.status.success() && output.stdout.starts_with(path_bytes) { 124 tidy_error!(bad, "binary checked into source: {}", file.display()); 125 } 126 } 127 }, 128 ) 129 } 130 } 131