|
5 | 5 | //! huge amount of bloat to the Git history, so it's good to just ensure we
|
6 | 6 | //! don't do that again.
|
7 | 7 |
|
8 |
| -use std::path::Path; |
| 8 | +pub use os_impl::*; |
9 | 9 |
|
10 | 10 | // All files are executable on Windows, so just check on Unix.
|
11 | 11 | #[cfg(windows)]
|
12 |
| -pub fn check(_path: &Path, _output: &Path, _bad: &mut bool) {} |
| 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 | +} |
13 | 21 |
|
14 | 22 | #[cfg(unix)]
|
15 |
| -pub fn check(path: &Path, output: &Path, bad: &mut bool) { |
| 23 | +mod os_impl { |
16 | 24 | use std::fs;
|
17 | 25 | use std::os::unix::prelude::*;
|
| 26 | + use std::path::Path; |
18 | 27 | use std::process::{Command, Stdio};
|
19 | 28 |
|
| 29 | + enum FilesystemSupport { |
| 30 | + Supported, |
| 31 | + Unsupported, |
| 32 | + ReadOnlyFs, |
| 33 | + } |
| 34 | + |
| 35 | + use FilesystemSupport::*; |
| 36 | + |
20 | 37 | fn is_executable(path: &Path) -> std::io::Result<bool> {
|
21 | 38 | Ok(path.metadata()?.mode() & 0o111 != 0)
|
22 | 39 | }
|
23 | 40 |
|
24 |
| - // We want to avoid false positives on filesystems that do not support the |
25 |
| - // executable bit. This occurs on some versions of Window's linux subsystem, |
26 |
| - // for example. |
27 |
| - // |
28 |
| - // We try to create the temporary file first in the src directory, which is |
29 |
| - // the preferred location as it's most likely to be on the same filesystem, |
30 |
| - // and then in the output (`build`) directory if that fails. Sometimes we |
31 |
| - // see the source directory mounted as read-only which means we can't |
32 |
| - // readily create a file there to test. |
33 |
| - // |
34 |
| - // See #36706 and #74753 for context. |
35 |
| - // |
36 |
| - // We also add the thread ID to avoid threads trampling on each others files. |
37 |
| - let file_name = format!("t{}.tidy-test-file", std::thread::current().id().as_u64()); |
38 |
| - let mut temp_path = path.join(&file_name); |
39 |
| - match fs::File::create(&temp_path).or_else(|_| { |
40 |
| - temp_path = output.join(&file_name); |
41 |
| - fs::File::create(&temp_path) |
42 |
| - }) { |
43 |
| - Ok(file) => { |
44 |
| - let exec = is_executable(&temp_path).unwrap_or(false); |
45 |
| - std::mem::drop(file); |
46 |
| - std::fs::remove_file(&temp_path).expect("Deleted temp file"); |
47 |
| - if exec { |
48 |
| - // If the file is executable, then we assume that this |
49 |
| - // filesystem does not track executability, so skip this check. |
50 |
| - return; |
51 |
| - } |
| 41 | + 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 | + }; |
52 | 79 | }
|
53 |
| - Err(e) => { |
54 |
| - // If the directory is read-only or we otherwise don't have rights, |
55 |
| - // just don't run this check. |
56 |
| - // |
57 |
| - // 30 is the "Read-only filesystem" code at least in one CI |
58 |
| - // environment. |
59 |
| - if e.raw_os_error() == Some(30) { |
60 |
| - eprintln!("tidy: Skipping binary file check, read-only filesystem"); |
61 |
| - return; |
62 |
| - } |
63 | 80 |
|
64 |
| - panic!("unable to create temporary file `{:?}`: {:?}", temp_path, e); |
| 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 | + } |
65 | 92 | }
|
| 93 | + |
| 94 | + return true; |
66 | 95 | }
|
67 | 96 |
|
68 |
| - super::walk_no_read( |
69 |
| - path, |
70 |
| - &mut |path| super::filter_dirs(path) || path.ends_with("src/etc"), |
71 |
| - &mut |entry| { |
72 |
| - let file = entry.path(); |
73 |
| - let filename = file.file_name().unwrap().to_string_lossy(); |
74 |
| - let extensions = [".py", ".sh"]; |
75 |
| - if extensions.iter().any(|e| filename.ends_with(e)) { |
76 |
| - return; |
77 |
| - } |
| 97 | + #[cfg(unix)] |
| 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 | + } |
78 | 109 |
|
79 |
| - if t!(is_executable(&file), file) { |
80 |
| - let rel_path = file.strip_prefix(path).unwrap(); |
81 |
| - let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/"); |
82 |
| - let output = Command::new("git") |
83 |
| - .arg("ls-files") |
84 |
| - .arg(&git_friendly_path) |
85 |
| - .current_dir(path) |
86 |
| - .stderr(Stdio::null()) |
87 |
| - .output() |
88 |
| - .unwrap_or_else(|e| { |
89 |
| - panic!("could not run git ls-files: {}", e); |
90 |
| - }); |
91 |
| - let path_bytes = rel_path.as_os_str().as_bytes(); |
92 |
| - if output.status.success() && output.stdout.starts_with(path_bytes) { |
93 |
| - tidy_error!(bad, "binary checked into source: {}", file.display()); |
| 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 | + } |
94 | 126 | }
|
95 |
| - } |
96 |
| - }, |
97 |
| - ) |
| 127 | + }, |
| 128 | + ) |
| 129 | + } |
98 | 130 | }
|
0 commit comments