diff options
-rw-r--r-- | Cargo.lock | 23 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/error.rs | 3 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 24 | ||||
-rw-r--r-- | src/minemod.rs | 21 | ||||
-rw-r--r-- | src/util.rs | 40 |
7 files changed, 113 insertions, 3 deletions
@@ -450,6 +450,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] name = "miniz_oxide" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -468,6 +477,7 @@ dependencies = [ "dirs", "itertools", "log", + "nix", "once_cell", "regex", "scraper", @@ -490,6 +500,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] +name = "nix" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -25,3 +25,6 @@ ureq = { version = "2.3.0", features = ["json"] } url = { version = "2.2.2", features = ["serde"] } uuid = { version = "0.8.2", features = ["v4"] } zip = "0.5.13" + +[target.'cfg(unix)'.dependencies] +nix = "0.23.0" diff --git a/src/error.rs b/src/error.rs index 08539c0..86d758f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,6 +32,9 @@ pub enum Error { UrlError(#[from] url::ParseError), #[error("the mod ID '{0}' does not point to a single mod")] AmbiguousModId(String), + #[cfg(unix)] + #[error("underlying Unix error")] + NixError(#[from] nix::errno::Errno), } pub type Result<T, E = Error> = std::result::Result<T, E>; @@ -16,7 +16,7 @@ pub mod error; pub mod game; pub mod kvstore; pub mod minemod; -mod util; +pub mod util; pub mod world; pub use baas::{Baas, Snapshot}; diff --git a/src/main.rs b/src/main.rs index da04f9c..f42647a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use log::debug; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use modderbaas::{ - minemod::ModContainer, Baas, ContentDb, Downloader, MineMod, Snapshot, Source, World, + minemod::ModContainer, util, Baas, ContentDb, Downloader, MineMod, Snapshot, Source, World, }; fn main() -> Result<()> { @@ -52,6 +52,12 @@ fn main() -> Result<()> { .short("n") .long("dry-run") .required(false), + ) + .arg( + Arg::with_name("fix-permissions") + .short("p") + .long("fix-permissions") + .required(false), ), ) .get_matches(); @@ -192,8 +198,9 @@ fn install_mods( mods: &[&str], matches: &ArgMatches, ) -> Result<()> { - let target_dir = &Path::new(matches.value_of("target").unwrap()); + let target_dir = Path::new(matches.value_of("target").unwrap()); let dry_run = matches.is_present("dry-run"); + let fix_permissions = matches.is_present("fix-permissions"); let content_db = ContentDb::new(); let downloader = Downloader::new()?; @@ -301,6 +308,19 @@ fn install_mods( .install_to(target_dir) .context(format!("Error installing '{}'", mod_id))?; to_enable.extend(installed.mods()?); + + #[cfg(unix)] + { + use nix::{ + sys::stat, + unistd::{Gid, Uid}, + }; + if fix_permissions { + let perms = stat::stat(target_dir)?; + let (uid, gid) = (Uid::from_raw(perms.st_uid), Gid::from_raw(perms.st_gid)); + util::chown_recursive(installed.path(), Some(uid), Some(gid))?; + } + } } for m in to_enable { diff --git a/src/minemod.rs b/src/minemod.rs index 9162907..f45b897 100644 --- a/src/minemod.rs +++ b/src/minemod.rs @@ -33,6 +33,11 @@ impl MineMod { Ok(MineMod { path: path.into() }) } + /// Returns the path of this mod. + pub fn path(&self) -> &Path { + &self.path + } + fn read_conf(&self) -> Result<HashMap<String, String>> { let conf = self.path.join("mod.conf"); kvstore::read(&conf) @@ -101,6 +106,11 @@ impl Modpack { Ok(Modpack { path: path.into() }) } + /// Returns the path of this modpack. + pub fn path(&self) -> &Path { + &self.path + } + fn conf(&self) -> Result<HashMap<String, String>> { let conf = self.path.join("modpack.conf"); kvstore::read(&conf) @@ -148,6 +158,9 @@ pub trait ModContainer: Any + fmt::Display { /// Returns the name of the mod container. fn name(&self) -> Result<String>; + /// Returns the on-disk path of this mod container. + fn path(&self) -> &Path; + /// Return all contained mods. fn mods(&self) -> Result<Vec<MineMod>>; @@ -160,6 +173,10 @@ impl ModContainer for MineMod { self.mod_id() } + fn path(&self) -> &Path { + self.path() + } + fn mods(&self) -> Result<Vec<MineMod>> { Ok(vec![self.clone()]) } @@ -175,6 +192,10 @@ impl ModContainer for Modpack { self.name() } + fn path(&self) -> &Path { + self.path() + } + fn mods(&self) -> Result<Vec<MineMod>> { self.mods() } diff --git a/src/util.rs b/src/util.rs index 24b8701..d5f9ec2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,19 @@ use std::{fs, io, path::Path}; +#[cfg(unix)] +use nix::unistd::{self, Gid, Uid}; + +use super::error::Result; + +#[cfg(not(unix))] +pub mod nix { + //! Stub mod on non-unix systems. + pub mod unistd { + pub enum Uid {} + pub enum Gid {} + } +} + /// Recursively copy the *contents* of the given directory to the given path. pub fn copy_recursive<S: AsRef<Path>, D: AsRef<Path>>(source: S, destination: D) -> io::Result<()> { copy_inner(source.as_ref(), destination.as_ref()) @@ -19,3 +33,29 @@ fn copy_inner(source: &Path, destination: &Path) -> io::Result<()> { } Ok(()) } + +/// Recursively change the owner of the given path to the given ones. +/// +/// Note that this function only works on Unix systems. **It will panic on other systems!** +pub fn chown_recursive<P: AsRef<Path>>(path: P, uid: Option<Uid>, gid: Option<Gid>) -> Result<()> { + chown_inner(path.as_ref(), uid, gid) +} + +#[cfg(unix)] +fn chown_inner(path: &Path, uid: Option<Uid>, gid: Option<Gid>) -> Result<()> { + unistd::chown(path, uid, gid)?; + + let metadata = fs::metadata(path)?; + if metadata.is_dir() { + for item in fs::read_dir(path)? { + let item = item?; + chown_inner(&item.path(), uid, gid)?; + } + } + Ok(()) +} + +#[cfg(not(unix))] +fn chown_inner(_: &Path, _: Option<Uid>, _: Option<Gid>) -> Result<()> { + panic!("chown() is not available on non-Unix systems!"); +} |