diff --git a/Cargo.lock b/Cargo.lock index 34892e4d01..8f685a8685 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,6 +779,7 @@ dependencies = [ "ic-wasm", "indicatif", "itertools 0.10.3", + "junction", "k256", "lazy_static", "mime", @@ -786,6 +787,7 @@ dependencies = [ "mockito", "net2", "num-traits", + "os_str_bytes", "pem", "petgraph", "proptest", @@ -806,6 +808,7 @@ dependencies = [ "slog", "slog-async", "slog-term", + "supports-color", "sysinfo", "tar", "tempfile", @@ -1749,6 +1752,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +[[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + [[package]] name = "itertools" version = "0.9.0" @@ -1793,6 +1802,16 @@ dependencies = [ "serde", ] +[[package]] +name = "junction" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be39922b087cecaba4e2d5592dedfc8bda5d4a5a1231f143337cca207950b61d" +dependencies = [ + "scopeguard", + "winapi", +] + [[package]] name = "k256" version = "0.11.4" @@ -2242,6 +2261,9 @@ name = "os_str_bytes" version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +dependencies = [ + "memchr", +] [[package]] name = "pairing" @@ -3292,6 +3314,16 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "supports-color" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" +dependencies = [ + "atty", + "is_ci", +] + [[package]] name = "syn" version = "1.0.99" diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index 837f3815a1..abdd4d2f4f 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -57,6 +57,7 @@ mime = "0.3.16" mime_guess = "2.0.4" net2 = "0.2.34" num-traits = "0.2" +os_str_bytes = "6.3.0" pem = "1.0.2" petgraph = "0.6.0" rand = "0.8.5" @@ -76,6 +77,7 @@ shell-words = "1.1.0" slog = { version = "2.5.2", features = ["max_level_trace"] } slog-async = "2.4.0" slog-term = "2.9.0" +supports-color = "1.3.0" sysinfo = "0.24.4" tar = "0.4.38" tempfile = "3.3.0" @@ -89,6 +91,9 @@ walkdir = "2.2.9" wasmparser = "0.87.0" which = "4.2.5" +[target.'cfg(windows)'.dependencies] +junction = "0.2.0" + [dev-dependencies] env_logger = "0.9" proptest = "1.0" diff --git a/src/dfx/src/commands/identity/new.rs b/src/dfx/src/commands/identity/new.rs index 822a823032..a50149eb09 100644 --- a/src/dfx/src/commands/identity/new.rs +++ b/src/dfx/src/commands/identity/new.rs @@ -15,7 +15,14 @@ pub struct NewIdentityOpts { /// The identity to create. new_identity: String, - /// The file path to the opensc-pkcs11 library e.g. "/usr/local/lib/opensc-pkcs11.so" + #[cfg_attr( + not(windows), + doc = r#"The file path to the opensc-pkcs11 library e.g. "/usr/local/lib/opensc-pkcs11.so""# + )] + #[cfg_attr( + windows, + doc = r#"The file path to the opensc-pkcs11 library e.g. "C:\Program Files (x86)\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll"# + )] #[clap(long, requires("hsm-key-id"))] hsm_pkcs11_lib_path: Option, diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index 28798e8bfc..c81c58b5b5 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -21,6 +21,7 @@ use anyhow::{anyhow, bail, Context, Error}; use clap::Parser; use fn_error_context::context; use garcon::{Delay, Waiter}; +use os_str_bytes::{OsStrBytes, OsStringBytes}; use slog::{info, warn, Logger}; use std::fs; use std::fs::create_dir_all; @@ -526,20 +527,20 @@ fn create_new_persistent_socket_path(uds_holder_path: &Path, prefix: &str) -> Df // Unix domain socket names can only be so long. // An attempt to use a path under .dfx/ resulted in this error: // path must be shorter than libc::sockaddr_un.sun_path - let uds_path = format!("/tmp/{}.{}.{}", prefix, pid, timestamp_seconds); - std::fs::write(uds_holder_path, &uds_path).with_context(|| { + let uds_path = std::env::temp_dir().join(format!("{}.{}.{}", prefix, pid, timestamp_seconds)); + std::fs::write(uds_holder_path, &uds_path.to_raw_bytes()).with_context(|| { format!( "unable to write unix domain socket path to {}", uds_holder_path.to_string_lossy() ) })?; - Ok(PathBuf::from(uds_path)) + Ok(uds_path) } #[context("Failed to get persistent socket path for {} at {}.", prefix, uds_holder_path.to_string_lossy())] fn get_persistent_socket_path(uds_holder_path: &Path, prefix: &str) -> DfxResult { - if let Ok(uds_path) = std::fs::read_to_string(uds_holder_path) { - Ok(PathBuf::from(uds_path.trim())) + if let Ok(uds_path) = std::fs::read(uds_holder_path) { + Ok(PathBuf::assert_from_raw_vec(uds_path)) } else { create_new_persistent_socket_path(uds_holder_path, prefix) } diff --git a/src/dfx/src/config/cache.rs b/src/dfx/src/config/cache.rs index 2f9791aa22..b590534ff8 100644 --- a/src/dfx/src/config/cache.rs +++ b/src/dfx/src/config/cache.rs @@ -1,6 +1,8 @@ use crate::config::dfx_version; use crate::lib::error::{CacheError, DfxError, DfxResult}; use crate::util; +#[cfg(windows)] +use crate::util::project_dirs; use anyhow::{bail, Context}; use fn_error_context::context; @@ -8,11 +10,13 @@ use indicatif::{ProgressBar, ProgressDrawTarget}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use semver::Version; +#[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; use std::process::ExitStatus; // POSIX permissions for files in the cache. +#[cfg(unix)] const EXEC_READ_USER_ONLY_PERMISSION: u32 = 0o500; pub trait Cache { @@ -68,11 +72,20 @@ impl Cache for DiskBasedCache { #[context("Failed to get cache root.")] pub fn get_cache_root() -> DfxResult { - let cache_root = std::env::var("DFX_CACHE_ROOT").ok(); - let home = - std::env::var("HOME").map_err(|_| DfxError::new(CacheError::CannotFindHomeDirectory()))?; - let root = cache_root.unwrap_or(home); - let p = PathBuf::from(root).join(".cache").join("dfinity"); + let cache_root = std::env::var_os("DFX_CACHE_ROOT"); + // dirs-next is not used for *nix to preserve existing paths + #[cfg(not(windows))] + let p = { + let home = std::env::var_os("HOME") + .ok_or_else(|| DfxError::new(CacheError::CannotFindHomeDirectory()))?; + let root = cache_root.unwrap_or(home); + PathBuf::from(root).join(".cache").join("dfinity") + }; + #[cfg(windows)] + let p = match cache_root { + Some(var) => PathBuf::from(var), + None => project_dirs()?.cache_dir().to_owned(), + }; if !p.exists() { if let Err(_e) = std::fs::create_dir_all(&p) { return Err(DfxError::new(CacheError::CannotCreateCacheDirectory(p))); @@ -180,23 +193,26 @@ pub fn install_version(v: &str, force: bool) -> DfxResult { } file.unpack_in(temp_p.as_path()) .context("Failed to unpack archive asset.")?; - - let full_path = temp_p.join(file.path().context("Failed to get file path.")?); - let mut perms = std::fs::metadata(full_path.as_path()) - .with_context(|| { + // On *nix we need to set the execute permission as the tgz doesn't include it + #[cfg(unix)] + { + let full_path = temp_p.join(file.path().context("Failed to get file path.")?); + let mut perms = std::fs::metadata(full_path.as_path()) + .with_context(|| { + format!( + "Failed to get file metadata for {}.", + full_path.to_string_lossy() + ) + })? + .permissions(); + perms.set_mode(EXEC_READ_USER_ONLY_PERMISSION); + std::fs::set_permissions(full_path.as_path(), perms).with_context(|| { format!( - "Failed to get file metadata for {}.", + "Failed to set file permissions for {}.", full_path.to_string_lossy() ) - })? - .permissions(); - perms.set_mode(EXEC_READ_USER_ONLY_PERMISSION); - std::fs::set_permissions(full_path.as_path(), perms).with_context(|| { - format!( - "Failed to set file permissions for {}.", - full_path.to_string_lossy() - ) - })?; + })?; + } } // Copy our own binary in the cache. @@ -211,19 +227,22 @@ pub fn install_version(v: &str, force: bool) -> DfxResult { dfx.to_string_lossy() ) })?; - // And make it executable. - let mut perms = std::fs::metadata(&dfx) - .with_context(|| { - format!( - "Failed to read file metadata for {}.", - dfx.to_string_lossy() - ) - })? - .permissions(); - perms.set_mode(EXEC_READ_USER_ONLY_PERMISSION); - std::fs::set_permissions(&dfx, perms).with_context(|| { - format!("Failed to set file metadata for {}.", dfx.to_string_lossy()) - })?; + // On *nix we need to set the execute permission as the tgz doesn't include it + #[cfg(unix)] + { + let mut perms = std::fs::metadata(&dfx) + .with_context(|| { + format!( + "Failed to read file metadata for {}.", + dfx.to_string_lossy() + ) + })? + .permissions(); + perms.set_mode(EXEC_READ_USER_ONLY_PERMISSION); + std::fs::set_permissions(&dfx, perms).with_context(|| { + format!("Failed to set file metadata for {}.", dfx.to_string_lossy()) + })?; + } // atomically install cache version into place if force && p.exists() { diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 221b17a079..20c62c49db 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -3,13 +3,12 @@ use crate::lib::bitcoin::adapter::config::BitcoinAdapterLogLevel; use crate::lib::canister_http::adapter::config::HttpAdapterLogLevel; use crate::lib::config::get_config_dfx_dir_path; use crate::lib::error::{BuildError, DfxError, DfxResult}; -use crate::util::{PossiblyStr, SerdeVec}; +use crate::util::{project_dirs, PossiblyStr, SerdeVec}; use crate::{error_invalid_argument, error_invalid_config, error_invalid_data}; use anyhow::{anyhow, Context}; use byte_unit::Byte; use candid::Principal; -use directories_next::ProjectDirs; use fn_error_context::context; use schemars::JsonSchema; use serde::de::{Error as _, MapAccess, Visitor}; @@ -883,10 +882,10 @@ impl NetworksConfig { } #[context("Failed to determine shared network data directory.")] pub fn get_network_data_directory(network: &str) -> DfxResult { - let project_dirs = ProjectDirs::from("org", "dfinity", "dfx").ok_or_else(|| { - anyhow!("Unable to retrieve a valid home directory path from the operating system") - })?; - Ok(project_dirs.data_local_dir().join("network").join(network)) + Ok(project_dirs()? + .data_local_dir() + .join("network") + .join(network)) } #[context("Failed to read shared networks configuration.")] diff --git a/src/dfx/src/lib/config.rs b/src/dfx/src/lib/config.rs index dfdb372b48..38b3ed6f6f 100644 --- a/src/dfx/src/lib/config.rs +++ b/src/dfx/src/lib/config.rs @@ -1,4 +1,6 @@ use crate::lib::error::DfxResult; +#[cfg(windows)] +use crate::util::project_dirs; use anyhow::{bail, Context}; use fn_error_context::context; @@ -6,10 +8,19 @@ use std::path::PathBuf; #[context("Failed to get path to dfx config dir.")] pub fn get_config_dfx_dir_path() -> DfxResult { - let config_root = std::env::var("DFX_CONFIG_ROOT").ok(); - let home = std::env::var("HOME").context("Failed to resolve 'HOME' env var.")?; - let root = config_root.unwrap_or(home); - let p = PathBuf::from(root).join(".config").join("dfx"); + let config_root = std::env::var_os("DFX_CONFIG_ROOT"); + // dirs-next is not used for *nix to preserve existing paths + #[cfg(not(windows))] + let p = { + let home = std::env::var_os("HOME").context("Failed to resolve 'HOME' env var.")?; + let root = config_root.unwrap_or(home); + PathBuf::from(root).join(".config").join("dfx") + }; + #[cfg(windows)] + let p = match config_root { + Some(var) => PathBuf::from(var), + None => project_dirs()?.config_dir().to_owned(), + }; if !p.exists() { std::fs::create_dir_all(&p) .with_context(|| format!("Cannot create config directory at {}", p.display()))?; diff --git a/src/dfx/src/lib/dist.rs b/src/dfx/src/lib/dist.rs index 49655cc63e..8c40fa0519 100644 --- a/src/dfx/src/lib/dist.rs +++ b/src/dfx/src/lib/dist.rs @@ -1,5 +1,7 @@ use crate::lib::error::{DfxError, DfxResult}; use crate::lib::manifest::Manifest; +#[cfg(windows)] +use crate::util::project_dirs; use crate::{error_invalid_argument, error_invalid_data}; use anyhow::Context; @@ -9,12 +11,11 @@ use indicatif::{ProgressBar, ProgressDrawTarget}; use semver::Version; use std::fs; use std::io::Write; -use std::path::Path; use tar::Archive; pub static DEFAULT_RELEASE_ROOT: &str = "https://sdk.dfinity.org"; -pub static CACHE_ROOT: &str = ".cache/dfinity/versions/"; -pub static DOWNLOADS_DIR: &str = ".cache/dfinity/downloads/"; +pub static CACHE_SUBDIR: &str = "versions"; +pub static DOWNLOADS_SUBDIR: &str = "downloads"; #[context("Failed to get distribution manifest.")] pub fn get_manifest() -> DfxResult { @@ -58,10 +59,15 @@ pub fn install_version(version: &Version) -> DfxResult<()> { )) .map_err(|e| error_invalid_argument!("invalid url: {}", e))?; - let home = std::env::var("HOME").context("Failed to resolve env var HOME.")?; - let home = Path::new(&home); + // directories-next is not used for *nix to preserve existing paths + #[cfg(not(windows))] + let cache_dir = + std::path::Path::new(&std::env::var_os("HOME").context("Failed to resolve env var HOME.")?) + .join(".cache/dfinity"); + #[cfg(windows)] + let cache_dir = project_dirs()?.cache_dir(); - let download_dir = home.join(DOWNLOADS_DIR); + let download_dir = cache_dir.join(DOWNLOADS_SUBDIR); if !download_dir.exists() { fs::create_dir_all(&download_dir) .with_context(|| format!("Failed to create dir {}.", download_dir.to_string_lossy()))?; @@ -88,11 +94,12 @@ pub fn install_version(version: &Version) -> DfxResult<()> { b.finish_with_message("Download complete"); } - let mut cache_dir = home.join(CACHE_ROOT); - cache_dir.push(version.to_string()); - if !cache_dir.exists() { - fs::create_dir_all(&cache_dir) - .with_context(|| format!("Failed to create {}.", cache_dir.to_string_lossy()))?; + let mut version_cache_dir = cache_dir.join(CACHE_SUBDIR); + version_cache_dir.push(version.to_string()); + if !version_cache_dir.exists() { + fs::create_dir_all(&version_cache_dir).with_context(|| { + format!("Failed to create {}.", version_cache_dir.to_string_lossy()) + })?; } let b = ProgressBar::new_spinner(); @@ -106,7 +113,7 @@ pub fn install_version(version: &Version) -> DfxResult<()> { .with_context(|| format!("Failed to open {}.", download_file.to_string_lossy()))?; let tar = GzDecoder::new(tar_gz); let mut archive = Archive::new(tar); - archive.unpack(&cache_dir).with_context(|| { + archive.unpack(&version_cache_dir).with_context(|| { format!( "Failed to unpack archive at {}.", download_file.to_string_lossy() @@ -115,7 +122,7 @@ pub fn install_version(version: &Version) -> DfxResult<()> { b.finish_with_message("Unpack complete"); // Install components - let dfx = cache_dir.join("dfx"); + let dfx = version_cache_dir.join("dfx"); std::process::Command::new(dfx) .args(&["cache", "install"]) .status() diff --git a/src/dfx/src/lib/error/cache.rs b/src/dfx/src/lib/error/cache.rs index 71843ccd95..fdb53abb26 100644 --- a/src/dfx/src/lib/error/cache.rs +++ b/src/dfx/src/lib/error/cache.rs @@ -9,6 +9,8 @@ pub enum CacheError { #[error("Cannot find cache directory at '{0}'.")] CannotFindCacheDirectory(PathBuf), + // Windows paths do not require environment variables (and are found by dirs-next, which has its own errors) + #[cfg(not(windows))] #[error("Cannot find home directory.")] CannotFindHomeDirectory(), diff --git a/src/dfx/src/lib/identity/identity_manager.rs b/src/dfx/src/lib/identity/identity_manager.rs index 13487e4489..647bf68170 100644 --- a/src/dfx/src/lib/identity/identity_manager.rs +++ b/src/dfx/src/lib/identity/identity_manager.rs @@ -75,7 +75,14 @@ impl EncryptionConfiguration { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct HardwareIdentityConfiguration { - /// The file path to the opensc-pkcs11 library e.g. "/usr/local/lib/opensc-pkcs11.so" + #[cfg_attr( + not(windows), + doc = r#"The file path to the opensc-pkcs11 library e.g. "/usr/local/lib/opensc-pkcs11.so""# + )] + #[cfg_attr( + windows, + doc = r#"The file path to the opensc-pkcs11 library e.g. "C:\Program Files (x86)\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll"# + )] pub pkcs11_lib_path: String, /// A sequence of pairs of hex digits @@ -457,29 +464,32 @@ To create a more secure identity, create and use an identity that is protected b } let creds_pem_path = get_legacy_creds_pem_path()?; - if creds_pem_path.exists() { - slog::info!( - logger, - " - migrating key from {} to {}", - creds_pem_path.display(), - identity_pem_path.display() - ); - fs::copy(&creds_pem_path, &identity_pem_path).with_context(|| { - format!( - "Failed to migrate legacy identity from {} to {}.", - creds_pem_path.to_string_lossy(), - identity_pem_path.to_string_lossy() - ) - })?; - } else { - slog::info!( - logger, - " - generating new key at {}", - identity_pem_path.display() - ); - let (key, mnemonic) = generate_key()?; - pem_encryption::write_pem_file(&identity_pem_path, None, key.as_slice())?; - eprintln!("Your seed phrase: {}\nThis can be used to reconstruct your key in case of emergency, so write it down in a safe place.", mnemonic.phrase()); + match creds_pem_path { + Some(creds_pem_path) if creds_pem_path.exists() => { + slog::info!( + logger, + " - migrating key from {} to {}", + creds_pem_path.display(), + identity_pem_path.display() + ); + fs::copy(&creds_pem_path, &identity_pem_path).with_context(|| { + format!( + "Failed to migrate legacy identity from {} to {}.", + creds_pem_path.to_string_lossy(), + identity_pem_path.to_string_lossy() + ) + })?; + } + _ => { + slog::info!( + logger, + " - generating new key at {}", + identity_pem_path.display() + ); + let (key, mnemonic) = generate_key()?; + pem_encryption::write_pem_file(&identity_pem_path, None, key.as_slice())?; + eprintln!("Your seed phrase: {}\nThis can be used to reconstruct your key in case of emergency, so write it down in a safe place.", mnemonic.phrase()); + } } } else { slog::info!( @@ -499,16 +509,23 @@ To create a more secure identity, create and use an identity that is protected b } #[context("Failed to get legacy pem path.")] -fn get_legacy_creds_pem_path() -> DfxResult { - let config_root = std::env::var("DFX_CONFIG_ROOT").ok(); - let home = std::env::var("HOME") - .map_err(|_| DfxError::new(IdentityError::CannotFindHomeDirectory()))?; - let root = config_root.unwrap_or(home); - - Ok(PathBuf::from(root) - .join(".dfinity") - .join("identity") - .join("creds.pem")) +fn get_legacy_creds_pem_path() -> DfxResult> { + if cfg!(windows) { + // No legacy path on Windows - there was no Windows support when paths were changed + Ok(None) + } else { + let config_root = std::env::var("DFX_CONFIG_ROOT").ok(); + let home = std::env::var("HOME") + .map_err(|_| DfxError::new(IdentityError::CannotFindHomeDirectory()))?; + let root = config_root.unwrap_or(home); + + Ok(Some( + PathBuf::from(root) + .join(".dfinity") + .join("identity") + .join("creds.pem"), + )) + } } #[context("Failed to load identity manager config from {}.", path.to_string_lossy())] diff --git a/src/dfx/src/lib/identity/pem_encryption.rs b/src/dfx/src/lib/identity/pem_encryption.rs index e9159d43d4..8b3304ce72 100644 --- a/src/dfx/src/lib/identity/pem_encryption.rs +++ b/src/dfx/src/lib/identity/pem_encryption.rs @@ -49,6 +49,7 @@ pub fn write_pem_file( .with_context(|| format!("Failed to read permissions of {}.", path.to_string_lossy()))? .permissions(); permissions.set_readonly(true); + // On *nix, set the read permission to owner-only. #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; diff --git a/src/dfx/src/lib/manifest.rs b/src/dfx/src/lib/manifest.rs index 31610ddfff..8fef2610aa 100644 --- a/src/dfx/src/lib/manifest.rs +++ b/src/dfx/src/lib/manifest.rs @@ -8,8 +8,7 @@ use indicatif::{ProgressBar, ProgressDrawTarget}; use semver::Version; use serde::{Deserialize, Deserializer}; use std::collections::BTreeMap; -use std::os::unix::fs::PermissionsExt; -use std::{env, fs}; +use std::env; use tar::Archive; fn parse_semver<'de, D>(version: &str) -> Result @@ -149,22 +148,27 @@ pub fn get_latest_release(release_root: &str, version: &Version, arch: &str) -> archive .unpack(¤t_exe_dir) .with_context(|| format!("Failed to unpack to {}.", current_exe_dir.to_string_lossy()))?; - b.set_message("Setting permissions"); - let mut permissions = fs::metadata(¤t_exe_path) - .with_context(|| { + // On *nix we need to set the execute permission as the tgz doesn't include it + #[cfg(unix)] + { + use std::{fs, os::unix::fs::PermissionsExt}; + b.set_message("Setting permissions"); + let mut permissions = fs::metadata(¤t_exe_path) + .with_context(|| { + format!( + "Failed to read metadata for {}.", + current_exe_path.to_string_lossy() + ) + })? + .permissions(); + permissions.set_mode(0o775); // FIXME Preserve existing permissions + fs::set_permissions(¤t_exe_path, permissions).with_context(|| { format!( - "Failed to read metadata for {}.", + "Failed to set metadata for {}.", current_exe_path.to_string_lossy() ) - })? - .permissions(); - permissions.set_mode(0o775); // FIXME Preserve existing permissions - fs::set_permissions(¤t_exe_path, permissions).with_context(|| { - format!( - "Failed to set metadata for {}.", - current_exe_path.to_string_lossy() - ) - })?; + })?; + } b.finish_with_message("Done"); Ok(()) } diff --git a/src/dfx/src/lib/network/local_server_descriptor.rs b/src/dfx/src/lib/network/local_server_descriptor.rs index c2e0c011c3..735aef38af 100644 --- a/src/dfx/src/lib/network/local_server_descriptor.rs +++ b/src/dfx/src/lib/network/local_server_descriptor.rs @@ -27,6 +27,7 @@ pub struct LocalServerDescriptor { /// /.dfx/network/local /// $HOME/Library/Application Support/org.dfinity.dfx/network/local /// $HOME/.local/share/dfx/network/local + /// $APPDATA/dfx/network/local pub data_directory: PathBuf, pub bind_address: SocketAddr, diff --git a/src/dfx/src/lib/sign/sign_transport.rs b/src/dfx/src/lib/sign/sign_transport.rs index 583fcec9eb..f0d8f4981d 100644 --- a/src/dfx/src/lib/sign/sign_transport.rs +++ b/src/dfx/src/lib/sign/sign_transport.rs @@ -7,7 +7,7 @@ use ic_agent::{AgentError, RequestId}; use std::fs::{File, OpenOptions}; use std::future::Future; use std::io::{Read, Write}; -use std::path::Path; +use std::path::PathBuf; use std::pin::Pin; use thiserror::Error; @@ -18,12 +18,12 @@ enum SerializeStatus { } pub(crate) struct SignReplicaV2Transport { - file_name: String, + file_name: PathBuf, message_template: SignedMessageV1, } impl SignReplicaV2Transport { - pub fn new>(file_name: U, message_template: SignedMessageV1) -> Self { + pub fn new>(file_name: U, message_template: SignedMessageV1) -> Self { Self { file_name: file_name.into(), message_template, @@ -38,9 +38,8 @@ impl ReplicaV2Transport for SignReplicaV2Transport { envelope: Vec, ) -> Pin, AgentError>> + Send + 'a>> { async fn run(s: &SignReplicaV2Transport, envelope: Vec) -> Result, AgentError> { - let path = Path::new(&s.file_name); - let mut file = - File::open(&path).map_err(|x| AgentError::MessageError(x.to_string()))?; + let path = &s.file_name; + let mut file = File::open(path).map_err(|x| AgentError::MessageError(x.to_string()))?; let mut json = String::new(); file.read_to_string(&mut json) .map_err(|x| AgentError::MessageError(x.to_string()))?; @@ -59,7 +58,7 @@ impl ReplicaV2Transport for SignReplicaV2Transport { Err(AgentError::TransportError( SerializeStatus::Success(format!( "Signed request_status append to update message in [{}]", - &s.file_name + s.file_name.display() )) .into(), )) @@ -87,14 +86,17 @@ impl ReplicaV2Transport for SignReplicaV2Transport { .with_content(hex::encode(&envelope)); let json = serde_json::to_string(&message) .map_err(|x| AgentError::MessageError(x.to_string()))?; - let path = Path::new(&s.file_name); + let path = &s.file_name; let mut file = - File::create(&path).map_err(|x| AgentError::MessageError(x.to_string()))?; + File::create(path).map_err(|x| AgentError::MessageError(x.to_string()))?; file.write_all(json.as_bytes()) .map_err(|x| AgentError::MessageError(x.to_string()))?; Err(AgentError::TransportError( - SerializeStatus::Success(format!("Update message generated at [{}]", &s.file_name)) - .into(), + SerializeStatus::Success(format!( + "Update message generated at [{}]", + s.file_name.display() + )) + .into(), )) } @@ -114,14 +116,17 @@ impl ReplicaV2Transport for SignReplicaV2Transport { .with_content(hex::encode(&envelope)); let json = serde_json::to_string(&message) .map_err(|x| AgentError::MessageError(x.to_string()))?; - let path = Path::new(&s.file_name); + let path = &s.file_name; let mut file = - File::create(&path).map_err(|x| AgentError::MessageError(x.to_string()))?; + File::create(path).map_err(|x| AgentError::MessageError(x.to_string()))?; file.write_all(json.as_bytes()) .map_err(|x| AgentError::MessageError(x.to_string()))?; Err(AgentError::TransportError( - SerializeStatus::Success(format!("Query message generated at [{}]", &s.file_name)) - .into(), + SerializeStatus::Success(format!( + "Query message generated at [{}]", + s.file_name.display() + )) + .into(), )) } diff --git a/src/dfx/src/lib/toolchain.rs b/src/dfx/src/lib/toolchain.rs index cb46bb1034..b4257f802b 100644 --- a/src/dfx/src/lib/toolchain.rs +++ b/src/dfx/src/lib/toolchain.rs @@ -3,11 +3,12 @@ use crate::lib::dist; use crate::lib::error::{DfxError, DfxResult}; use anyhow::{bail, Context}; +use directories_next::BaseDirs; use fn_error_context::context; use semver::{Version, VersionReq}; use std::fmt; use std::fmt::Formatter; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::str::FromStr; const TOOLCHAINS_ROOT: &str = ".dfinity/toolchains/"; @@ -113,11 +114,22 @@ impl Toolchain { format!("Failed to remove {}.", toolchain_path.to_string_lossy()) })?; } + #[cfg(unix)] std::os::unix::fs::symlink(&cache_path, &toolchain_path).with_context(|| { format!( "Failed to create symlink from {} to {}.", - toolchain_path.to_string_lossy(), - cache_path.to_string_lossy() + toolchain_path.display(), + cache_path.display(), + ) + })?; + // On Windows, symlinks require admin permission or developer mode, + // and junctions are preferable anyway as the filesystem parses them instead of the OS. + #[cfg(windows)] + junction::create(&cache_path, &toolchain_path).with_context(|| { + format!( + "Failed to create junction from {} to {}.", + toolchain_path.display(), + cache_path.display(), ) })?; } @@ -147,9 +159,8 @@ impl Toolchain { #[context("Failed to get toolchain path.")] pub fn get_path(&self) -> DfxResult { - let home = std::env::var("HOME").context("Failed to resolve env var 'HOME'.")?; - let home = Path::new(&home); - let toolchains_dir = home.join(TOOLCHAINS_ROOT); + let dirs = BaseDirs::new().context("Failed to resolve home dir.")?; + let toolchains_dir = dirs.home_dir().join(TOOLCHAINS_ROOT); std::fs::create_dir_all(&toolchains_dir).with_context(|| { format!( "Failed to create toolchain dir {}.", @@ -172,11 +183,22 @@ impl Toolchain { ) })?; } + #[cfg(unix)] std::os::unix::fs::symlink(&toolchain_path, &default_path).with_context(|| { format!( "Failed to create symlink from {} to {}.", - toolchain_path.to_string_lossy(), - default_path.to_string_lossy() + toolchain_path.display(), + default_path.display(), + ) + })?; + // On Windows, symlinks require admin permission or developer mode, + // and junctions are preferable anyway as the filesystem parses them instead of the OS. + #[cfg(windows)] + junction::create(&toolchain_path, &default_path).with_context(|| { + format!( + "Failed to create junction from {} to {}.", + toolchain_path.display(), + default_path.display(), ) })?; println!("Default toolchain set to {}", self); @@ -186,9 +208,8 @@ impl Toolchain { #[context("Failed to get installed toolchains.")] pub fn list_installed_toolchains() -> DfxResult> { - let home = std::env::var("HOME").context("Failed to resolve env var 'HOME'.")?; - let home = Path::new(&home); - let toolchains_dir = home.join(TOOLCHAINS_ROOT); + let dirs = BaseDirs::new().context("Failed to resolve home dir.")?; + let toolchains_dir = dirs.home_dir().join(TOOLCHAINS_ROOT); let mut toolchains = vec![]; for entry in std::fs::read_dir(&toolchains_dir).with_context(|| { format!( @@ -230,9 +251,8 @@ pub fn get_default_toolchain() -> DfxResult { #[context("Failed to get default toolchain path.")] fn get_default_path() -> DfxResult { - let home = std::env::var("HOME").context("Failed to read env var 'HOME'.")?; - let home = Path::new(&home); - let default_path = home.join(DEFAULT_PATH); + let dirs = BaseDirs::new().context("Failed to resolve home dir.")?; + let default_path = dirs.home_dir().join(DEFAULT_PATH); let parent = default_path.parent().unwrap(); std::fs::create_dir_all(parent) .with_context(|| format!("Failed to create dir {}.", parent.to_string_lossy()))?; diff --git a/src/dfx/src/util/assets.rs b/src/dfx/src/util/assets.rs index 925880b57b..79d62aac39 100644 --- a/src/dfx/src/util/assets.rs +++ b/src/dfx/src/util/assets.rs @@ -7,17 +7,17 @@ use std::io::Read; include!(concat!(env!("OUT_DIR"), "/load_assets.rs")); pub fn dfinity_logo() -> String { - if atty::is(atty::Stream::Stdout) { - //MacOS's Terminal.app does not support Truecolor (RGB-colored characters) properly. - //Therefore we use xterm256 coloring when the program is running on macos - if std::env::consts::OS == "macos" { - include_str!("../../assets/dfinity-color-xterm256.aart").to_string() - } else { - include_str!("../../assets/dfinity-color.aart").to_string() + let colors = supports_color::on(atty::Stream::Stdout); + if let Some(colors) = colors { + //Some terminals, notably MacOS's Terminal.app, do not support Truecolor (RGB-colored characters) properly. + //Therefore we use xterm256 coloring when the program is running in such a terminal. + if colors.has_16m { + return include_str!("../../assets/dfinity-color.aart").to_string(); + } else if colors.has_256 { + return include_str!("../../assets/dfinity-color-xterm256.aart").to_string(); } - } else { - include_str!("../../assets/dfinity-nocolor.aart").to_string() } + include_str!("../../assets/dfinity-nocolor.aart").to_string() } #[context("Failed to load wallet wasm.")] diff --git a/src/dfx/src/util/mod.rs b/src/dfx/src/util/mod.rs index 576257e447..e26f76de7f 100644 --- a/src/dfx/src/util/mod.rs +++ b/src/dfx/src/util/mod.rs @@ -6,9 +6,11 @@ use candid::parser::typing::{pretty_check_file, TypeEnv}; use candid::types::{Function, Type}; use candid::Deserialize; use candid::{parser::value::IDLValue, IDLArgs}; +use directories_next::ProjectDirs; use fn_error_context::context; -use net2::TcpListenerExt; -use net2::{unix::UnixTcpBuilderExt, TcpBuilder}; +#[cfg(unix)] +use net2::unix::UnixTcpBuilderExt; +use net2::{TcpBuilder, TcpListenerExt}; use num_traits::FromPrimitive; use rust_decimal::Decimal; use schemars::JsonSchema; @@ -38,11 +40,15 @@ pub fn get_reusable_socket_addr(ip: IpAddr, port: u16) -> DfxResult } else { TcpBuilder::new_v6().context("Failed to create IPv6 builder.")? }; - let listener = tcp_builder + let tcp_builder = tcp_builder .reuse_address(true) - .context("Failed to set option reuse_address of tcp builder.")? + .context("Failed to set option reuse_address of tcp builder.")?; + // On Windows, SO_REUSEADDR without SO_EXCLUSIVEADDRUSE acts like SO_REUSEPORT (among other things), so this is only necessary on *nix. + #[cfg(unix)] + let tcp_builder = tcp_builder .reuse_port(true) - .context("Failed to set option reuse_port of tcp builder.")? + .context("Failed to set option reuse_port of tcp builder.")?; + let listener = tcp_builder .bind(SocketAddr::new(ip, port)) .with_context(|| format!("Failed to set socket of tcp builder to {}:{}.", ip, port))? .to_tcp_listener() @@ -343,6 +349,13 @@ pub fn pretty_thousand_separators(num: String) -> String { .collect::<_>() } +pub fn project_dirs() -> DfxResult<&'static ProjectDirs> { + lazy_static::lazy_static! { + static ref DIRS: Option = ProjectDirs::from("org", "dfinity", "dfx"); + } + DIRS.as_ref().context("Failed to resolve 'HOME' env var.") +} + #[cfg(test)] mod tests { use super::{format_as_trillions, pretty_thousand_separators};