Keep mobile storage paths container-relative

This commit is contained in:
2026-08-17 19:43:22 +02:00
parent 9f262ec80c
commit c617430149
2 changed files with 94 additions and 8 deletions

View File

@@ -608,16 +608,23 @@ impl Config {
if !vault.is_dir() { if !vault.is_dir() {
return Err(ConfigError::VaultIsNotDirectory { path: vault }); return Err(ConfigError::VaultIsNotDirectory { path: vault });
} }
let vault = vault
.to_str()
.ok_or(ConfigError::InvalidField { field: "vault" })?;
let mut document = self.document.clone(); let mut document = self.document.clone();
let root = document let root = document
.as_table_mut() .as_table_mut()
.ok_or_else(|| ConfigError::Malformed { .ok_or_else(|| ConfigError::Malformed {
path: self.source.clone(), path: self.source.clone(),
})?; })?;
root.insert("vault".to_owned(), toml::Value::String(vault.to_owned())); if vault != self.vault {
let base = self
.source
.parent()
.ok_or(ConfigError::InvalidField { field: "source" })?;
let stored = vault.strip_prefix(base).unwrap_or(&vault);
root.insert(
"vault".to_owned(),
toml::Value::String(path_text(stored, "vault")?),
);
}
root.insert( root.insert(
"default_key".to_owned(), "default_key".to_owned(),
toml::Value::String(settings.default_key), toml::Value::String(settings.default_key),
@@ -1152,7 +1159,7 @@ struct RawGitRemote {
fn validate_config( fn validate_config(
source: PathBuf, source: PathBuf,
document: toml::Value, mut document: toml::Value,
raw: RawConfig, raw: RawConfig,
) -> Result<Config, ConfigError> { ) -> Result<Config, ConfigError> {
let base = source let base = source
@@ -1162,6 +1169,7 @@ fn validate_config(
if vault.exists() && !vault.is_dir() { if vault.exists() && !vault.is_dir() {
return Err(ConfigError::VaultIsNotDirectory { path: vault }); return Err(ConfigError::VaultIsNotDirectory { path: vault });
} }
make_local_path_relative(&mut document, base, &vault, "vault")?;
let key_material = resolve_required_path(base, raw.key_material, "key_material")?; let key_material = resolve_required_path(base, raw.key_material, "key_material")?;
if !key_material.exists() { if !key_material.exists() {
@@ -1176,6 +1184,7 @@ fn validate_config(
} }
let key_material = fs::canonicalize(&key_material) let key_material = fs::canonicalize(&key_material)
.map_err(|_| ConfigError::InvalidKeyMaterial { path: key_material })?; .map_err(|_| ConfigError::InvalidKeyMaterial { path: key_material })?;
make_local_path_relative(&mut document, base, &key_material, "key_material")?;
let default_key = raw let default_key = raw
.default_key .default_key
@@ -1276,7 +1285,54 @@ fn resolve_required_path(
if value.as_os_str().is_empty() { if value.as_os_str().is_empty() {
return Err(ConfigError::InvalidField { field }); return Err(ConfigError::InvalidField { field });
} }
Ok(resolve_path(base, &value)) let resolved = resolve_path(base, &value);
if resolved.exists() || !value.is_absolute() {
return Ok(resolved);
}
Ok(relocated_mobile_path(base, &resolved, field).unwrap_or(resolved))
}
fn relocated_mobile_path(base: &Path, path: &Path, field: &'static str) -> Option<PathBuf> {
let leaf = match field {
"vault" => "vault",
"key_material" => "keys",
_ => return None,
};
let suffix = Path::new("Library/Application Support/ironstorage").join(leaf);
let is_stale_mobile_path = [
Path::new("/private/var/mobile/Containers/Data/Application"),
Path::new("/var/mobile/Containers/Data/Application"),
]
.iter()
.any(|prefix| {
path.strip_prefix(prefix).is_ok_and(|relative| {
let mut components = relative.components();
components.next().is_some() && components.as_path() == suffix
})
});
let relocated = base.join(leaf);
(is_stale_mobile_path && relocated.exists()).then_some(relocated)
}
fn make_local_path_relative(
document: &mut toml::Value,
base: &Path,
path: &Path,
field: &'static str,
) -> Result<(), ConfigError> {
let Ok(relative) = path.strip_prefix(base) else {
return Ok(());
};
let root = document
.as_table_mut()
.ok_or_else(|| ConfigError::Malformed {
path: base.join(CONFIG_FILE),
})?;
root.insert(
field.to_owned(),
toml::Value::String(path_text(relative, field)?),
);
Ok(())
} }
fn path_text(path: &Path, field: &'static str) -> Result<String, ConfigError> { fn path_text(path: &Path, field: &'static str) -> Result<String, ConfigError> {
@@ -1649,4 +1705,31 @@ mod tests {
("ALICE", Some(1_789_000_000), MobileTab::Preferences) ("ALICE", Some(1_789_000_000), MobileTab::Preferences)
); );
} }
#[test]
fn stale_ios_container_path_follows_the_current_container() {
let temporary = tempfile::tempdir().expect("temporary directory");
let vault = temporary.path().join("vault");
let keys = temporary.path().join("keys");
let source = temporary.path().join("config.toml");
fs::create_dir(&vault).expect("vault");
fs::create_dir(&keys).expect("keys");
fs::write(
&source,
r#"vault = "/private/var/mobile/Containers/Data/Application/OLD-CONTAINER/Library/Application Support/ironstorage/vault"
default_key = "ALICE"
key_material = "keys"
"#,
)
.expect("config");
let config = Config::load(Some(&source)).expect("recover moved container");
assert_eq!(config.vault(), fs::canonicalize(vault).expect("vault path"));
config
.update_mobile_tab(MobileTab::Preferences)
.expect("persist recovered config");
let contents = fs::read_to_string(source).expect("read config");
assert!(contents.contains("vault = \"vault\""));
assert!(!contents.contains("OLD-CONTAINER"));
}
} }

View File

@@ -842,6 +842,9 @@ mod tests {
.as_str(), .as_str(),
ALICE_FINGERPRINT ALICE_FINGERPRINT
); );
let config = std::fs::read_to_string(fixture.config.source())?;
assert!(config.contains("vault = \"vault\""));
assert!(config.contains("key_material = \"keys\""));
Ok(()) Ok(())
} }
@@ -870,8 +873,8 @@ mod tests {
std::fs::write( std::fs::write(
&config_path, &config_path,
format!( format!(
"vault = {:?}\ndefault_key = {:?}\nkey_material = {:?}\n", "vault = \"vault\"\ndefault_key = {:?}\nkey_material = \"keys\"\n",
vault, ALICE_FINGERPRINT, keys ALICE_FINGERPRINT
), ),
)?; )?;
let config = Config::load(Some(&config_path))?; let config = Config::load(Some(&config_path))?;