From de8fff0fcadbe8d98b9b5a953d51c7b4c5d5b870 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 19 Oct 2022 21:42:09 +0000 Subject: [PATCH 01/33] added basic dac mode --- src/entry.rs | 3 +++ src/index.rs | 11 +++++++++ src/lib.rs | 1 + src/live_tree.rs | 8 ++++++ src/permissions.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++ src/stitch.rs | 6 ++++- 6 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/permissions.rs diff --git a/src/entry.rs b/src/entry.rs index 71162ed3..cbd4880f 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -17,6 +17,7 @@ use std::fmt::Debug; use crate::kind::Kind; +use crate::permissions::Permissions; use crate::unix_time::UnixTime; use crate::*; @@ -26,6 +27,7 @@ pub trait Entry: Debug + Eq + PartialEq { fn mtime(&self) -> UnixTime; fn size(&self) -> Option; fn symlink_target(&self) -> &Option; + fn dac(&self) -> Permissions; /// True if the metadata supports an assumption the file contents have /// not changed. @@ -33,5 +35,6 @@ pub trait Entry: Debug + Eq + PartialEq { basis_entry.kind() == self.kind() && basis_entry.mtime() == self.mtime() && basis_entry.size() == self.size() + && basis_entry.dac() == self.dac() } } diff --git a/src/index.rs b/src/index.rs index f9cbb94c..5dac0640 100644 --- a/src/index.rs +++ b/src/index.rs @@ -22,6 +22,7 @@ use std::vec; use crate::compress::snappy::{Compressor, Decompressor}; use crate::kind::Kind; +use crate::permissions::Permissions; use crate::stats::{IndexReadStats, IndexWriterStats}; use crate::transport::local::LocalTransport; use crate::transport::Transport; @@ -49,6 +50,9 @@ pub struct IndexEntry { #[serde(default)] pub mtime: i64, + /// Discretionary Access Control permissions (such as read/write/execute on unix) + pub dac: Permissions, + /// Fractional nanoseconds for modification time. /// /// This is zero in indexes written prior to 0.6.2, but treating it as @@ -103,6 +107,10 @@ impl Entry for IndexEntry { fn symlink_target(&self) -> &Option { &self.target } + + fn dac(&self) -> Permissions { + self.dac + } } impl IndexEntry { @@ -120,6 +128,7 @@ impl IndexEntry { target: source.symlink_target().clone(), mtime: mtime.secs, mtime_nanos: mtime.nanosecs, + dac: source.dac(), } } } @@ -511,6 +520,7 @@ mod tests { kind: Kind::File, addrs: vec![], target: None, + dac: Permissions::default(), } } @@ -523,6 +533,7 @@ mod tests { kind: Kind::File, addrs: vec![], target: None, + dac: Permissions::default(), }]; let index_json = serde_json::to_string(&entries).unwrap(); println!("{}", index_json); diff --git a/src/lib.rs b/src/lib.rs index 47610c9e..d3c9c1e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ pub mod kind; pub mod live_tree; mod merge; pub(crate) mod misc; +mod permissions; pub mod restore; pub mod show; pub mod stats; diff --git a/src/live_tree.rs b/src/live_tree.rs index dd4d3224..a18563fd 100644 --- a/src/live_tree.rs +++ b/src/live_tree.rs @@ -18,6 +18,7 @@ use std::fs; use std::io::ErrorKind; use std::path::{Path, PathBuf}; +use crate::permissions::Permissions; use crate::stats::LiveTreeIterStats; use crate::unix_time::UnixTime; use crate::*; @@ -55,6 +56,7 @@ pub struct LiveEntry { mtime: UnixTime, size: Option, symlink_target: Option, + dac: Permissions, } impl tree::ReadTree for LiveTree { @@ -102,6 +104,10 @@ impl Entry for LiveEntry { fn symlink_target(&self) -> &Option { &self.symlink_target } + + fn dac(&self) -> Permissions { + self.dac + } } impl LiveEntry { @@ -120,12 +126,14 @@ impl LiveEntry { } else { None }; + let dac = Permissions::from(metadata.permissions()); LiveEntry { apath, kind: metadata.file_type().into(), mtime, symlink_target, size, + dac, } } } diff --git a/src/permissions.rs b/src/permissions.rs new file mode 100644 index 00000000..e5da84f7 --- /dev/null +++ b/src/permissions.rs @@ -0,0 +1,61 @@ +// Copyright 2022 Stephanie Aelmore. +// Conserve backup system. +// Copyright 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Martin Pool. + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +//! Discretionary Access Control permissions for archived files. +//! +//! On unix systems, the mode has 9 significant bits, divided into three classes, +//! owner class, group class, and others class. The owner class permissions +//! apply only to the owner, the group class permissions apply to members of the user's group, +//! and the others class permissions apply to all other users. +//! +//! For each class, there are 3 bitflags: read, write, and execute. This is typically +//! written as an octal number, such as 0o664, which means the user and group can +//! both read and write, and other users can only read. +//! +//! The mode is also often presented as a string of characters, such as "rw-rw-r--", +//! where each character represents one bit. +//! +//! On windows systems, files can be either read-only or writeable. For cross-compatibility, +//! the mode is always stored using the unix format, where the read-only state is stored +//! using the write bit in the user class. +//! +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct Permissions { + mode: u32, +} +impl Default for Permissions { + fn default() -> Self { + Self { mode: 0o664 } + } +} +#[cfg(not(unix))] +impl From for Permissions { + fn from(p: std::fs::Permissions) -> Self { + Self { + mode: match p.readonly() { + true => 0o444, + false => 0o664, + }, + } + } +} +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +impl From for Permissions { + fn from(p: std::fs::Permissions) -> Self { + Self { mode: p.mode() } + } +} diff --git a/src/stitch.rs b/src/stitch.rs index 78bec76f..9a786eb0 100644 --- a/src/stitch.rs +++ b/src/stitch.rs @@ -143,7 +143,10 @@ fn previous_existing_band(archive: &Archive, band_id: &BandId) -> Option #[cfg(test)] mod test { use super::*; - use crate::test_fixtures::{ScratchArchive, TreeFixture}; + use crate::{ + permissions::Permissions, + test_fixtures::{ScratchArchive, TreeFixture}, + }; fn symlink(name: &str, target: &str) -> IndexEntry { IndexEntry { @@ -153,6 +156,7 @@ mod test { mtime: 0, mtime_nanos: 0, addrs: Vec::new(), + dac: Permissions::default(), } } From cc2848f9f6ccee705014946039e82577b4d3c51d Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 19 Oct 2022 21:47:36 +0000 Subject: [PATCH 02/33] compressed index size in basic test increased to 90 --- src/index.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.rs b/src/index.rs index 5dac0640..33971cdd 100644 --- a/src/index.rs +++ b/src/index.rs @@ -541,7 +541,8 @@ mod tests { index_json, "[{\"apath\":\"/a/b\",\ \"kind\":\"File\",\ - \"mtime\":1461736377}]" + \"mtime\":1461736377 + \"dac\":{\"mode\":436}}]" ); } @@ -596,7 +597,7 @@ mod tests { assert_eq!(stats.index_hunks, 1); assert!(stats.compressed_index_bytes > 30); assert!( - stats.compressed_index_bytes < 70, + stats.compressed_index_bytes < 90, "expected shorter compressed index: {}", stats.compressed_index_bytes ); From baa738cf4e0221d03c03d635c4c5c145f9841f17 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 19 Oct 2022 21:49:19 +0000 Subject: [PATCH 03/33] index tests pass --- src/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index 33971cdd..abd12cd7 100644 --- a/src/index.rs +++ b/src/index.rs @@ -541,7 +541,7 @@ mod tests { index_json, "[{\"apath\":\"/a/b\",\ \"kind\":\"File\",\ - \"mtime\":1461736377 + \"mtime\":1461736377,\ \"dac\":{\"mode\":436}}]" ); } From a586b59c003ab22b90c268a9469804a688934a4a Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Thu, 20 Oct 2022 00:31:55 +0000 Subject: [PATCH 04/33] added permissions to restore module --- src/permissions.rs | 17 +++++++++++++++++ src/restore.rs | 15 +++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/permissions.rs b/src/permissions.rs index e5da84f7..d9bd974b 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -45,6 +45,8 @@ impl Default for Permissions { impl From for Permissions { fn from(p: std::fs::Permissions) -> Self { Self { + // set the user class write bit based on readonly status + // the rest of the bits are left in the default state mode: match p.readonly() { true => 0o444, false => 0o664, @@ -52,6 +54,15 @@ impl From for Permissions { } } } +#[cfg(windows)] +impl Into for Permissions { + fn into(self) -> std::fs::Permissions { + // TODO: Actually implement the windows compatibility + // basically we just need to extract the readonly bit from the mode, + // but I can't figure out how to instantiate + std::fs::Permissions::from(std::sys::windows::fs_imp::FilePermissions::new(self.readonly)) + } +} #[cfg(unix)] use std::os::unix::fs::PermissionsExt; impl From for Permissions { @@ -59,3 +70,9 @@ impl From for Permissions { Self { mode: p.mode() } } } +#[cfg(unix)] +impl From for std::fs::Permissions { + fn from(p: Permissions) -> Self { + std::fs::Permissions::from_mode(p.mode) + } +} diff --git a/src/restore.rs b/src/restore.rs index 34dbdd78..67469942 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -23,6 +23,7 @@ use filetime::{set_file_handle_times, set_symlink_file_times}; use crate::band::BandSelectionPolicy; use crate::entry::Entry; use crate::io::{directory_is_empty, ensure_dir_exists}; +use crate::permissions::Permissions; use crate::stats::RestoreStats; use crate::unix_time::UnixTime; use crate::*; @@ -148,6 +149,7 @@ pub fn restore( pub struct RestoreTree { path: PathBuf, + dir_permissions: Vec<(PathBuf, Permissions)>, dir_mtimes: Vec<(PathBuf, UnixTime)>, } @@ -156,6 +158,7 @@ impl RestoreTree { RestoreTree { path, dir_mtimes: Vec::new(), + dir_permissions: Vec::new(), } } @@ -182,6 +185,11 @@ impl RestoreTree { } fn finish(self) -> Result { + for (path, dac) in self.dir_permissions { + if let Err(err) = fs::set_permissions(path, dac.into()) { + ui::problem(&format!("Failed to set directory permissions: {:?}", err)); + } + } for (path, time) in self.dir_mtimes { if let Err(err) = filetime::set_file_mtime(path, time.into()) { ui::problem(&format!("Failed to set directory mtime: {:?}", err)); @@ -197,7 +205,8 @@ impl RestoreTree { return Err(Error::Restore { path, source }); } } - self.dir_mtimes.push((path, entry.mtime())); + self.dir_mtimes.push((path.clone(), entry.mtime())); + self.dir_permissions.push((path, entry.dac())); Ok(()) } @@ -207,7 +216,6 @@ impl RestoreTree { source_entry: &R::Entry, from_tree: &R, ) -> Result { - // TODO: Restore permissions. let path = self.rooted_path(source_entry.apath()); let restore_err = |source| Error::Restore { path: path.clone(), @@ -226,6 +234,9 @@ impl RestoreTree { source, } })?; + // Restore permissions + let dac = source_entry.dac(); + fs::set_permissions(path, dac.into())?; // TODO: Accumulate more stats. Ok(RestoreStats { From 209813945280f9fe2e2212c1f97db1858ac12138 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Thu, 20 Oct 2022 00:41:15 +0000 Subject: [PATCH 05/33] print backed up mode for each file --- src/backup.rs | 2 +- src/permissions.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backup.rs b/src/backup.rs index 0274e866..ce418a3a 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -117,7 +117,7 @@ pub fn backup( } Ok(Some(diff_kind)) => { if options.print_filenames && diff_kind != DiffKind::Unchanged { - writeln!(view, "{} {}", diff_kind.as_sigil(), entry.apath())?; + writeln!(view, "{} {} {:o}", diff_kind.as_sigil(), entry.apath(), entry.dac().mode)?; } view.update(|model| match diff_kind { DiffKind::Changed => model.entries_changed += 1, diff --git a/src/permissions.rs b/src/permissions.rs index d9bd974b..eb8d0c2a 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -34,7 +34,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Permissions { - mode: u32, + pub mode: u32, } impl Default for Permissions { fn default() -> Self { From 9908da40f96c50ffbb788b3d02cbad0b89dd52ef Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Thu, 20 Oct 2022 00:49:09 +0000 Subject: [PATCH 06/33] added proper octal formatting --- src/backup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backup.rs b/src/backup.rs index ce418a3a..d7dc756a 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -117,7 +117,7 @@ pub fn backup( } Ok(Some(diff_kind)) => { if options.print_filenames && diff_kind != DiffKind::Unchanged { - writeln!(view, "{} {} {:o}", diff_kind.as_sigil(), entry.apath(), entry.dac().mode)?; + writeln!(view, "{} {} 0o{:o}", diff_kind.as_sigil(), entry.apath(), entry.dac().mode)?; } view.update(|model| match diff_kind { DiffKind::Changed => model.entries_changed += 1, From 1180a02614ff7b49bf83197de938234dc642ef36 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Thu, 20 Oct 2022 00:56:18 +0000 Subject: [PATCH 07/33] added mode display to restore --- src/restore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/restore.rs b/src/restore.rs index 67469942..d650de35 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -105,7 +105,7 @@ pub fn restore( )?; for entry in entry_iter { if options.print_filenames { - progress_bar.message(&format!("{}\n", entry.apath())); + progress_bar.message(&format!("{} 0o{:o}\n", entry.apath(), entry.dac().mode)); } progress_bar.update(|model| model.filename = entry.apath().to_string()); if let Err(e) = match entry.kind() { From 74d12d15ae779608848739a121f706a891f06e01 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Thu, 20 Oct 2022 01:07:41 +0000 Subject: [PATCH 08/33] added dac to test so it can pass --- tests/api/live_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api/live_tree.rs b/tests/api/live_tree.rs index f419a58f..85c08563 100644 --- a/tests/api/live_tree.rs +++ b/tests/api/live_tree.rs @@ -53,7 +53,7 @@ fn list_simple_directory() { ); let repr = format!("{:?}", &result[6]); - let re = Regex::new(r#"LiveEntry \{ apath: Apath\("/jam/apricot"\), kind: File, mtime: UnixTime \{ [^)]* \}, size: Some\(8\), symlink_target: None \}"#).unwrap(); + let re = Regex::new(r#"LiveEntry \{ apath: Apath\("/jam/apricot"\), kind: File, mtime: UnixTime \{ [^)]* \}, size: Some\(8\), symlink_target: None, dac: Permissions \{ mode: [0-9]+ \} \}"#).unwrap(); assert!(re.is_match(&repr)); // TODO: Somehow get the stats out of the iterator. From 4766c2eb79543824a9dcbd29bff3f309ae664339 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Thu, 20 Oct 2022 01:39:18 +0000 Subject: [PATCH 09/33] made tests pass --- src/index.rs | 5 +++-- src/permissions.rs | 6 +++--- tests/cli/backup.rs | 2 +- tests/cli/exclude.rs | 4 ++-- tests/cli/main.rs | 8 ++++---- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/index.rs b/src/index.rs index abd12cd7..b0d20e7e 100644 --- a/src/index.rs +++ b/src/index.rs @@ -51,6 +51,7 @@ pub struct IndexEntry { pub mtime: i64, /// Discretionary Access Control permissions (such as read/write/execute on unix) + #[serde(default)] pub dac: Permissions, /// Fractional nanoseconds for modification time. @@ -542,7 +543,7 @@ mod tests { "[{\"apath\":\"/a/b\",\ \"kind\":\"File\",\ \"mtime\":1461736377,\ - \"dac\":{\"mode\":436}}]" + \"dac\":{\"mode\":33279}}]" ); } @@ -597,7 +598,7 @@ mod tests { assert_eq!(stats.index_hunks, 1); assert!(stats.compressed_index_bytes > 30); assert!( - stats.compressed_index_bytes < 90, + stats.compressed_index_bytes <= 90, "expected shorter compressed index: {}", stats.compressed_index_bytes ); diff --git a/src/permissions.rs b/src/permissions.rs index eb8d0c2a..964c8ca0 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -38,7 +38,7 @@ pub struct Permissions { } impl Default for Permissions { fn default() -> Self { - Self { mode: 0o664 } + Self { mode: 0o100777 } } } #[cfg(not(unix))] @@ -48,8 +48,8 @@ impl From for Permissions { // set the user class write bit based on readonly status // the rest of the bits are left in the default state mode: match p.readonly() { - true => 0o444, - false => 0o664, + true => 0o100444, + false => 0o100664, }, } } diff --git a/tests/cli/backup.rs b/tests/cli/backup.rs index 0b70cd43..713bcf51 100644 --- a/tests/cli/backup.rs +++ b/tests/cli/backup.rs @@ -31,5 +31,5 @@ fn backup_verbose() { .arg(src.path()) .assert() .success() - .stdout("+ /subdir/a\n+ /subdir/b\n"); + .stdout("+ /subdir/a 0o100644\n+ /subdir/b 0o100644\n"); } diff --git a/tests/cli/exclude.rs b/tests/cli/exclude.rs index 1cd14370..381bb5c1 100644 --- a/tests/cli/exclude.rs +++ b/tests/cli/exclude.rs @@ -54,7 +54,7 @@ fn exclude_simple_glob() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ /src/hello.c\n") + .stdout("+ /src/hello.c 0o100644\n") .success(); run_conserve() @@ -80,7 +80,7 @@ fn exclude_glob_only_in_root() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ /src/hello.c\n+ /src/hello.o\n") + .stdout("+ /src/hello.c 0o100644\n+ /src/hello.o 0o100644\n") .success(); run_conserve() diff --git a/tests/cli/main.rs b/tests/cli/main.rs index f3095c1f..0bd960d4 100644 --- a/tests/cli/main.rs +++ b/tests/cli/main.rs @@ -220,10 +220,10 @@ fn basic_backup() { .success() .stderr(predicate::str::is_empty()) .stdout(predicate::str::starts_with( - "/\n\ - /hello\n\ - /subdir\n\ - /subdir/subfile\n\ + "/ 0o40775\n\ + /hello 0o100664\n\ + /subdir 0o40775\n\ + /subdir/subfile 0o100664\n\ Restore complete.\n", )); From f1560337fccbd76825f777113cedfed7e18da8ec Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Thu, 20 Oct 2022 02:03:32 +0000 Subject: [PATCH 10/33] restructured file --- src/permissions.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/permissions.rs b/src/permissions.rs index 964c8ca0..5c4237d9 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -29,6 +29,7 @@ //! On windows systems, files can be either read-only or writeable. For cross-compatibility, //! the mode is always stored using the unix format, where the read-only state is stored //! using the write bit in the user class. +//! TODO: Implement windows compatibility. //! use serde::{Deserialize, Serialize}; @@ -38,15 +39,30 @@ pub struct Permissions { } impl Default for Permissions { fn default() -> Self { + // created with full permission so that restoring old archives works properly + // might want to rework the tests so that this isn't necessary Self { mode: 0o100777 } } } +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +impl From for Permissions { + fn from(p: std::fs::Permissions) -> Self { + Self { mode: p.mode() } + } +} +impl From for std::fs::Permissions { + fn from(p: Permissions) -> Self { + std::fs::Permissions::from_mode(p.mode) + } +} #[cfg(not(unix))] impl From for Permissions { fn from(p: std::fs::Permissions) -> Self { Self { // set the user class write bit based on readonly status // the rest of the bits are left in the default state + // TODO: fix this and test on windows mode: match p.readonly() { true => 0o100444, false => 0o100664, @@ -63,16 +79,3 @@ impl Into for Permissions { std::fs::Permissions::from(std::sys::windows::fs_imp::FilePermissions::new(self.readonly)) } } -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; -impl From for Permissions { - fn from(p: std::fs::Permissions) -> Self { - Self { mode: p.mode() } - } -} -#[cfg(unix)] -impl From for std::fs::Permissions { - fn from(p: Permissions) -> Self { - std::fs::Permissions::from_mode(p.mode) - } -} From 34a6b66eebf0f760f3a30b5ca6e24036cca1938d Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Tue, 8 Nov 2022 12:50:40 -1000 Subject: [PATCH 11/33] renamed Permissions to UnixMode and reformatted mode display --- src/backup.rs | 8 +++- src/entry.rs | 6 +-- src/index.rs | 16 +++---- src/lib.rs | 2 +- src/live_tree.rs | 12 ++--- src/restore.rs | 16 +++---- src/stitch.rs | 4 +- src/{permissions.rs => unix_mode.rs} | 69 +++++++++++++++++++++------- tests/api/live_tree.rs | 2 +- tests/cli/backup.rs | 2 +- tests/cli/exclude.rs | 4 +- 11 files changed, 91 insertions(+), 50 deletions(-) rename src/{permissions.rs => unix_mode.rs} (57%) diff --git a/src/backup.rs b/src/backup.rs index d7dc756a..2ae43187 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -117,7 +117,13 @@ pub fn backup( } Ok(Some(diff_kind)) => { if options.print_filenames && diff_kind != DiffKind::Unchanged { - writeln!(view, "{} {} 0o{:o}", diff_kind.as_sigil(), entry.apath(), entry.dac().mode)?; + writeln!( + view, + "{} {} {}", + diff_kind.as_sigil(), + entry.umode(), + entry.apath() + )?; } view.update(|model| match diff_kind { DiffKind::Changed => model.entries_changed += 1, diff --git a/src/entry.rs b/src/entry.rs index cbd4880f..17355a64 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -17,7 +17,7 @@ use std::fmt::Debug; use crate::kind::Kind; -use crate::permissions::Permissions; +use crate::unix_mode::UnixMode; use crate::unix_time::UnixTime; use crate::*; @@ -27,7 +27,7 @@ pub trait Entry: Debug + Eq + PartialEq { fn mtime(&self) -> UnixTime; fn size(&self) -> Option; fn symlink_target(&self) -> &Option; - fn dac(&self) -> Permissions; + fn umode(&self) -> UnixMode; /// True if the metadata supports an assumption the file contents have /// not changed. @@ -35,6 +35,6 @@ pub trait Entry: Debug + Eq + PartialEq { basis_entry.kind() == self.kind() && basis_entry.mtime() == self.mtime() && basis_entry.size() == self.size() - && basis_entry.dac() == self.dac() + && basis_entry.umode() == self.umode() } } diff --git a/src/index.rs b/src/index.rs index b0d20e7e..29318e09 100644 --- a/src/index.rs +++ b/src/index.rs @@ -22,7 +22,7 @@ use std::vec; use crate::compress::snappy::{Compressor, Decompressor}; use crate::kind::Kind; -use crate::permissions::Permissions; +use crate::unix_mode::UnixMode; use crate::stats::{IndexReadStats, IndexWriterStats}; use crate::transport::local::LocalTransport; use crate::transport::Transport; @@ -52,7 +52,7 @@ pub struct IndexEntry { /// Discretionary Access Control permissions (such as read/write/execute on unix) #[serde(default)] - pub dac: Permissions, + pub umode: UnixMode, /// Fractional nanoseconds for modification time. /// @@ -109,8 +109,8 @@ impl Entry for IndexEntry { &self.target } - fn dac(&self) -> Permissions { - self.dac + fn umode(&self) -> UnixMode { + self.umode } } @@ -129,7 +129,7 @@ impl IndexEntry { target: source.symlink_target().clone(), mtime: mtime.secs, mtime_nanos: mtime.nanosecs, - dac: source.dac(), + umode: source.umode(), } } } @@ -521,7 +521,7 @@ mod tests { kind: Kind::File, addrs: vec![], target: None, - dac: Permissions::default(), + umode: UnixMode::default(), } } @@ -534,7 +534,7 @@ mod tests { kind: Kind::File, addrs: vec![], target: None, - dac: Permissions::default(), + umode: UnixMode::default(), }]; let index_json = serde_json::to_string(&entries).unwrap(); println!("{}", index_json); @@ -543,7 +543,7 @@ mod tests { "[{\"apath\":\"/a/b\",\ \"kind\":\"File\",\ \"mtime\":1461736377,\ - \"dac\":{\"mode\":33279}}]" + \"umode\":{\"mode\":33279}}]" ); } diff --git a/src/lib.rs b/src/lib.rs index d3c9c1e2..d607dda6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,6 @@ pub mod kind; pub mod live_tree; mod merge; pub(crate) mod misc; -mod permissions; pub mod restore; pub mod show; pub mod stats; @@ -44,6 +43,7 @@ pub mod test_fixtures; pub mod transport; mod tree; pub mod ui; +mod unix_mode; pub mod unix_time; mod validate; diff --git a/src/live_tree.rs b/src/live_tree.rs index a18563fd..e802ed4e 100644 --- a/src/live_tree.rs +++ b/src/live_tree.rs @@ -18,7 +18,7 @@ use std::fs; use std::io::ErrorKind; use std::path::{Path, PathBuf}; -use crate::permissions::Permissions; +use crate::unix_mode::UnixMode; use crate::stats::LiveTreeIterStats; use crate::unix_time::UnixTime; use crate::*; @@ -56,7 +56,7 @@ pub struct LiveEntry { mtime: UnixTime, size: Option, symlink_target: Option, - dac: Permissions, + umode: UnixMode, } impl tree::ReadTree for LiveTree { @@ -105,8 +105,8 @@ impl Entry for LiveEntry { &self.symlink_target } - fn dac(&self) -> Permissions { - self.dac + fn umode(&self) -> UnixMode { + self.umode } } @@ -126,14 +126,14 @@ impl LiveEntry { } else { None }; - let dac = Permissions::from(metadata.permissions()); + let umode = UnixMode::from(metadata.permissions()); LiveEntry { apath, kind: metadata.file_type().into(), mtime, symlink_target, size, - dac, + umode, } } } diff --git a/src/restore.rs b/src/restore.rs index d650de35..7556f074 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -23,7 +23,7 @@ use filetime::{set_file_handle_times, set_symlink_file_times}; use crate::band::BandSelectionPolicy; use crate::entry::Entry; use crate::io::{directory_is_empty, ensure_dir_exists}; -use crate::permissions::Permissions; +use crate::unix_mode::UnixMode; use crate::stats::RestoreStats; use crate::unix_time::UnixTime; use crate::*; @@ -105,7 +105,7 @@ pub fn restore( )?; for entry in entry_iter { if options.print_filenames { - progress_bar.message(&format!("{} 0o{:o}\n", entry.apath(), entry.dac().mode)); + progress_bar.message(&format!("{} 0o{:o}\n", entry.apath(), entry.umode().mode)); } progress_bar.update(|model| model.filename = entry.apath().to_string()); if let Err(e) = match entry.kind() { @@ -149,7 +149,7 @@ pub fn restore( pub struct RestoreTree { path: PathBuf, - dir_permissions: Vec<(PathBuf, Permissions)>, + dir_umodes: Vec<(PathBuf, UnixMode)>, dir_mtimes: Vec<(PathBuf, UnixTime)>, } @@ -158,7 +158,7 @@ impl RestoreTree { RestoreTree { path, dir_mtimes: Vec::new(), - dir_permissions: Vec::new(), + dir_umodes: Vec::new(), } } @@ -185,8 +185,8 @@ impl RestoreTree { } fn finish(self) -> Result { - for (path, dac) in self.dir_permissions { - if let Err(err) = fs::set_permissions(path, dac.into()) { + for (path, umode) in self.dir_umodes { + if let Err(err) = fs::set_permissions(path, umode.into()) { ui::problem(&format!("Failed to set directory permissions: {:?}", err)); } } @@ -206,7 +206,7 @@ impl RestoreTree { } } self.dir_mtimes.push((path.clone(), entry.mtime())); - self.dir_permissions.push((path, entry.dac())); + self.dir_umodes.push((path, entry.umode())); Ok(()) } @@ -235,7 +235,7 @@ impl RestoreTree { } })?; // Restore permissions - let dac = source_entry.dac(); + let dac = source_entry.umode(); fs::set_permissions(path, dac.into())?; // TODO: Accumulate more stats. diff --git a/src/stitch.rs b/src/stitch.rs index 9a786eb0..5bf14051 100644 --- a/src/stitch.rs +++ b/src/stitch.rs @@ -144,7 +144,7 @@ fn previous_existing_band(archive: &Archive, band_id: &BandId) -> Option mod test { use super::*; use crate::{ - permissions::Permissions, + unix_mode::UnixMode, test_fixtures::{ScratchArchive, TreeFixture}, }; @@ -156,7 +156,7 @@ mod test { mtime: 0, mtime_nanos: 0, addrs: Vec::new(), - dac: Permissions::default(), + umode: UnixMode::default(), } } diff --git a/src/permissions.rs b/src/unix_mode.rs similarity index 57% rename from src/permissions.rs rename to src/unix_mode.rs index 5c4237d9..a0c94ef9 100644 --- a/src/permissions.rs +++ b/src/unix_mode.rs @@ -29,36 +29,60 @@ //! On windows systems, files can be either read-only or writeable. For cross-compatibility, //! the mode is always stored using the unix format, where the read-only state is stored //! using the write bit in the user class. -//! TODO: Implement windows compatibility. +//! TODO: Properly implement and test Windows compatibility. +//! TODO: Implement the sticky bit, SUID, SGID //! use serde::{Deserialize, Serialize}; +use std::{fs::Permissions, fmt}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Permissions { +pub struct UnixMode { pub mode: u32, } -impl Default for Permissions { +impl Default for UnixMode { fn default() -> Self { // created with full permission so that restoring old archives works properly - // might want to rework the tests so that this isn't necessary + // TODO: might want to rework the tests so that this isn't necessary Self { mode: 0o100777 } } } +impl fmt::Display for UnixMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let owner = (self.mode & 0o700)>>6; + let group = (self.mode & 0o070)>>3; + let all = self.mode & 0o007; + + write!(f, "-")?; + let display_mode = &mut |bits: u32| -> fmt::Result { + write!(f, "{}", if (bits & 0b100) > 0 {'r'} else {'-'})?; + write!(f, "{}", if (bits & 0b010) > 0 {'w'} else {'-'})?; + write!(f, "{}", if (bits & 0b001) > 0 {'x'} else {'-'}) + }; + display_mode(owner)?; + display_mode(group)?; + display_mode(all) + } +} #[cfg(unix)] use std::os::unix::fs::PermissionsExt; -impl From for Permissions { - fn from(p: std::fs::Permissions) -> Self { + +impl From for UnixMode { + fn from(p: Permissions) -> Self { Self { mode: p.mode() } } +}impl From for UnixMode { + fn from(mode: u32) -> Self { + Self { mode } + } } -impl From for std::fs::Permissions { - fn from(p: Permissions) -> Self { - std::fs::Permissions::from_mode(p.mode) +impl From for Permissions { + fn from(p: UnixMode) -> Self { + Permissions::from_mode(p.mode) } } #[cfg(not(unix))] -impl From for Permissions { - fn from(p: std::fs::Permissions) -> Self { +impl From for UnixMode { + fn from(p: Permissions) -> Self { Self { // set the user class write bit based on readonly status // the rest of the bits are left in the default state @@ -71,11 +95,22 @@ impl From for Permissions { } } #[cfg(windows)] -impl Into for Permissions { - fn into(self) -> std::fs::Permissions { - // TODO: Actually implement the windows compatibility - // basically we just need to extract the readonly bit from the mode, - // but I can't figure out how to instantiate - std::fs::Permissions::from(std::sys::windows::fs_imp::FilePermissions::new(self.readonly)) +use std::sys::windows::fs_imp::FilePermissions; +#[cfg(windows)] +impl Into for UnixMode { + fn into(self) -> Permissions { + Permissions::from(FilePermissions::new(self.readonly)) + } +} + +#[cfg(test)] +mod tests { + use crate::unix_mode::UnixMode; + #[test] + fn display_unix_modes() { + assert_eq!("-rwxrwxr--", format!("{}", UnixMode::from(0o774))); + assert_eq!("-rwxr-xr-x", format!("{}", UnixMode::from(0o755))); + assert_eq!("-rwxr---wx", format!("{}", UnixMode::from(0o743))); + assert_eq!("----r---wx", format!("{}", UnixMode::from(0o043))); } } diff --git a/tests/api/live_tree.rs b/tests/api/live_tree.rs index 85c08563..5d140a74 100644 --- a/tests/api/live_tree.rs +++ b/tests/api/live_tree.rs @@ -53,7 +53,7 @@ fn list_simple_directory() { ); let repr = format!("{:?}", &result[6]); - let re = Regex::new(r#"LiveEntry \{ apath: Apath\("/jam/apricot"\), kind: File, mtime: UnixTime \{ [^)]* \}, size: Some\(8\), symlink_target: None, dac: Permissions \{ mode: [0-9]+ \} \}"#).unwrap(); + let re = Regex::new(r#"LiveEntry \{ apath: Apath\("/jam/apricot"\), kind: File, mtime: UnixTime \{ [^)]* \}, size: Some\(8\), symlink_target: None, umode: UnixMode \{ mode: [0-9]+ \} \}"#).unwrap(); assert!(re.is_match(&repr)); // TODO: Somehow get the stats out of the iterator. diff --git a/tests/cli/backup.rs b/tests/cli/backup.rs index 713bcf51..a191a8b4 100644 --- a/tests/cli/backup.rs +++ b/tests/cli/backup.rs @@ -31,5 +31,5 @@ fn backup_verbose() { .arg(src.path()) .assert() .success() - .stdout("+ /subdir/a 0o100644\n+ /subdir/b 0o100644\n"); + .stdout("+ -rw-rw-r-- /subdir/a\n+ -rw-rw-r-- /subdir/b\n"); } diff --git a/tests/cli/exclude.rs b/tests/cli/exclude.rs index 381bb5c1..ed8ba244 100644 --- a/tests/cli/exclude.rs +++ b/tests/cli/exclude.rs @@ -54,7 +54,7 @@ fn exclude_simple_glob() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ /src/hello.c 0o100644\n") + .stdout("+ -rw-rw-r-- /src/hello.c\n") .success(); run_conserve() @@ -80,7 +80,7 @@ fn exclude_glob_only_in_root() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ /src/hello.c 0o100644\n+ /src/hello.o 0o100644\n") + .stdout("+ -rw-rw-r-- /src/hello.c\n+ -rw-rw-r-- /src/hello.o\n") .success(); run_conserve() From 9fbbf54dc199e1a9350a21de0c48cb7729526a8c Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Tue, 8 Nov 2022 13:20:44 -1000 Subject: [PATCH 12/33] added SUID, SGID, and sticky --- src/unix_mode.rs | 65 ++++++++++++++++++++++++++++++++------------ tests/cli/backup.rs | 2 +- tests/cli/exclude.rs | 4 +-- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index a0c94ef9..29c95fb0 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -33,7 +33,7 @@ //! TODO: Implement the sticky bit, SUID, SGID //! use serde::{Deserialize, Serialize}; -use std::{fs::Permissions, fmt}; +use std::{fmt, fs::Permissions}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct UnixMode { @@ -48,19 +48,41 @@ impl Default for UnixMode { } impl fmt::Display for UnixMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let owner = (self.mode & 0o700)>>6; - let group = (self.mode & 0o070)>>3; - let all = self.mode & 0o007; + let sss = (self.mode & 0o7000) >> 9; + let owner = (self.mode & 0o0700) >> 6; + let group = (self.mode & 0o0070) >> 3; + let other = self.mode & 0o0007; - write!(f, "-")?; - let display_mode = &mut |bits: u32| -> fmt::Result { - write!(f, "{}", if (bits & 0b100) > 0 {'r'} else {'-'})?; - write!(f, "{}", if (bits & 0b010) > 0 {'w'} else {'-'})?; - write!(f, "{}", if (bits & 0b001) > 0 {'x'} else {'-'}) - }; - display_mode(owner)?; - display_mode(group)?; - display_mode(all) + // write!(f, "-")?; // describes type of entry - d for dir, l for link, etc. + + // owner permissions + write!(f, "{}", if owner & 0b100 > 0 { 'r' } else { '-' })?; + write!(f, "{}", if owner & 0b010 > 0 { 'w' } else { '-' })?; + if sss == 0b100 {// Set UID + write!(f, "{}", if owner & 0b001 > 0 { 's' } else { 'S' })?; + } else { + write!(f, "{}", if owner & 0b001 > 0 { 'x' } else { '-' })?; + } + + // group permissions + write!(f, "{}", if group & 0b100 > 0 { 'r' } else { '-' })?; + write!(f, "{}", if group & 0b010 > 0 { 'w' } else { '-' })?; + if sss == 0b010 {// Set GID + write!(f, "{}", if group & 0b001 > 0 { 's' } else { 'S' })?; + } else { + write!(f, "{}", if group & 0b001 > 0 { 'x' } else { '-' })?; + } + + // other permissions + write!(f, "{}", if other & 0b100 > 0 { 'r' } else { '-' })?; + write!(f, "{}", if other & 0b010 > 0 { 'w' } else { '-' })?; + if sss == 0b001 {// sticky + write!(f, "{}", if other & 0b001 > 0 { 't' } else { 'T' })?; + } else { + write!(f, "{}", if other & 0b001 > 0 { 'x' } else { '-' })?; + } + + Ok(()) } } #[cfg(unix)] @@ -70,7 +92,8 @@ impl From for UnixMode { fn from(p: Permissions) -> Self { Self { mode: p.mode() } } -}impl From for UnixMode { +} +impl From for UnixMode { fn from(mode: u32) -> Self { Self { mode } } @@ -108,9 +131,15 @@ mod tests { use crate::unix_mode::UnixMode; #[test] fn display_unix_modes() { - assert_eq!("-rwxrwxr--", format!("{}", UnixMode::from(0o774))); - assert_eq!("-rwxr-xr-x", format!("{}", UnixMode::from(0o755))); - assert_eq!("-rwxr---wx", format!("{}", UnixMode::from(0o743))); - assert_eq!("----r---wx", format!("{}", UnixMode::from(0o043))); + assert_eq!("rwxrwxr--", format!("{}", UnixMode::from(0o774))); + assert_eq!("rwxr-xr-x", format!("{}", UnixMode::from(0o755))); + assert_eq!("rwxr---wx", format!("{}", UnixMode::from(0o743))); + assert_eq!("---r---wx", format!("{}", UnixMode::from(0o043))); + assert_eq!("rwsr-xr-x", format!("{}", UnixMode::from(0o4755))); + assert_eq!("rwxr-sr-x", format!("{}", UnixMode::from(0o2755))); + assert_eq!("rwxr-xr-t", format!("{}", UnixMode::from(0o1755))); + assert_eq!("rwxrwxr-T", format!("{}", UnixMode::from(0o1774))); + assert_eq!("rwxr-S-wx", format!("{}", UnixMode::from(0o2743))); + assert_eq!("--Sr---wx", format!("{}", UnixMode::from(0o4043))); } } diff --git a/tests/cli/backup.rs b/tests/cli/backup.rs index a191a8b4..f398b6cc 100644 --- a/tests/cli/backup.rs +++ b/tests/cli/backup.rs @@ -31,5 +31,5 @@ fn backup_verbose() { .arg(src.path()) .assert() .success() - .stdout("+ -rw-rw-r-- /subdir/a\n+ -rw-rw-r-- /subdir/b\n"); + .stdout("+ rw-rw-r-- /subdir/a\n+ rw-rw-r-- /subdir/b\n"); } diff --git a/tests/cli/exclude.rs b/tests/cli/exclude.rs index ed8ba244..8c7e2cf9 100644 --- a/tests/cli/exclude.rs +++ b/tests/cli/exclude.rs @@ -54,7 +54,7 @@ fn exclude_simple_glob() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ -rw-rw-r-- /src/hello.c\n") + .stdout("+ rw-rw-r-- /src/hello.c\n") .success(); run_conserve() @@ -80,7 +80,7 @@ fn exclude_glob_only_in_root() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ -rw-rw-r-- /src/hello.c\n+ -rw-rw-r-- /src/hello.o\n") + .stdout("+ rw-rw-r-- /src/hello.c\n+ rw-rw-r-- /src/hello.o\n") .success(); run_conserve() From 451f5fd630534203ba3d1911cdcfa5a3e9f93137 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Tue, 8 Nov 2022 13:22:20 -1000 Subject: [PATCH 13/33] spelling correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ed01213..68c487f4 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ The current data format (called "0.6") will be readable by future releases for a Be aware Conserve is developed as a part-time non-commercial project and there's no guarantee of support or reliability. Bug reports are welcome but I cannot promise they will receive a resolution within any particular time frame. -As of October 2022 I am primarily spending my open-souce time on [cargo-mutants](https://github.com/sourcefrog/cargo-mutants). When that is feature complete, which is anticipated by early-mid 2023, I will likely come back to working more on Conserve. +As of October 2022 I am primarily spending my open-source time on [cargo-mutants](https://github.com/sourcefrog/cargo-mutants). When that is feature complete, which is anticipated by early-mid 2023, I will likely come back to working more on Conserve. There is still room for several performance improvements and features. See the [issue tracker][issues] for a list. From fa3bba20fd549c3b1b2ad8c9486a0f4f8a2a9317 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Tue, 8 Nov 2022 13:44:15 -1000 Subject: [PATCH 14/33] added unix mode to ls command --- src/restore.rs | 6 +++--- src/show.rs | 2 +- tests/cli/exclude.rs | 6 +++--- tests/cli/main.rs | 24 ++++++++++++------------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/restore.rs b/src/restore.rs index 7556f074..8b643f97 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -105,7 +105,7 @@ pub fn restore( )?; for entry in entry_iter { if options.print_filenames { - progress_bar.message(&format!("{} 0o{:o}\n", entry.apath(), entry.umode().mode)); + progress_bar.message(&format!("{} {}\n", entry.umode(), entry.apath())); } progress_bar.update(|model| model.filename = entry.apath().to_string()); if let Err(e) = match entry.kind() { @@ -235,8 +235,8 @@ impl RestoreTree { } })?; // Restore permissions - let dac = source_entry.umode(); - fs::set_permissions(path, dac.into())?; + let umode = source_entry.umode(); + fs::set_permissions(path, umode.into())?; // TODO: Accumulate more stats. Ok(RestoreStats { diff --git a/src/show.rs b/src/show.rs index 3bc3b3d5..c7d76bbd 100644 --- a/src/show.rs +++ b/src/show.rs @@ -120,7 +120,7 @@ pub fn show_index_json(band: &Band, w: &mut dyn Write) -> Result<()> { pub fn show_entry_names>(it: I, w: &mut dyn Write) -> Result<()> { let mut bw = BufWriter::new(w); for entry in it { - writeln!(bw, "{}", entry.apath())?; + writeln!(bw, "{} {}", entry.umode(), entry.apath())?; } Ok(()) } diff --git a/tests/cli/exclude.rs b/tests/cli/exclude.rs index 8c7e2cf9..9614485f 100644 --- a/tests/cli/exclude.rs +++ b/tests/cli/exclude.rs @@ -61,7 +61,7 @@ fn exclude_simple_glob() { .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("/\n/src\n/src/hello.c\n") + .stdout("rwxrwxr-x /\nrwxrwxr-x /src\nrw-rw-r-- /src/hello.c\n") .success(); } @@ -87,7 +87,7 @@ fn exclude_glob_only_in_root() { .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("/\n/src\n/src/hello.c\n/src/hello.o\n") + .stdout("rwxrwxr-x /\nrwxrwxr-x /src\nrw-rw-r-- /src/hello.c\nrw-rw-r-- /src/hello.o\n") .success(); } @@ -117,7 +117,7 @@ fn exclude_suffix_pattern() { .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("/\n/release\n/src\n/subproj\n/target\n/src/hello.rs\n/subproj/target\n") + .stdout("rwxrwxr-x /\nrwxrwxr-x /release\nrwxrwxr-x /src\nrwxrwxr-x /subproj\nrwxrwxr-x /target\nrw-rw-r-- /src/hello.rs\nrwxrwxr-x /subproj/target\n") .success(); } diff --git a/tests/cli/main.rs b/tests/cli/main.rs index 0bd960d4..ebd25abd 100644 --- a/tests/cli/main.rs +++ b/tests/cli/main.rs @@ -104,10 +104,10 @@ fn basic_backup() { .success() .stderr(predicate::str::is_empty()) .stdout( - "/\n\ - /hello\n\ - /subdir\n\ - /subdir/subfile\n", + "rwxrwxr-x /\n\ + rw-rw-r-- /hello\n\ + rwxrwxr-x /subdir\n\ + rw-rw-r-- /subdir/subfile\n", ); run_conserve() @@ -199,10 +199,10 @@ fn basic_backup() { .success() .stderr(predicate::str::is_empty()) .stdout( - "/\n\ - /hello\n\ - /subdir\n\ - /subdir/subfile\n", + "rwxrwxr-x /\n\ + rw-rw-r-- /hello\n\ + rwxrwxr-x /subdir\n\ + rw-rw-r-- /subdir/subfile\n", ); // TODO: Factor out comparison to expected tree. @@ -220,10 +220,10 @@ fn basic_backup() { .success() .stderr(predicate::str::is_empty()) .stdout(predicate::str::starts_with( - "/ 0o40775\n\ - /hello 0o100664\n\ - /subdir 0o40775\n\ - /subdir/subfile 0o100664\n\ + "rwxrwxr-x /\n\ + rw-rw-r-- /hello\n\ + rwxrwxr-x /subdir\n\ + rw-rw-r-- /subdir/subfile\n\ Restore complete.\n", )); From b31ef7f5c99fe1a70ac049a84b153a3abebbfd1c Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Tue, 8 Nov 2022 13:57:52 -1000 Subject: [PATCH 15/33] changed default mode from 0o777 to 0o775 --- src/index.rs | 2 +- src/unix_mode.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.rs b/src/index.rs index 29318e09..e339520a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -543,7 +543,7 @@ mod tests { "[{\"apath\":\"/a/b\",\ \"kind\":\"File\",\ \"mtime\":1461736377,\ - \"umode\":{\"mode\":33279}}]" + \"umode\":{\"mode\":33277}}]" ); } diff --git a/src/unix_mode.rs b/src/unix_mode.rs index 29c95fb0..5909fb4f 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -43,7 +43,7 @@ impl Default for UnixMode { fn default() -> Self { // created with full permission so that restoring old archives works properly // TODO: might want to rework the tests so that this isn't necessary - Self { mode: 0o100777 } + Self { mode: 0o100775 } } } impl fmt::Display for UnixMode { From 01e6129dcd14f6b09ce3eeb6ef42bc9baac0fc00 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Tue, 8 Nov 2022 13:59:46 -1000 Subject: [PATCH 16/33] edited comments --- src/unix_mode.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index 5909fb4f..74bdf48b 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -30,7 +30,6 @@ //! the mode is always stored using the unix format, where the read-only state is stored //! using the write bit in the user class. //! TODO: Properly implement and test Windows compatibility. -//! TODO: Implement the sticky bit, SUID, SGID //! use serde::{Deserialize, Serialize}; use std::{fmt, fs::Permissions}; @@ -41,8 +40,8 @@ pub struct UnixMode { } impl Default for UnixMode { fn default() -> Self { - // created with full permission so that restoring old archives works properly - // TODO: might want to rework the tests so that this isn't necessary + // created with execute permission so that restoring old archives works properly + // TODO: ideally we would set this based on the inode type read from the archive Self { mode: 0o100775 } } } @@ -53,8 +52,6 @@ impl fmt::Display for UnixMode { let group = (self.mode & 0o0070) >> 3; let other = self.mode & 0o0007; - // write!(f, "-")?; // describes type of entry - d for dir, l for link, etc. - // owner permissions write!(f, "{}", if owner & 0b100 > 0 { 'r' } else { '-' })?; write!(f, "{}", if owner & 0b010 > 0 { 'w' } else { '-' })?; From eb5b07121d911a08adca9924480a4eb57e0895a1 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 00:30:58 +0000 Subject: [PATCH 17/33] fixed tests to work on DevContainer --- tests/cli/backup.rs | 2 +- tests/cli/exclude.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/cli/backup.rs b/tests/cli/backup.rs index f398b6cc..1e61d247 100644 --- a/tests/cli/backup.rs +++ b/tests/cli/backup.rs @@ -31,5 +31,5 @@ fn backup_verbose() { .arg(src.path()) .assert() .success() - .stdout("+ rw-rw-r-- /subdir/a\n+ rw-rw-r-- /subdir/b\n"); + .stdout("+ rw-r--r-- /subdir/a\n+ rw-r--r-- /subdir/b\n"); } diff --git a/tests/cli/exclude.rs b/tests/cli/exclude.rs index 9614485f..a188c8a4 100644 --- a/tests/cli/exclude.rs +++ b/tests/cli/exclude.rs @@ -54,14 +54,14 @@ fn exclude_simple_glob() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ rw-rw-r-- /src/hello.c\n") + .stdout("+ rw-r--r-- /src/hello.c\n") .success(); run_conserve() .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("rwxrwxr-x /\nrwxrwxr-x /src\nrw-rw-r-- /src/hello.c\n") + .stdout("rwxr-xr-x /\nrwxr-xr-x /src\nrw-r--r-- /src/hello.c\n") .success(); } @@ -80,14 +80,14 @@ fn exclude_glob_only_in_root() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ rw-rw-r-- /src/hello.c\n+ rw-rw-r-- /src/hello.o\n") + .stdout("+ rw-r--r-- /src/hello.c\n+ rw-r--r-- /src/hello.o\n") .success(); run_conserve() .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("rwxrwxr-x /\nrwxrwxr-x /src\nrw-rw-r-- /src/hello.c\nrw-rw-r-- /src/hello.o\n") + .stdout("rwxr-xr-x /\nrwxr-xr-x /src\nrw-r--r-- /src/hello.c\nrw-r--r-- /src/hello.o\n") .success(); } @@ -117,7 +117,7 @@ fn exclude_suffix_pattern() { .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("rwxrwxr-x /\nrwxrwxr-x /release\nrwxrwxr-x /src\nrwxrwxr-x /subproj\nrwxrwxr-x /target\nrw-rw-r-- /src/hello.rs\nrwxrwxr-x /subproj/target\n") + .stdout("rwxr-xr-x /\nrwxr-xr-x /release\nrwxr-xr-x /src\nrwxr-xr-x /subproj\nrwxr-xr-x /target\nrw-r--r-- /src/hello.rs\nrwxr-xr-x /subproj/target\n") .success(); } From 61ea5f0e88c80f18011b5f5c31f936b003b4624e Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 01:13:01 +0000 Subject: [PATCH 18/33] added long_listing option "-l" to backup, restore, and ls commands --- src/backup.rs | 22 +++++++++++++++------- src/bin/conserve.rs | 14 ++++++++++++++ src/index.rs | 2 +- src/live_tree.rs | 2 +- src/restore.rs | 11 +++++++++-- src/show.rs | 12 ++++++++++-- src/stitch.rs | 2 +- src/unix_mode.rs | 9 ++++++--- tests/cli/backup.rs | 2 +- tests/cli/exclude.rs | 10 +++++----- tests/cli/main.rs | 24 ++++++++++++------------ 11 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/backup.rs b/src/backup.rs index 2ae43187..cd4bc24b 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -35,6 +35,9 @@ pub struct BackupOptions { /// Exclude these globs from the backup. pub exclude: Exclude, + /// If printing filenames, include metadata such as file permissions + pub long_listing: bool, + pub max_entries_per_hunk: usize, } @@ -44,6 +47,7 @@ impl Default for BackupOptions { print_filenames: false, exclude: Exclude::nothing(), max_entries_per_hunk: crate::index::MAX_ENTRIES_PER_HUNK, + long_listing: false, } } } @@ -117,13 +121,17 @@ pub fn backup( } Ok(Some(diff_kind)) => { if options.print_filenames && diff_kind != DiffKind::Unchanged { - writeln!( - view, - "{} {} {}", - diff_kind.as_sigil(), - entry.umode(), - entry.apath() - )?; + if options.long_listing { + writeln!( + view, + "{} {} {}", + diff_kind.as_sigil(), + entry.umode(), + entry.apath() + )?; + } else { + writeln!(view, "{} {}", diff_kind.as_sigil(), entry.apath())?; + } } view.update(|model| match diff_kind { DiffKind::Changed => model.entries_changed += 1, diff --git a/src/bin/conserve.rs b/src/bin/conserve.rs index 596a7a7e..f7075419 100644 --- a/src/bin/conserve.rs +++ b/src/bin/conserve.rs @@ -61,6 +61,8 @@ enum Command { exclude_from: Vec, #[clap(long)] no_stats: bool, + #[clap(long, short = 'l')] + long_listing: bool, }, #[clap(subcommand)] @@ -134,6 +136,9 @@ enum Command { exclude: Vec, #[clap(long, short = 'E', number_of_values = 1)] exclude_from: Vec, + + #[clap(short = 'l')] + long_listing: bool, }, /// Copy a stored tree to a restore directory. @@ -154,6 +159,8 @@ enum Command { only_subtree: Option, #[clap(long)] no_stats: bool, + #[clap(long, short = 'l')] + long_listing: bool, }, /// Show the total size of files in a stored tree or source directory, with exclusions. @@ -259,12 +266,14 @@ impl Command { exclude, exclude_from, no_stats, + long_listing, } => { let exclude = ExcludeBuilder::from_args(exclude, exclude_from)?.build()?; let source = &LiveTree::open(source)?; let options = BackupOptions { print_filenames: *verbose, exclude, + long_listing: *long_listing, ..Default::default() }; let stats = backup(&Archive::open(open_transport(archive)?)?, source, &options)?; @@ -359,6 +368,7 @@ impl Command { stos, exclude, exclude_from, + long_listing, } => { let exclude = ExcludeBuilder::from_args(exclude, exclude_from)?.build()?; if let Some(archive) = &stos.archive { @@ -367,12 +377,14 @@ impl Command { stored_tree_from_opt(archive, &stos.backup)? .iter_entries(Apath::root(), exclude)?, &mut stdout, + *long_listing, )?; } else { show::show_entry_names( LiveTree::open(stos.source.clone().unwrap())? .iter_entries(Apath::root(), exclude)?, &mut stdout, + *long_listing, )?; } } @@ -386,6 +398,7 @@ impl Command { exclude_from, only_subtree, no_stats, + long_listing, } => { let band_selection = band_selection_policy_from_opt(backup); let archive = Archive::open(open_transport(archive)?)?; @@ -396,6 +409,7 @@ impl Command { only_subtree: only_subtree.clone(), band_selection, overwrite: *force_overwrite, + long_listing: *long_listing, }; let stats = restore(&archive, destination, &options)?; diff --git a/src/index.rs b/src/index.rs index e339520a..485c5bdd 100644 --- a/src/index.rs +++ b/src/index.rs @@ -22,10 +22,10 @@ use std::vec; use crate::compress::snappy::{Compressor, Decompressor}; use crate::kind::Kind; -use crate::unix_mode::UnixMode; use crate::stats::{IndexReadStats, IndexWriterStats}; use crate::transport::local::LocalTransport; use crate::transport::Transport; +use crate::unix_mode::UnixMode; use crate::unix_time::UnixTime; use crate::*; diff --git a/src/live_tree.rs b/src/live_tree.rs index e802ed4e..89ad9de6 100644 --- a/src/live_tree.rs +++ b/src/live_tree.rs @@ -18,8 +18,8 @@ use std::fs; use std::io::ErrorKind; use std::path::{Path, PathBuf}; -use crate::unix_mode::UnixMode; use crate::stats::LiveTreeIterStats; +use crate::unix_mode::UnixMode; use crate::unix_time::UnixTime; use crate::*; diff --git a/src/restore.rs b/src/restore.rs index 8b643f97..86be85f1 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -23,8 +23,8 @@ use filetime::{set_file_handle_times, set_symlink_file_times}; use crate::band::BandSelectionPolicy; use crate::entry::Entry; use crate::io::{directory_is_empty, ensure_dir_exists}; -use crate::unix_mode::UnixMode; use crate::stats::RestoreStats; +use crate::unix_mode::UnixMode; use crate::unix_time::UnixTime; use crate::*; @@ -38,6 +38,8 @@ pub struct RestoreOptions { pub overwrite: bool, // The band to select, or by default the last complete one. pub band_selection: BandSelectionPolicy, + /// If printing filenames, include metadata such as file permissions + pub long_listing: bool, } impl Default for RestoreOptions { @@ -48,6 +50,7 @@ impl Default for RestoreOptions { band_selection: BandSelectionPolicy::LatestClosed, exclude: Exclude::nothing(), only_subtree: None, + long_listing: false, } } } @@ -105,7 +108,11 @@ pub fn restore( )?; for entry in entry_iter { if options.print_filenames { - progress_bar.message(&format!("{} {}\n", entry.umode(), entry.apath())); + if options.long_listing { + progress_bar.message(&format!("{} {}\n", entry.umode(), entry.apath())); + } else { + progress_bar.message(&format!("{}\n", entry.apath())); + } } progress_bar.update(|model| model.filename = entry.apath().to_string()); if let Err(e) = match entry.kind() { diff --git a/src/show.rs b/src/show.rs index c7d76bbd..07f1cc4f 100644 --- a/src/show.rs +++ b/src/show.rs @@ -117,10 +117,18 @@ pub fn show_index_json(band: &Band, w: &mut dyn Write) -> Result<()> { .map_err(|source| Error::SerializeIndex { source }) } -pub fn show_entry_names>(it: I, w: &mut dyn Write) -> Result<()> { +pub fn show_entry_names>( + it: I, + w: &mut dyn Write, + long_listing: bool, +) -> Result<()> { let mut bw = BufWriter::new(w); for entry in it { - writeln!(bw, "{} {}", entry.umode(), entry.apath())?; + if long_listing { + writeln!(bw, "{} {}", entry.umode(), entry.apath())?; + } else { + writeln!(bw, "{}", entry.apath())?; + } } Ok(()) } diff --git a/src/stitch.rs b/src/stitch.rs index 5bf14051..61c5e721 100644 --- a/src/stitch.rs +++ b/src/stitch.rs @@ -144,8 +144,8 @@ fn previous_existing_band(archive: &Archive, band_id: &BandId) -> Option mod test { use super::*; use crate::{ - unix_mode::UnixMode, test_fixtures::{ScratchArchive, TreeFixture}, + unix_mode::UnixMode, }; fn symlink(name: &str, target: &str) -> IndexEntry { diff --git a/src/unix_mode.rs b/src/unix_mode.rs index 74bdf48b..ad8b4081 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -55,7 +55,8 @@ impl fmt::Display for UnixMode { // owner permissions write!(f, "{}", if owner & 0b100 > 0 { 'r' } else { '-' })?; write!(f, "{}", if owner & 0b010 > 0 { 'w' } else { '-' })?; - if sss == 0b100 {// Set UID + if sss == 0b100 { + // Set UID write!(f, "{}", if owner & 0b001 > 0 { 's' } else { 'S' })?; } else { write!(f, "{}", if owner & 0b001 > 0 { 'x' } else { '-' })?; @@ -64,7 +65,8 @@ impl fmt::Display for UnixMode { // group permissions write!(f, "{}", if group & 0b100 > 0 { 'r' } else { '-' })?; write!(f, "{}", if group & 0b010 > 0 { 'w' } else { '-' })?; - if sss == 0b010 {// Set GID + if sss == 0b010 { + // Set GID write!(f, "{}", if group & 0b001 > 0 { 's' } else { 'S' })?; } else { write!(f, "{}", if group & 0b001 > 0 { 'x' } else { '-' })?; @@ -73,7 +75,8 @@ impl fmt::Display for UnixMode { // other permissions write!(f, "{}", if other & 0b100 > 0 { 'r' } else { '-' })?; write!(f, "{}", if other & 0b010 > 0 { 'w' } else { '-' })?; - if sss == 0b001 {// sticky + if sss == 0b001 { + // sticky write!(f, "{}", if other & 0b001 > 0 { 't' } else { 'T' })?; } else { write!(f, "{}", if other & 0b001 > 0 { 'x' } else { '-' })?; diff --git a/tests/cli/backup.rs b/tests/cli/backup.rs index 1e61d247..59813799 100644 --- a/tests/cli/backup.rs +++ b/tests/cli/backup.rs @@ -26,7 +26,7 @@ fn backup_verbose() { src.create_file("subdir/b"); run_conserve() - .args(&["backup", "--no-stats", "-v"]) + .args(&["backup", "--no-stats", "-v", "-l"]) .arg(af.path()) .arg(src.path()) .assert() diff --git a/tests/cli/exclude.rs b/tests/cli/exclude.rs index a188c8a4..1cd14370 100644 --- a/tests/cli/exclude.rs +++ b/tests/cli/exclude.rs @@ -54,14 +54,14 @@ fn exclude_simple_glob() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ rw-r--r-- /src/hello.c\n") + .stdout("+ /src/hello.c\n") .success(); run_conserve() .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("rwxr-xr-x /\nrwxr-xr-x /src\nrw-r--r-- /src/hello.c\n") + .stdout("/\n/src\n/src/hello.c\n") .success(); } @@ -80,14 +80,14 @@ fn exclude_glob_only_in_root() { .arg(&af.path()) .arg(&src.path()) .assert() - .stdout("+ rw-r--r-- /src/hello.c\n+ rw-r--r-- /src/hello.o\n") + .stdout("+ /src/hello.c\n+ /src/hello.o\n") .success(); run_conserve() .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("rwxr-xr-x /\nrwxr-xr-x /src\nrw-r--r-- /src/hello.c\nrw-r--r-- /src/hello.o\n") + .stdout("/\n/src\n/src/hello.c\n/src/hello.o\n") .success(); } @@ -117,7 +117,7 @@ fn exclude_suffix_pattern() { .args(&["ls"]) .arg(&af.path()) .assert() - .stdout("rwxr-xr-x /\nrwxr-xr-x /release\nrwxr-xr-x /src\nrwxr-xr-x /subproj\nrwxr-xr-x /target\nrw-r--r-- /src/hello.rs\nrwxr-xr-x /subproj/target\n") + .stdout("/\n/release\n/src\n/subproj\n/target\n/src/hello.rs\n/subproj/target\n") .success(); } diff --git a/tests/cli/main.rs b/tests/cli/main.rs index ebd25abd..f3095c1f 100644 --- a/tests/cli/main.rs +++ b/tests/cli/main.rs @@ -104,10 +104,10 @@ fn basic_backup() { .success() .stderr(predicate::str::is_empty()) .stdout( - "rwxrwxr-x /\n\ - rw-rw-r-- /hello\n\ - rwxrwxr-x /subdir\n\ - rw-rw-r-- /subdir/subfile\n", + "/\n\ + /hello\n\ + /subdir\n\ + /subdir/subfile\n", ); run_conserve() @@ -199,10 +199,10 @@ fn basic_backup() { .success() .stderr(predicate::str::is_empty()) .stdout( - "rwxrwxr-x /\n\ - rw-rw-r-- /hello\n\ - rwxrwxr-x /subdir\n\ - rw-rw-r-- /subdir/subfile\n", + "/\n\ + /hello\n\ + /subdir\n\ + /subdir/subfile\n", ); // TODO: Factor out comparison to expected tree. @@ -220,10 +220,10 @@ fn basic_backup() { .success() .stderr(predicate::str::is_empty()) .stdout(predicate::str::starts_with( - "rwxrwxr-x /\n\ - rw-rw-r-- /hello\n\ - rwxrwxr-x /subdir\n\ - rw-rw-r-- /subdir/subfile\n\ + "/\n\ + /hello\n\ + /subdir\n\ + /subdir/subfile\n\ Restore complete.\n", )); From c05cf50a39f462f4659b18b5a69fd22699644d72 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 01:26:35 +0000 Subject: [PATCH 19/33] attempt to fix windows build --- src/unix_mode.rs | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index ad8b4081..4827c646 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -88,21 +88,12 @@ impl fmt::Display for UnixMode { #[cfg(unix)] use std::os::unix::fs::PermissionsExt; +#[cfg(unix)] impl From for UnixMode { fn from(p: Permissions) -> Self { Self { mode: p.mode() } } } -impl From for UnixMode { - fn from(mode: u32) -> Self { - Self { mode } - } -} -impl From for Permissions { - fn from(p: UnixMode) -> Self { - Permissions::from_mode(p.mode) - } -} #[cfg(not(unix))] impl From for UnixMode { fn from(p: Permissions) -> Self { @@ -111,18 +102,21 @@ impl From for UnixMode { // the rest of the bits are left in the default state // TODO: fix this and test on windows mode: match p.readonly() { - true => 0o100444, - false => 0o100664, + true => 0o100555, + false => 0o100775, }, } } } -#[cfg(windows)] -use std::sys::windows::fs_imp::FilePermissions; -#[cfg(windows)] -impl Into for UnixMode { - fn into(self) -> Permissions { - Permissions::from(FilePermissions::new(self.readonly)) +impl From for UnixMode { + fn from(mode: u32) -> Self { + Self { mode } + } +} +#[cfg(unix)] +impl From for Permissions { + fn from(p: UnixMode) -> Self { + Permissions::from_mode(p.mode) } } From d1b6bf944b3b35f349cc5eea378ace759d8a5b5a Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 01:32:40 +0000 Subject: [PATCH 20/33] reordered code --- src/unix_mode.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index 4827c646..0f7c85e2 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -85,6 +85,12 @@ impl fmt::Display for UnixMode { Ok(()) } } +impl From for UnixMode { + fn from(mode: u32) -> Self { + Self { mode } + } +} + #[cfg(unix)] use std::os::unix::fs::PermissionsExt; @@ -94,6 +100,12 @@ impl From for UnixMode { Self { mode: p.mode() } } } +#[cfg(unix)] +impl From for Permissions { + fn from(p: UnixMode) -> Self { + Permissions::from_mode(p.mode) + } +} #[cfg(not(unix))] impl From for UnixMode { fn from(p: Permissions) -> Self { @@ -108,17 +120,6 @@ impl From for UnixMode { } } } -impl From for UnixMode { - fn from(mode: u32) -> Self { - Self { mode } - } -} -#[cfg(unix)] -impl From for Permissions { - fn from(p: UnixMode) -> Self { - Permissions::from_mode(p.mode) - } -} #[cfg(test)] mod tests { From 3989c546e5ee6dbee909b8f1c1ea337a2f806368 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 01:33:56 +0000 Subject: [PATCH 21/33] filetime::set_symlink_file_times not used on windows --- src/restore.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/restore.rs b/src/restore.rs index 86be85f1..b3f285a5 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -18,7 +18,9 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::{fs, time::Instant}; -use filetime::{set_file_handle_times, set_symlink_file_times}; +use filetime::set_file_handle_times; +#[cfg(unix)] +use filetime::set_symlink_file_times; use crate::band::BandSelectionPolicy; use crate::entry::Entry; From b0b6be6237be04979a61007e536c082b2c6609d8 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 01:38:35 +0000 Subject: [PATCH 22/33] added from for Permissions on not(unix) --- src/unix_mode.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index 0f7c85e2..f1a72cc3 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -120,6 +120,12 @@ impl From for UnixMode { } } } +#[cfg(not(unix))] +impl From for Permissions { + fn from(p: UnixMode) -> Self { + Permissions::from(self.mode & 0o000400 > 0) + } +} #[cfg(test)] mod tests { From 5746c5605abda79b9df2d6534a7b39124b27ee0a Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 01:46:35 +0000 Subject: [PATCH 23/33] added From<&str> for UnixMode --- src/unix_mode.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index f1a72cc3..96730044 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -90,6 +90,24 @@ impl From for UnixMode { Self { mode } } } +impl From<&str> for UnixMode { + // TODO: fix this - it won't work properly as is + fn from(s: &str) -> Self { + let mut mode = 0; + for c in s.chars() { + mode += match c { + 'r' => 0b100, + 'w' => 0b010, + 'x' => 0b001, + _ => 0 + }; + } + + Self { + mode + } + } +} #[cfg(unix)] use std::os::unix::fs::PermissionsExt; From da0f19702082f82f6d0ca72e2291511d08d31525 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 01:51:11 +0000 Subject: [PATCH 24/33] fixed fmt --- src/unix_mode.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index 96730044..8bac6b6c 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -99,13 +99,11 @@ impl From<&str> for UnixMode { 'r' => 0b100, 'w' => 0b010, 'x' => 0b001, - _ => 0 + _ => 0, }; } - Self { - mode - } + Self { mode } } } From fd1e274687bc1b5d3ba7721510838bc8c17ee1ed Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 02:03:51 +0000 Subject: [PATCH 25/33] fixed From<&str> for UnixMode and typo in From in not(unix) --- src/unix_mode.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index 8bac6b6c..09f5f0c5 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -91,10 +91,13 @@ impl From for UnixMode { } } impl From<&str> for UnixMode { - // TODO: fix this - it won't work properly as is + // TODO: implement set uid, set gid, and sticky fn from(s: &str) -> Self { - let mut mode = 0; - for c in s.chars() { + let mut mode: u32 = 0; + for (n, c) in s.chars().enumerate() { + if n % 3 == 0 { + mode <<= 3; + } mode += match c { 'r' => 0b100, 'w' => 0b010, @@ -139,7 +142,7 @@ impl From for UnixMode { #[cfg(not(unix))] impl From for Permissions { fn from(p: UnixMode) -> Self { - Permissions::from(self.mode & 0o000400 > 0) + Permissions::from(p.mode & 0o000400 > 0) } } @@ -159,4 +162,11 @@ mod tests { assert_eq!("rwxr-S-wx", format!("{}", UnixMode::from(0o2743))); assert_eq!("--Sr---wx", format!("{}", UnixMode::from(0o4043))); } + #[test] + fn from_str() { + assert_eq!(UnixMode::from("rwxrwxr--"), UnixMode { mode: 0o774 }); + assert_eq!(UnixMode::from("rwxr-xr-x"), UnixMode { mode: 0o755 }); + assert_eq!(UnixMode::from("rwxr---wx"), UnixMode { mode: 0o743 }); + assert_eq!(UnixMode::from("---r---wx"), UnixMode { mode: 0o043 }); + } } From 7470ff69d7eb823e22f0766911b41fa3c1e2958e Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 02:25:05 +0000 Subject: [PATCH 26/33] refactored to (hopefully) build on windows --- src/restore.rs | 12 ++++++++++++ src/unix_mode.rs | 11 +++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/restore.rs b/src/restore.rs index b3f285a5..7dba5436 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -195,9 +195,21 @@ impl RestoreTree { fn finish(self) -> Result { for (path, umode) in self.dir_umodes { + #[cfg(unix)] if let Err(err) = fs::set_permissions(path, umode.into()) { ui::problem(&format!("Failed to set directory permissions: {:?}", err)); } + #[cfg(not(unix))] + { + fn set_permissions(path: PathBuf, readonly: bool) -> io::Result<()> { + let mut p = std::fs::File::open(&path)?.metadata()?.permissions(); + p.set_readonly(readonly); + fs::set_permissions(path, p) + } + if let Err(err) = set_permissions(path, umode.readonly()) { + ui::problem(&format!("Failed to set directory permissions: {:?}", err)); + } + } } for (path, time) in self.dir_mtimes { if let Err(err) = filetime::set_file_mtime(path, time.into()) { diff --git a/src/unix_mode.rs b/src/unix_mode.rs index 09f5f0c5..e921e171 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -45,6 +45,11 @@ impl Default for UnixMode { Self { mode: 0o100775 } } } +impl UnixMode { + pub fn readonly(self) -> bool { + self.mode & 0o000400 > 0 + } +} impl fmt::Display for UnixMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let sss = (self.mode & 0o7000) >> 9; @@ -139,12 +144,6 @@ impl From for UnixMode { } } } -#[cfg(not(unix))] -impl From for Permissions { - fn from(p: UnixMode) -> Self { - Permissions::from(p.mode & 0o000400 > 0) - } -} #[cfg(test)] mod tests { From d70fb33e663cff314a42cf5fe80b7fa4ad10948e Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 02:32:44 +0000 Subject: [PATCH 27/33] replaced another instance of umode.into in not(unix) cfg --- src/restore.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/restore.rs b/src/restore.rs index 7dba5436..0856be13 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -257,7 +257,17 @@ impl RestoreTree { })?; // Restore permissions let umode = source_entry.umode(); + #[cfg(unix)] fs::set_permissions(path, umode.into())?; + #[cfg(not(unix))] + { + fn set_permissions(path: PathBuf, readonly: bool) -> io::Result<()> { + let mut p = std::fs::File::open(&path)?.metadata()?.permissions(); + p.set_readonly(readonly); + fs::set_permissions(path, p) + } + set_permissions(path, umode.readonly())?; + } // TODO: Accumulate more stats. Ok(RestoreStats { From 6d8de1ee0fea501c9f7792384efac8998d6e9825 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 02:44:48 +0000 Subject: [PATCH 28/33] fixed UnixMode::readonly function --- src/unix_mode.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/unix_mode.rs b/src/unix_mode.rs index e921e171..04801fa1 100644 --- a/src/unix_mode.rs +++ b/src/unix_mode.rs @@ -47,7 +47,8 @@ impl Default for UnixMode { } impl UnixMode { pub fn readonly(self) -> bool { - self.mode & 0o000400 > 0 + // determine if a file is readonly based on whether the owner can write it + self.mode & 0o000200 == 0 } } impl fmt::Display for UnixMode { From e8fe647bc87ac95713e917880418af32f133118f Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 03:00:36 +0000 Subject: [PATCH 29/33] made tests easier so they pass on WIndows --- tests/cli/backup.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cli/backup.rs b/tests/cli/backup.rs index 59813799..0b70cd43 100644 --- a/tests/cli/backup.rs +++ b/tests/cli/backup.rs @@ -26,10 +26,10 @@ fn backup_verbose() { src.create_file("subdir/b"); run_conserve() - .args(&["backup", "--no-stats", "-v", "-l"]) + .args(&["backup", "--no-stats", "-v"]) .arg(af.path()) .arg(src.path()) .assert() .success() - .stdout("+ rw-r--r-- /subdir/a\n+ rw-r--r-- /subdir/b\n"); + .stdout("+ /subdir/a\n+ /subdir/b\n"); } From 0dddda6a561a8bf7fcb8f40c8e473e1d05423235 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 03:00:48 +0000 Subject: [PATCH 30/33] disabled permission restore on Windows --- src/restore.rs | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/restore.rs b/src/restore.rs index 0856be13..f045b967 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -201,14 +201,18 @@ impl RestoreTree { } #[cfg(not(unix))] { - fn set_permissions(path: PathBuf, readonly: bool) -> io::Result<()> { - let mut p = std::fs::File::open(&path)?.metadata()?.permissions(); - p.set_readonly(readonly); - fs::set_permissions(path, p) - } - if let Err(err) = set_permissions(path, umode.readonly()) { - ui::problem(&format!("Failed to set directory permissions: {:?}", err)); - } + ui::problem(&format!("Can't restore permissions on non-Unix: {}", path)); + // TODO: Figure out why we're getting "NotFound" and "PermissionDenied" + // errors on windows. + + // fn set_permissions(path: PathBuf, readonly: bool) -> io::Result<()> { + // let mut p = std::fs::File::open(&path)?.metadata()?.permissions(); + // p.set_readonly(readonly); + // fs::set_permissions(path, p) + // } + // if let Err(err) = set_permissions(path, umode.readonly()) { + // ui::problem(&format!("Failed to set directory permissions: {:?}", err)); + // } } } for (path, time) in self.dir_mtimes { @@ -261,12 +265,16 @@ impl RestoreTree { fs::set_permissions(path, umode.into())?; #[cfg(not(unix))] { - fn set_permissions(path: PathBuf, readonly: bool) -> io::Result<()> { - let mut p = std::fs::File::open(&path)?.metadata()?.permissions(); - p.set_readonly(readonly); - fs::set_permissions(path, p) - } - set_permissions(path, umode.readonly())?; + ui::problem(&format!("Can't restore permissions on non-Unix: {}", path)); + // TODO: Figure out why we're getting "NotFound" and "PermissionDenied" + // errors on windows. + + // fn set_permissions(path: PathBuf, readonly: bool) -> io::Result<()> { + // let mut p = std::fs::File::open(&path)?.metadata()?.permissions(); + // p.set_readonly(readonly); + // fs::set_permissions(path, p) + // } + // set_permissions(path, umode.readonly())?; } // TODO: Accumulate more stats. From ffee2e7c6409d424d0185fb44fa8fb01820bde86 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 03:07:12 +0000 Subject: [PATCH 31/33] fixed syntax error --- src/restore.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/restore.rs b/src/restore.rs index f045b967..e2ef075d 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -201,7 +201,7 @@ impl RestoreTree { } #[cfg(not(unix))] { - ui::problem(&format!("Can't restore permissions on non-Unix: {}", path)); + ui::problem(&format!("Can't restore permissions on non-Unix: {:?}", path)); // TODO: Figure out why we're getting "NotFound" and "PermissionDenied" // errors on windows. @@ -265,7 +265,7 @@ impl RestoreTree { fs::set_permissions(path, umode.into())?; #[cfg(not(unix))] { - ui::problem(&format!("Can't restore permissions on non-Unix: {}", path)); + ui::problem(&format!("Can't restore permissions on non-Unix: {:?}", path)); // TODO: Figure out why we're getting "NotFound" and "PermissionDenied" // errors on windows. From 0db9de333fd305e6046f87aa20db1baf2a3f5677 Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 03:09:22 +0000 Subject: [PATCH 32/33] cargo fmt --- src/restore.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/restore.rs b/src/restore.rs index e2ef075d..911d9ca3 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -201,7 +201,10 @@ impl RestoreTree { } #[cfg(not(unix))] { - ui::problem(&format!("Can't restore permissions on non-Unix: {:?}", path)); + ui::problem(&format!( + "Can't restore permissions on non-Unix: {:?}", + path + )); // TODO: Figure out why we're getting "NotFound" and "PermissionDenied" // errors on windows. @@ -265,7 +268,10 @@ impl RestoreTree { fs::set_permissions(path, umode.into())?; #[cfg(not(unix))] { - ui::problem(&format!("Can't restore permissions on non-Unix: {:?}", path)); + ui::problem(&format!( + "Can't restore permissions on non-Unix: {:?}", + path + )); // TODO: Figure out why we're getting "NotFound" and "PermissionDenied" // errors on windows. From 439b5d25e5506221db1b62332186263db6998ada Mon Sep 17 00:00:00 2001 From: Stephanie Aelmore Date: Wed, 9 Nov 2022 03:16:12 +0000 Subject: [PATCH 33/33] removed problem messages as they were causing tests to fail --- src/restore.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/restore.rs b/src/restore.rs index 911d9ca3..b1c088c2 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -201,10 +201,6 @@ impl RestoreTree { } #[cfg(not(unix))] { - ui::problem(&format!( - "Can't restore permissions on non-Unix: {:?}", - path - )); // TODO: Figure out why we're getting "NotFound" and "PermissionDenied" // errors on windows. @@ -268,10 +264,6 @@ impl RestoreTree { fs::set_permissions(path, umode.into())?; #[cfg(not(unix))] { - ui::problem(&format!( - "Can't restore permissions on non-Unix: {:?}", - path - )); // TODO: Figure out why we're getting "NotFound" and "PermissionDenied" // errors on windows.