Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add safe-directories. #3005

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ url = "2.1"
wait-timeout = "0.2"
xz2 = "0.1.3"
zstd = "0.11"
shell-escape = "0.1.5"

[dependencies.retry]
default-features = false
Expand All @@ -82,6 +83,8 @@ winreg = "0.8"

[target."cfg(windows)".dependencies.winapi]
features = [
"accctrl",
"aclapi",
"combaseapi",
"errhandlingapi",
"fileapi",
Expand All @@ -92,6 +95,7 @@ features = [
"minwindef",
"processthreadsapi",
"psapi",
"sddl",
"shlobj",
"shtypes",
"synchapi",
Expand Down
3 changes: 2 additions & 1 deletion src/cli/proxy_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ fn direct_proxy(
toolchain: Option<&str>,
args: &[OsString],
) -> Result<ExitCode> {
let cmd = match toolchain {
let mut cmd = match toolchain {
None => cfg.create_command_for_dir(&utils::current_dir()?, arg0)?,
Some(tc) => cfg.create_command_for_toolchain(tc, false, arg0)?,
};
cfg.rustup_safe_directories_env(&mut cmd)?;
run_command_for_dir(cmd, arg0, args)
}
86 changes: 86 additions & 0 deletions src/cli/rustup_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ pub fn main() -> Result<utils::ExitCode> {
("default-host", Some(m)) => set_default_host_triple(cfg, m)?,
("profile", Some(m)) => set_profile(cfg, m)?,
("auto-self-update", Some(m)) => set_auto_self_update(cfg, m)?,
("safe-directories", Some(c)) => match c.subcommand() {
("add", Some(m)) => safe_directories_add(cfg, m.value_of("path").unwrap())?,
("remove", Some(m)) => safe_directories_remove(cfg, m.value_of("path").unwrap())?,
("list", Some(_)) => handle_epipe(safe_directories_list(cfg))?,
("clear", Some(_)) => safe_directories_clear(cfg)?,
(_, _) => unreachable!(),
},
(_, _) => unreachable!(),
},
("completions", Some(c)) => {
Expand Down Expand Up @@ -721,6 +728,30 @@ pub(crate) fn cli() -> App<'static, 'static> {
.possible_values(SelfUpdateMode::modes())
.default_value(SelfUpdateMode::default_mode()),
),
)
.subcommand(
SubCommand::with_name("safe-directories")
.about("Configure directories that allow toolchain override files")
.setting(AppSettings::VersionlessSubcommands)
.setting(AppSettings::DeriveDisplayOrder)
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("add")
.about("Adds a path to the safe-directories list")
.arg(Arg::with_name("path").required(true)),
)
.subcommand(
SubCommand::with_name("remove")
.about("Removes the given path from the safe directories list")
.arg(Arg::with_name("path").required(true)),
)
.subcommand(
SubCommand::with_name("list")
.about("Lists the currently configured safe directories"),
)
.subcommand(
SubCommand::with_name("clear").about("Removes all safe directories"),
),
),
);

Expand Down Expand Up @@ -1718,3 +1749,58 @@ fn output_completion_script(shell: Shell, command: CompletionCommand) -> Result<

Ok(utils::ExitCode(0))
}

pub(crate) fn safe_directories_add(cfg: &Cfg, path: &str) -> Result<utils::ExitCode> {
let p_buf = PathBuf::from(path);
if path != "*" && !p_buf.exists() {
warn!("path `{}` does not exist", path);
}
cfg.settings_file.with_mut(|s| {
if !s.safe_directories.iter().any(|p| Path::new(p) == p_buf) {
s.safe_directories.push(path.to_string());
}
Ok(())
})?;
info!("added `{}` to safe directories", path);
Ok(utils::ExitCode(0))
}

pub(crate) fn safe_directories_remove(cfg: &Cfg, path: &str) -> Result<utils::ExitCode> {
let p_buf = PathBuf::from(path);
let found = cfg.settings_file.with_mut(|s| {
Ok(s.safe_directories
.iter()
.position(|p| Path::new(p) == p_buf)
.map(|idx| s.safe_directories.remove(idx))
.is_some())
})?;
if found {
info!("removed `{}` from the safe directories list", path);
} else {
bail!("path `{}` was not found in the safe directory list", path);
}
Ok(utils::ExitCode(0))
}

pub(crate) fn safe_directories_list(cfg: &Cfg) -> Result<utils::ExitCode> {
cfg.settings_file.with(|s| {
if s.safe_directories.is_empty() {
info!("no safe directories configured");
} else {
for path in &s.safe_directories {
writeln!(process().stdout(), "{}", path)?;
}
}
Ok(())
})?;
Ok(utils::ExitCode(0))
}

pub(crate) fn safe_directories_clear(cfg: &Cfg) -> Result<utils::ExitCode> {
cfg.settings_file.with_mut(|s| {
s.safe_directories.clear();
Ok(())
})?;
info!("safe directories cleared");
Ok(utils::ExitCode(0))
}
41 changes: 41 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::env;
use std::fmt::{self, Display};
use std::io;
use std::path::{Path, PathBuf};
Expand All @@ -23,6 +24,7 @@ use crate::notifications::*;
use crate::process;
use crate::settings::{Settings, SettingsFile, DEFAULT_METADATA_VERSION};
use crate::toolchain::{DistributableToolchain, Toolchain, UpdateStatus};
use crate::utils::ownership;
use crate::utils::utils;

#[derive(Debug, ThisError)]
Expand Down Expand Up @@ -636,6 +638,7 @@ impl Cfg {
) -> Result<Option<(OverrideFile, OverrideReason)>> {
let notify = self.notify_handler.as_ref();
let mut dir = Some(dir);
let safe_directories = self.safe_directories(settings);

while let Some(d) = dir {
// First check the override database
Expand Down Expand Up @@ -673,6 +676,12 @@ impl Cfg {
};

if let Ok(contents) = contents {
ownership::validate_ownership(&toolchain_file, &safe_directories).map_err(
|err| match err.downcast::<ownership::OwnershipError>() {
Ok(e) => RustupError::Ownership(e).into(),
Err(e) => e,
},
)?;
let override_file = Cfg::parse_override_file(contents, parse_mode)?;
if let Some(toolchain_name) = &override_file.toolchain.channel {
let all_toolchains = self.list_toolchains()?;
Expand Down Expand Up @@ -1008,6 +1017,38 @@ impl Cfg {
Ok(normalized_name.to_owned())
}
}

/// This adds the `RUSTUP_SAFE_DIRECTORIES` environment variable to the
/// given command so that the directory listing can be relayed to Cargo so
/// that it can share the same configuration.
pub(crate) fn rustup_safe_directories_env(&self, cmd: &mut Command) -> Result<()> {
self.settings_file.with(|s| {
let dirs = self.safe_directories(s);
if !dirs.is_empty() {
let dirs = env::join_paths(dirs)?;
cmd.env("RUSTUP_SAFE_DIRECTORIES", dirs);
}
Ok(())
})
}

/// Returns the safe_directories setting, including from the environment.
fn safe_directories(&self, settings: &Settings) -> Vec<PathBuf> {
let mut safe_directories: Vec<PathBuf> = settings
.safe_directories
.iter()
.map(PathBuf::from)
.collect();
if let Some(dirs) = process().var_os("RUSTUP_SAFE_DIRECTORIES") {
let from_env = env::split_paths(&dirs)
// Filter so that the environment doesn't grow if there are
// recursive invocations of a proxy.
.filter(|env_path| !safe_directories.iter().any(|d| d == env_path))
.collect::<Vec<_>>();
safe_directories.extend(from_env);
}
safe_directories
}
}

/// Specifies how a `rust-toolchain`/`rust-toolchain.toml` configuration file should be parsed.
Expand Down
23 changes: 23 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(clippy::large_enum_variant)]

use std::borrow::Cow;
use std::ffi::OsString;
use std::fmt::Debug;
use std::io::{self, Write};
Expand All @@ -10,6 +11,7 @@ use url::Url;

use crate::currentprocess::process;
use crate::dist::manifest::{Component, Manifest};
use crate::utils::ownership::OwnershipError;

const TOOLSTATE_MSG: &str =
"If you require these components, please install and use the latest successful build version,\n\
Expand Down Expand Up @@ -115,6 +117,8 @@ pub enum RustupError {
UnsupportedVersion(String),
#[error("could not write {name} file: '{}'", .path.display())]
WritingFile { name: &'static str, path: PathBuf },
#[error("{}", ownership_error_msg(.0))]
Ownership(OwnershipError),
}

fn remove_component_msg(cs: &Component, manifest: &Manifest, toolchain: &str) -> String {
Expand Down Expand Up @@ -197,3 +201,22 @@ fn component_unavailable_msg(cs: &[Component], manifest: &Manifest, toolchain: &

String::from_utf8(buf).unwrap()
}

fn ownership_error_msg(err: &OwnershipError) -> String {
// rust-toolchain or rust-toolchain.toml
let what = err.path.file_name().unwrap().to_string_lossy();
let to_add = err.path.parent().unwrap();
let escaped = shell_escape::escape(Cow::Borrowed(to_add.to_str().expect("utf-8 path")));
format!(
"`{}` is owned by a different user\n \
For safety reasons, Rustup does not allow opening `{what}` files owned by\n \
a different user, unless explicitly approved.\n\
\n \
To approve this directory, run\n\
\n \
rustup set safe-directories add {escaped}\n\
\n \
See https://rust-lang.github.io/rustup/safe-directories.html for more information.",
err.path.display(),
)
}
24 changes: 23 additions & 1 deletion src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};

use crate::cli::self_update::SelfUpdateMode;
use crate::dist::dist::Profile;
Expand Down Expand Up @@ -81,6 +81,7 @@ pub struct Settings {
pub overrides: BTreeMap<String, String>,
pub pgp_keys: Option<String>,
pub auto_self_update: Option<SelfUpdateMode>,
pub safe_directories: Vec<String>,
}

impl Default for Settings {
Expand All @@ -93,6 +94,7 @@ impl Default for Settings {
overrides: BTreeMap::new(),
pgp_keys: None,
auto_self_update: None,
safe_directories: Vec::new(),
}
}
}
Expand Down Expand Up @@ -155,6 +157,16 @@ impl Settings {
.and_then(|mode| SelfUpdateMode::from_str(mode.as_str()).ok());
let profile = get_opt_string(&mut table, "profile", path)?
.and_then(|p| Profile::from_str(p.as_str()).ok());
let safe_directories = get_array(&mut table, "safe_directories", path)?
.into_iter()
.map(|p| match p {
toml::Value::String(s) => Ok(s),
_ => Err(anyhow!(format!(
"expected string in safe_directories in `{}`",
path
))),
})
.collect::<Result<_>>()?;
Ok(Self {
version,
default_host_triple: get_opt_string(&mut table, "default_host_triple", path)?,
Expand All @@ -163,6 +175,7 @@ impl Settings {
overrides: Self::table_to_overrides(&mut table, path)?,
pgp_keys: get_opt_string(&mut table, "pgp_keys", path)?,
auto_self_update,
safe_directories,
})
}
pub(crate) fn into_toml(self) -> toml::value::Table {
Expand Down Expand Up @@ -193,6 +206,15 @@ impl Settings {
);
}

if !self.safe_directories.is_empty() {
let array = Vec::from_iter(
self.safe_directories
.iter()
.map(|p| toml::Value::String(p.to_string())),
);
result.insert("safe_directories".to_owned(), toml::Value::Array(array));
}

let overrides = Self::overrides_to_table(self.overrides);
result.insert("overrides".to_owned(), toml::Value::Table(overrides));

Expand Down
1 change: 1 addition & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
///! Utility functions for Rustup
pub(crate) mod notifications;
pub(crate) mod ownership;
pub mod raw;
pub(crate) mod toml_utils;
pub(crate) mod tty;
Expand Down
Loading