Skip to content

Commit 9776116

Browse files
committed
Move shell() helpers to a helper module
The new `auxiliary` module within `gix_path::env` is a sibling of the `git` module and is similarly an implementation detail only. So far the only "auxiliary" program this module finds is `sh`. The module is named `auxiliary` rather than `aux` because Windows has problems with files named like `aux` or `aux.rs`, due to `AUX` being a reserved device name.
1 parent 0510e50 commit 9776116

File tree

2 files changed

+82
-79
lines changed

2 files changed

+82
-79
lines changed

gix-path/src/env/auxiliary.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use std::ffi::OsString;
2+
use std::path::Path;
3+
4+
/// `usr`-like directory component names that MSYS2 may provide, other than for `/usr` itself.
5+
///
6+
/// These are the values of the "Prefix" column of the "Environments" and "Legacy Environments"
7+
/// tables in the [MSYS2 Environments](https://www.msys2.org/docs/environments/) documentation,
8+
/// with the leading `/` separator removed, except that this does not list `usr` itself.
9+
///
10+
/// On Windows, we prefer to use `sh` as provided by Git for Windows, when present. To find it, we
11+
/// run `git --exec-path` to get a path that is usually `<platform>/libexec/git-core` in the Git
12+
/// for Windows installation, where `<platform>` is something like `mingw64`. It is also acceptable
13+
/// to find `sh` in an environment not provided by Git for Windows, such as an independent MSYS2
14+
/// environment in which a `git` package has been installed. However, in an unusual installation,
15+
/// or if the user has set a custom value of `GIT_EXEC_PATH`, the output of `git --exec-path` may
16+
/// take a form other than `<platform>/libexec/git-core`, such that finding shell at a location
17+
/// like `../../../bin/sh.exe` relative to it should not be attempted. We lower the risk by
18+
/// checking that `<platform>` is a plausible value that is not likely to have any other meaning.
19+
///
20+
/// This involves two tradeoffs. First, it may be reasonable to find `sh.exe` in an environment
21+
/// that is not MSYS2 at all, for which in principle the prefix could be different. But listing
22+
/// more prefixes or matching a broad pattern of platform-like strings might be too broad. So only
23+
/// prefixes that have been used in MSYS2 are considered.
24+
///
25+
/// Second, we don't recognize `usr` itself here, even though is a plausible prefix. In MSYS2, it
26+
/// is the prefix for MSYS2 non-native programs, i.e. those that use `msys-2.0.dll`. But unlike the
27+
/// `<platform>` names we recognize, `usr` also has an effectively unbounded range of plausible
28+
/// meanings on non-Unix systems, which may occasionally relate to subdirectories whose contents
29+
/// are controlled by different user accounts.
30+
///
31+
/// If we start with a `libexec/git-core` directory that we already use and trust, and it is in a
32+
/// directory with a name like `mingw64`, we infer that this `mingw64` directory has the expected
33+
/// meaning and that its `usr` sibling, if present, is acceptable to treat as though it is a
34+
/// first-level directory inside an MSYS2-like tree. So we are willing to traverse down to
35+
/// `usr/sh.exe` and attempt to use it. But if the `libexec/git-core` we use and trust is inside a
36+
/// directory named `usr`, that `usr` directory may still not have the meaning we expect of `usr`.
37+
///
38+
/// The conditions for a privilege escalation attack or other serious malfunction seem unlikely. If
39+
/// research indicates the risk is low enough, `usr` may be added. But for now it is omitted.
40+
const MSYS_USR_VARIANTS: &[&str] = &["mingw64", "mingw32", "clangarm64", "clang64", "clang32", "ucrt64"];
41+
42+
/// Shell path fragments to concatenate to the root of a Git for Windows or MSYS2 installation.
43+
///
44+
/// These look like absolute Unix-style paths, but the leading `/` separators are present because
45+
/// they simplify forming paths like `C:/Program Files/Git` obtained by removing trailing
46+
/// components from the output of `git --exec-path`.
47+
const RAW_SH_EXE_PATH_SUFFIXES: &[&str] = &[
48+
"/bin/sh.exe", // Usually a shim, which currently we prefer, if available.
49+
"/usr/bin/sh.exe",
50+
];
51+
52+
///
53+
fn raw_join(path: &Path, raw_suffix: &str) -> OsString {
54+
let mut raw_path = OsString::from(path);
55+
raw_path.push(raw_suffix);
56+
raw_path
57+
}
58+
59+
///
60+
pub(super) fn find_sh_on_windows() -> Option<OsString> {
61+
super::core_dir()
62+
.filter(|core| core.is_absolute() && core.ends_with("libexec/git-core"))
63+
.and_then(|core| core.ancestors().nth(2))
64+
.filter(|prefix| {
65+
// Only use `libexec/git-core` from inside something `usr`-like, such as `mingw64`.
66+
MSYS_USR_VARIANTS.iter().any(|name| prefix.ends_with(name))
67+
})
68+
.and_then(|prefix| prefix.parent())
69+
.into_iter()
70+
.flat_map(|git_root| {
71+
// Enumerate locations where `sh.exe` usually is. To avoid breaking scripts that assume the
72+
// shell's own path contains no `\`, and so messages are more readable, append literally
73+
// with `/` separators. The path from `git --exec-path` already uses `/` separators (and no
74+
// trailing `/`) unless explicitly overridden to an unusual value via `GIT_EXEC_PATH`.
75+
RAW_SH_EXE_PATH_SUFFIXES
76+
.iter()
77+
.map(|raw_suffix| raw_join(git_root, raw_suffix))
78+
})
79+
.find(|raw_path| Path::new(raw_path).is_file())
80+
}

gix-path/src/env/mod.rs

Lines changed: 2 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use once_cell::sync::Lazy;
66

77
use crate::env::git::EXE_NAME;
88

9+
mod auxiliary;
910
mod git;
1011

1112
/// Return the location at which installation specific git configuration file can be found, or `None`
@@ -28,84 +29,6 @@ pub fn installation_config_prefix() -> Option<&'static Path> {
2829
installation_config().map(git::config_to_base_path)
2930
}
3031

31-
/// `usr`-like directory component names that MSYS2 may provide, other than for `/usr` itself.
32-
///
33-
/// These are the values of the "Prefix" column of the "Environments" and "Legacy Environments"
34-
/// tables in the [MSYS2 Environments](https://www.msys2.org/docs/environments/) documentation,
35-
/// with the leading `/` separator removed, except that this does not list `usr` itself.
36-
///
37-
/// On Windows, we prefer to use `sh` as provided by Git for Windows, when present. To find it, we
38-
/// run `git --exec-path` to get a path that is usually `<platform>/libexec/git-core` in the Git
39-
/// for Windows installation, where `<platform>` is something like `mingw64`. It is also acceptable
40-
/// to find `sh` in an environment not provided by Git for Windows, such as an independent MSYS2
41-
/// environment in which a `git` package has been installed. However, in an unusual installation,
42-
/// or if the user has set a custom value of `GIT_EXEC_PATH`, the output of `git --exec-path` may
43-
/// take a form other than `<platform>/libexec/git-core`, such that finding shell at a location
44-
/// like `../../../bin/sh.exe` relative to it should not be attempted. We lower the risk by
45-
/// checking that `<platform>` is a plausible value that is not likely to have any other meaning.
46-
///
47-
/// This involves two tradeoffs. First, it may be reasonable to find `sh.exe` in an environment
48-
/// that is not MSYS2 at all, for which in principle the prefix could be different. But listing
49-
/// more prefixes or matching a broad pattern of platform-like strings might be too broad. So only
50-
/// prefixes that have been used in MSYS2 are considered.
51-
///
52-
/// Second, we don't recognize `usr` itself here, even though is a plausible prefix. In MSYS2, it
53-
/// is the prefix for MSYS2 non-native programs, i.e. those that use `msys-2.0.dll`. But unlike the
54-
/// `<platform>` names we recognize, `usr` also has an effectively unbounded range of plausible
55-
/// meanings on non-Unix systems, which may occasionally relate to subdirectories whose contents
56-
/// are controlled by different user accounts.
57-
///
58-
/// If we start with a `libexec/git-core` directory that we already use and trust, and it is in a
59-
/// directory with a name like `mingw64`, we infer that this `mingw64` directory has the expected
60-
/// meaning and that its `usr` sibling, if present, is acceptable to treat as though it is a
61-
/// first-level directory inside an MSYS2-like tree. So we are willing to traverse down to
62-
/// `usr/sh.exe` and attempt to use it. But if the `libexec/git-core` we use and trust is inside a
63-
/// directory named `usr`, that `usr` directory may still not have the meaning we expect of `usr`.
64-
///
65-
/// The conditions for a privilege escalation attack or other serious malfunction seem unlikely. If
66-
/// research indicates the risk is low enough, `usr` may be added. But for now it is omitted.
67-
const MSYS_USR_VARIANTS: &[&str] = &["mingw64", "mingw32", "clangarm64", "clang64", "clang32", "ucrt64"];
68-
69-
/// Shell path fragments to concatenate to the root of a Git for Windows or MSYS2 installation.
70-
///
71-
/// These look like absolute Unix-style paths, but the leading `/` separators are present because
72-
/// they simplify forming paths like `C:/Program Files/Git` obtained by removing trailing
73-
/// components from the output of `git --exec-path`.
74-
const RAW_SH_EXE_PATH_SUFFIXES: &[&str] = &[
75-
"/bin/sh.exe", // Usually a shim, which currently we prefer, if available.
76-
"/usr/bin/sh.exe",
77-
];
78-
79-
///
80-
fn raw_join(path: &Path, raw_suffix: &str) -> OsString {
81-
let mut raw_path = OsString::from(path);
82-
raw_path.push(raw_suffix);
83-
raw_path
84-
}
85-
86-
///
87-
fn find_sh_on_windows() -> Option<OsString> {
88-
core_dir()
89-
.filter(|core| core.is_absolute() && core.ends_with("libexec/git-core"))
90-
.and_then(|core| core.ancestors().nth(2))
91-
.filter(|prefix| {
92-
// Only use `libexec/git-core` from inside something `usr`-like, such as `mingw64`.
93-
MSYS_USR_VARIANTS.iter().any(|name| prefix.ends_with(name))
94-
})
95-
.and_then(|prefix| prefix.parent())
96-
.into_iter()
97-
.flat_map(|git_root| {
98-
// Enumerate locations where `sh.exe` usually is. To avoid breaking scripts that assume the
99-
// shell's own path contains no `\`, and so messages are more readable, append literally
100-
// with `/` separators. The path from `git --exec-path` already uses `/` separators (and no
101-
// trailing `/`) unless explicitly overridden to an unusual value via `GIT_EXEC_PATH`.
102-
RAW_SH_EXE_PATH_SUFFIXES
103-
.iter()
104-
.map(|raw_suffix| raw_join(git_root, raw_suffix))
105-
})
106-
.find(|raw_path| Path::new(raw_path).is_file())
107-
}
108-
10932
/// Return the shell that Git would use, the shell to execute commands from.
11033
///
11134
/// On Windows, this is the full path to `sh.exe` bundled with Git for Windows if we can find it.
@@ -117,7 +40,7 @@ fn find_sh_on_windows() -> Option<OsString> {
11740
pub fn shell() -> &'static OsStr {
11841
static PATH: Lazy<OsString> = Lazy::new(|| {
11942
if cfg!(windows) {
120-
find_sh_on_windows().unwrap_or_else(|| "sh.exe".into())
43+
auxiliary::find_sh_on_windows().unwrap_or_else(|| "sh.exe".into())
12144
} else {
12245
"/bin/sh".into()
12346
}

0 commit comments

Comments
 (0)