all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit
@ 2020-07-28 10:33 Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 1/6] add format description to format module Wolfgang Bumiller
                   ` (7 more replies)
  0 siblings, 8 replies; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 10:33 UTC (permalink / raw)
  To: pbs-devel

So apparently modification time values of *before* Jan 1 1970 are a
thing, so here's support for that...

This bumps the `Entry` struct in pxar (meaning an API bump), and still
supports reading old archives.

Note that I've introduced a new `StatxTimestamp` struct. I chose this
name as the `statx(2)` manpage's `struct statx_timestamp` is the only
struct which actually documents the fact that only the seconds are
signed, and the nanoseconds are *positive* and *relative* to the
seconds, iow. a timestamp of "-3.5 seconds" is represented as "-4
seconds, plus 500_000_000 nanoseconds". (The only other time I found
this to be explicitly mentioned is in the `chrono` crate's
`TimeZone::timestamp` method which explicitly creates a "DateTime from
the number of non-leap seconds since (...) 1970 (...) and the number of
nanoseconds since the last whole non-leap second.".

Wolfgang Bumiller (6):
 pxar:
  add format description to format module
  introduce StatxTimestamp helper type
  update mk-format-hashes for a new ENTRY
  implement Entry v2
  add entry v1 compatiblity test
  bump version to 0.3.0-1

 Cargo.toml                   |   2 +-
 debian/changelog             |   8 ++
 examples/mk-format-hashes.rs |  11 ++-
 src/decoder/mod.rs           |  21 +++-
 src/errors.rs                |  25 -----
 src/format/mod.rs            | 179 ++++++++++++++++++++++++++++++++---
 src/lib.rs                   |  28 +++---
 tests/compat.rs              | 136 ++++++++++++++++++++++++++
 8 files changed, 353 insertions(+), 57 deletions(-)
 delete mode 100644 src/errors.rs
 create mode 100644 tests/compat.rs

 backup:
  update to pxar 0.3 to support negative timestamps

-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] [PATCH pxar 1/6] add format description to format module
  2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
@ 2020-07-28 10:33 ` Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH backup] update to pxar 0.3 to support negative timestamps Wolfgang Bumiller
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 10:33 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 src/format/mod.rs | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/src/format/mod.rs b/src/format/mod.rs
index 2a7d377..0f9db79 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -4,6 +4,36 @@
 //!
 //! The Archive contains a list of items. Each item starts with a `Header`, followed by the
 //! item data.
+//!
+//! An archive contains items in the following order:
+//!  * `ENTRY`              -- containing general stat() data and related bits
+//!   * `XATTR`             -- one extended attribute
+//!   * ...                 -- more of these when there are multiple defined
+//!   * `ACL_USER`          -- one `USER ACL` entry
+//!   * ...                 -- more of these when there are multiple defined
+//!   * `ACL_GROUP`         -- one `GROUP ACL` entry
+//!   * ...                 -- more of these when there are multiple defined
+//!   * `ACL_GROUP_OBJ`     -- The `ACL_GROUP_OBJ`
+//!   * `ACL_DEFAULT`       -- The various default ACL fields if there's one defined
+//!   * `ACL_DEFAULT_USER`  -- one USER ACL entry
+//!   * ...                 -- more of these when multiple are defined
+//!   * `ACL_DEFAULT_GROUP` -- one GROUP ACL entry
+//!   * ...                 -- more of these when multiple are defined
+//!   * `FCAPS`             -- file capability in Linux disk format
+//!   * `QUOTA_PROJECT_ID`  -- the ext4/xfs quota project ID
+//!   * `PAYLOAD`           -- file contents, if it is one
+//!   * `SYMLINK`           -- symlink target, if it is one
+//!   * `DEVICE`            -- device major/minor, if it is a block/char device
+//!
+//!   If we are serializing a directory, then this is followed by:
+//!
+//!   * `FILENAME`          -- name of the first directory entry (strictly ordered!)
+//!   * `<archive>`         -- serialization of the first directory entry's metadata and contents,
+//!                            following the exact same archive format
+//!   * `FILENAME`          -- name of the second directory entry (strictly ordered!)
+//!   * `<archive>`         -- serialization of the second directory entry
+//!   * ...
+//!   * `GOODBYE`           -- lookup table at the end of a list of directory entries
 
 use std::cmp::Ordering;
 use std::ffi::{CStr, OsStr};
-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] [PATCH backup] update to pxar 0.3 to support negative timestamps
  2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 1/6] add format description to format module Wolfgang Bumiller
@ 2020-07-28 10:33 ` Wolfgang Bumiller
  2020-07-29  6:32   ` [pbs-devel] applied: " Dietmar Maurer
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 2/6] introduce StatxTimestamp helper type Wolfgang Bumiller
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 10:33 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 Cargo.toml           |  2 +-
 src/pxar/create.rs   |  9 ++++-----
 src/pxar/fuse.rs     | 17 ++++++-----------
 src/pxar/metadata.rs | 18 ++++++------------
 src/pxar/tools.rs    |  6 ++----
 5 files changed, 19 insertions(+), 33 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 258a9bb2..23063956 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,7 +43,7 @@ proxmox = { version = "0.2.1", features = [ "sortable-macro", "api-macro", "webs
 #proxmox = { git = "ssh://gitolite3@proxdev.maurer-it.com/rust/proxmox", version = "0.1.2", features = [ "sortable-macro", "api-macro" ] }
 #proxmox = { path = "../proxmox/proxmox", features = [ "sortable-macro", "api-macro", "websocket" ] }
 proxmox-fuse = "0.1.0"
-pxar = { version = "0.2.1", features = [ "tokio-io", "futures-io" ] }
+pxar = { version = "0.3.0", features = [ "tokio-io", "futures-io" ] }
 #pxar = { path = "../pxar", features = [ "tokio-io", "futures-io" ] }
 regex = "1.2"
 rustyline = "6"
diff --git a/src/pxar/create.rs b/src/pxar/create.rs
index f0831526..d0c5ae81 100644
--- a/src/pxar/create.rs
+++ b/src/pxar/create.rs
@@ -1,5 +1,4 @@
 use std::collections::{HashSet, HashMap};
-use std::convert::TryFrom;
 use std::ffi::{CStr, CString, OsStr};
 use std::fmt;
 use std::io::{self, Read, Write};
@@ -696,16 +695,16 @@ fn get_metadata(fd: RawFd, stat: &FileStat, flags: Flags, fs_magic: i64) -> Resu
     // required for some of these
     let proc_path = Path::new("/proc/self/fd/").join(fd.to_string());
 
-    let mtime = u64::try_from(stat.st_mtime * 1_000_000_000 + stat.st_mtime_nsec)
-        .map_err(|_| format_err!("file with negative mtime"))?;
-
     let mut meta = Metadata {
         stat: pxar::Stat {
             mode: u64::from(stat.st_mode),
             flags: 0,
             uid: stat.st_uid,
             gid: stat.st_gid,
-            mtime,
+            mtime: pxar::format::StatxTimestamp {
+                secs: stat.st_mtime,
+                nanos: stat.st_mtime_nsec as u32,
+            },
         },
         ..Default::default()
     };
diff --git a/src/pxar/fuse.rs b/src/pxar/fuse.rs
index 1534ff29..652b9219 100644
--- a/src/pxar/fuse.rs
+++ b/src/pxar/fuse.rs
@@ -673,11 +673,6 @@ fn to_stat(inode: u64, entry: &pxar::Entry) -> Result<libc::stat, Error> {
 
     let metadata = entry.metadata();
 
-    let time = i64::try_from(metadata.stat.mtime)
-        .map_err(|_| format_err!("mtime does not fit into a signed 64 bit integer"))?;
-    let sec = time / 1_000_000_000;
-    let nsec = time % 1_000_000_000;
-
     let mut stat: libc::stat = unsafe { mem::zeroed() };
     stat.st_ino = inode;
     stat.st_nlink = nlink;
@@ -687,11 +682,11 @@ fn to_stat(inode: u64, entry: &pxar::Entry) -> Result<libc::stat, Error> {
         .map_err(|err| format_err!("size does not fit into st_size field: {}", err))?;
     stat.st_uid = metadata.stat.uid;
     stat.st_gid = metadata.stat.gid;
-    stat.st_atime = sec;
-    stat.st_atime_nsec = nsec;
-    stat.st_mtime = sec;
-    stat.st_mtime_nsec = nsec;
-    stat.st_ctime = sec;
-    stat.st_ctime_nsec = nsec;
+    stat.st_atime = metadata.stat.mtime.secs;
+    stat.st_atime_nsec = metadata.stat.mtime.nanos as _;
+    stat.st_mtime = metadata.stat.mtime.secs;
+    stat.st_mtime_nsec = metadata.stat.mtime.nanos as _;
+    stat.st_ctime = metadata.stat.mtime.secs;
+    stat.st_ctime_nsec = metadata.stat.mtime.nanos as _;
     Ok(stat)
 }
diff --git a/src/pxar/metadata.rs b/src/pxar/metadata.rs
index 2cbed756..6df00230 100644
--- a/src/pxar/metadata.rs
+++ b/src/pxar/metadata.rs
@@ -37,26 +37,20 @@ fn allow_notsupp_remember<E: SysError>(err: E, not_supp: &mut bool) -> Result<()
     }
 }
 
-fn nsec_to_update_timespec(mtime_nsec: u64) -> [libc::timespec; 2] {
+fn timestamp_to_update_timespec(mtime: &pxar::format::StatxTimestamp) -> [libc::timespec; 2] {
     // restore mtime
     const UTIME_OMIT: i64 = (1 << 30) - 2;
-    const NANOS_PER_SEC: i64 = 1_000_000_000;
 
-    let sec = (mtime_nsec as i64) / NANOS_PER_SEC;
-    let nsec = (mtime_nsec as i64) % NANOS_PER_SEC;
-
-    let times: [libc::timespec; 2] = [
+    [
         libc::timespec {
             tv_sec: 0,
             tv_nsec: UTIME_OMIT,
         },
         libc::timespec {
-            tv_sec: sec,
-            tv_nsec: nsec,
+            tv_sec: mtime.secs,
+            tv_nsec: mtime.nanos as _,
         },
-    ];
-
-    times
+    ]
 }
 
 //
@@ -130,7 +124,7 @@ pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) ->
         libc::utimensat(
             libc::AT_FDCWD,
             c_proc_path.as_ptr(),
-            nsec_to_update_timespec(metadata.stat.mtime).as_ptr(),
+            timestamp_to_update_timespec(&metadata.stat.mtime).as_ptr(),
             0,
         )
     });
diff --git a/src/pxar/tools.rs b/src/pxar/tools.rs
index ec5c13b2..0fdb033d 100644
--- a/src/pxar/tools.rs
+++ b/src/pxar/tools.rs
@@ -120,8 +120,7 @@ pub fn format_single_line_entry(entry: &Entry) -> String {
     let mode_string = mode_string(entry);
 
     let meta = entry.metadata();
-    let mtime = meta.mtime_as_duration();
-    let mtime = chrono::Local.timestamp(mtime.as_secs() as i64, mtime.subsec_nanos());
+    let mtime = chrono::Local.timestamp(meta.stat.mtime.secs, meta.stat.mtime.nanos);
 
     let (size, link) = match entry.kind() {
         EntryKind::File { size, .. } => (format!("{}", *size), String::new()),
@@ -148,8 +147,7 @@ pub fn format_multi_line_entry(entry: &Entry) -> String {
     let mode_string = mode_string(entry);
 
     let meta = entry.metadata();
-    let mtime = meta.mtime_as_duration();
-    let mtime = chrono::Local.timestamp(mtime.as_secs() as i64, mtime.subsec_nanos());
+    let mtime = chrono::Local.timestamp(meta.stat.mtime.secs, meta.stat.mtime.nanos);
 
     let (size, link, type_name) = match entry.kind() {
         EntryKind::File { size, .. } => (format!("{}", *size), String::new(), "file"),
-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] [PATCH pxar 2/6] introduce StatxTimestamp helper type
  2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 1/6] add format description to format module Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH backup] update to pxar 0.3 to support negative timestamps Wolfgang Bumiller
@ 2020-07-28 10:33 ` Wolfgang Bumiller
  2020-07-28 14:05   ` [pbs-devel] [PATCH pxar v2 " Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 3/6] update mk-format-hashes for a new ENTRY Wolfgang Bumiller
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 10:33 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 src/format/mod.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 103 insertions(+)

diff --git a/src/format/mod.rs b/src/format/mod.rs
index 0f9db79..085b8b9 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -43,6 +43,7 @@ use std::io;
 use std::mem::size_of;
 use std::os::unix::ffi::OsStrExt;
 use std::path::Path;
+use std::time::{Duration, SystemTime};
 
 use endian_trait::Endian;
 use siphasher::sip::SipHasher24;
@@ -194,6 +195,108 @@ impl Display for Header {
     }
 }
 
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum SignedDuration {
+    Positive(Duration),
+    Negative(Duration),
+}
+
+#[derive(Clone, Debug, Default, Endian, Eq, PartialEq)]
+#[repr(C)]
+pub struct StatxTimestamp {
+    /// Seconds since the epoch (unix time).
+    pub secs: i64,
+
+    /// Nanoseconds since this struct's `secs`.
+    pub nanos: u32,
+}
+
+impl From<SystemTime> for StatxTimestamp {
+    fn from(time: SystemTime) -> Self {
+        match time.duration_since(SystemTime::UNIX_EPOCH) {
+            Ok(positive) => Self::from_duration_since_epoch(positive),
+            Err(negative) => Self::from_duration_before_epoch(negative.duration()),
+        }
+    }
+}
+
+impl StatxTimestamp {
+    /// `const` version of `Default`
+    pub const fn zero() -> Self {
+        Self { secs: 0, nanos: 0 }
+    }
+
+    /// Turn a positive duration relative to the unix epoch into a time stamp.
+    pub fn from_duration_since_epoch(duration: Duration) -> Self {
+        Self {
+            secs: duration.as_secs() as i64,
+            nanos: duration.subsec_nanos(),
+        }
+    }
+
+    /// Turn a *negative* duration from relative to the unix epoch into a time stamp.
+    pub fn from_duration_before_epoch(duration: Duration) -> Self {
+        match duration.subsec_nanos() {
+            0 => Self {
+                secs: duration.as_secs() as i64,
+                nanos: 0,
+            },
+            nanos => Self {
+                secs: -(duration.as_secs() as i64) - 1,
+                nanos: 1_000_000_000 - nanos,
+            },
+        }
+    }
+
+    /// Get the duration since the epoch. an `Ok` value is a positive duration, an `Err` value is a
+    /// negative duration.
+    pub fn to_duration(&self) -> SignedDuration {
+        if self.secs >= 0 {
+            SignedDuration::Positive(Duration::new(self.secs as u64, self.nanos))
+        } else {
+            SignedDuration::Negative(Duration::new(
+                -(self.secs + 1) as u64,
+                1_000_000_000 - self.nanos,
+            ))
+        }
+    }
+
+    /// Get a `std::time::SystemTime` from this time stamp.
+    pub fn system_time(&self) -> SystemTime {
+        match self.to_duration() {
+            SignedDuration::Positive(positive) => SystemTime::UNIX_EPOCH + positive,
+            SignedDuration::Negative(negative) => SystemTime::UNIX_EPOCH - negative,
+        }
+    }
+}
+
+#[test]
+fn test_statx_timestamp() {
+    const MAY_1_2015_1530: i64 = 1430487000;
+    let system_time = SystemTime::UNIX_EPOCH + Duration::new(MAY_1_2015_1530 as u64, 1_000_000);
+    let tx = StatxTimestamp::from(system_time);
+    assert_eq!(
+        tx,
+        StatxTimestamp {
+            secs: MAY_1_2015_1530,
+            nanos: 1_000_000,
+        }
+    );
+    assert_eq!(tx.system_time(), system_time);
+
+    const MAY_1_1960_1530: i64 = -305112600;
+    let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 1_000_000);
+    let tx = StatxTimestamp::from(system_time);
+    assert_eq!(
+        tx,
+        StatxTimestamp {
+            secs: MAY_1_1960_1530 - 1,
+            nanos: 999_000_000,
+        }
+    );
+    assert_eq!(tx.system_time(), system_time);
+}
+
 #[derive(Clone, Debug, Default, Endian)]
 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
 #[repr(C)]
-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] [PATCH pxar 3/6] update mk-format-hashes for a new ENTRY
  2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
                   ` (2 preceding siblings ...)
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 2/6] introduce StatxTimestamp helper type Wolfgang Bumiller
@ 2020-07-28 10:33 ` Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 4/6] implement Entry v2 Wolfgang Bumiller
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 10:33 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 examples/mk-format-hashes.rs | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/examples/mk-format-hashes.rs b/examples/mk-format-hashes.rs
index d2c9925..4ab6fff 100644
--- a/examples/mk-format-hashes.rs
+++ b/examples/mk-format-hashes.rs
@@ -1,7 +1,16 @@
 use pxar::format::hash_filename;
 
 const CONSTANTS: &[(&str, &str, &str)] = &[
-    ("", "PXAR_ENTRY", "__PROXMOX_FORMAT_ENTRY__"),
+    (
+        "Beginning of an entry (current version).",
+        "PXAR_ENTRY",
+        "__PROXMOX_FORMAT_ENTRY_V2__",
+    ),
+    (
+        "Previous version of the entry struct",
+        "PXAR_ENTRY_V1",
+        "__PROXMOX_FORMAT_ENTRY__",
+    ),
     ("", "PXAR_FILENAME", "__PROXMOX_FORMAT_FILENAME__"),
     ("", "PXAR_SYMLINK", "__PROXMOX_FORMAT_SYMLINK__"),
     ("", "PXAR_DEVICE", "__PROXMOX_FORMAT_DEVICE__"),
-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] [PATCH pxar 4/6] implement Entry v2
  2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
                   ` (3 preceding siblings ...)
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 3/6] update mk-format-hashes for a new ENTRY Wolfgang Bumiller
@ 2020-07-28 10:33 ` Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 5/6] add entry v1 compatiblity test Wolfgang Bumiller
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 10:33 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 src/decoder/mod.rs | 21 ++++++++++++++++-----
 src/errors.rs      | 25 -------------------------
 src/format/mod.rs  | 46 ++++++++++++++++++++++++++++++++++------------
 src/lib.rs         | 28 +++++++++++++++-------------
 4 files changed, 65 insertions(+), 55 deletions(-)
 delete mode 100644 src/errors.rs

diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs
index 9410942..e5b6d2c 100644
--- a/src/decoder/mod.rs
+++ b/src/decoder/mod.rs
@@ -360,11 +360,22 @@ impl<I: SeqRead> DecoderImpl<I> {
             self.entry.kind = EntryKind::Hardlink(self.read_hardlink().await?);
 
             Ok(Some(self.entry.take()))
-        } else if header.htype == format::PXAR_ENTRY {
-            self.entry.metadata = Metadata {
-                stat: seq_read_entry(&mut self.input).await?,
-                ..Default::default()
-            };
+        } else if header.htype == format::PXAR_ENTRY || header.htype == format::PXAR_ENTRY_V1 {
+            if header.htype == format::PXAR_ENTRY {
+                self.entry.metadata = Metadata {
+                    stat: seq_read_entry(&mut self.input).await?,
+                    ..Default::default()
+                };
+            } else if header.htype == format::PXAR_ENTRY_V1 {
+                let stat: format::Entry_V1 = seq_read_entry(&mut self.input).await?;
+
+                self.entry.metadata = Metadata {
+                    stat: stat.into(),
+                    ..Default::default()
+                };
+            } else {
+                unreachable!();
+            }
 
             self.current_header = unsafe { mem::zeroed() };
 
diff --git a/src/errors.rs b/src/errors.rs
deleted file mode 100644
index 8983e98..0000000
--- a/src/errors.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-use std::error::Error;
-use std::fmt;
-
-#[derive(Clone, Debug)]
-pub enum TimeError {
-    Underflow(std::time::SystemTimeError),
-    Overflow(std::time::Duration),
-}
-
-impl fmt::Display for TimeError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            TimeError::Underflow(st) => st.fmt(f),
-            TimeError::Overflow(dur) => write!(f, "timestamp out of range: {:?}", dur),
-        }
-    }
-}
-
-impl Error for TimeError {}
-
-impl From<std::time::SystemTimeError> for TimeError {
-    fn from(t: std::time::SystemTimeError) -> Self {
-        TimeError::Underflow(t)
-    }
-}
diff --git a/src/format/mod.rs b/src/format/mod.rs
index 085b8b9..ec61e15 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -78,7 +78,10 @@ pub mod mode {
     pub const ISVTX  : u64 = 0o0001000;
 }
 
-pub const PXAR_ENTRY: u64 = 0x11da850a1c1cceff;
+/// Beginning of an entry (current version).
+pub const PXAR_ENTRY: u64 = 0xd5956474e588acef;
+/// Previous version of the entry struct
+pub const PXAR_ENTRY_V1: u64 = 0x11da850a1c1cceff;
 pub const PXAR_FILENAME: u64 = 0x16701121063917b3;
 pub const PXAR_SYMLINK: u64 = 0x27f971e7dbf5dc5f;
 pub const PXAR_DEVICE: u64 = 0x9fc9e906586d5ce9;
@@ -300,7 +303,7 @@ fn test_statx_timestamp() {
 #[derive(Clone, Debug, Default, Endian)]
 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
 #[repr(C)]
-pub struct Entry {
+pub struct Entry_V1 {
     pub mode: u64,
     pub flags: u64,
     pub uid: u32,
@@ -308,6 +311,29 @@ pub struct Entry {
     pub mtime: u64,
 }
 
+impl Into<Entry> for Entry_V1 {
+    fn into(self) -> Entry {
+        Entry {
+            mode: self.mode,
+            flags: self.flags,
+            uid: self.uid,
+            gid: self.gid,
+            mtime: StatxTimestamp::from_duration_since_epoch(Duration::from_nanos(self.mtime)),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Default, Endian)]
+#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
+#[repr(C)]
+pub struct Entry {
+    pub mode: u64,
+    pub flags: u64,
+    pub uid: u32,
+    pub gid: u32,
+    pub mtime: StatxTimestamp,
+}
+
 /// Builder pattern methods.
 impl Entry {
     pub const fn mode(self, mode: u64) -> Self {
@@ -326,7 +352,7 @@ impl Entry {
         Self { gid, ..self }
     }
 
-    pub const fn mtime(self, mtime: u64) -> Self {
+    pub const fn mtime(self, mtime: StatxTimestamp) -> Self {
         Self { mtime, ..self }
     }
 
@@ -363,9 +389,10 @@ impl Entry {
 
 /// Convenience accessor methods.
 impl Entry {
-    /// Get the mtime as duration since the epoch.
-    pub fn mtime_as_duration(&self) -> std::time::Duration {
-        std::time::Duration::from_nanos(self.mtime)
+    /// Get the mtime as duration since the epoch. an `Ok` value is a positive duration, an `Err`
+    /// value is a negative duration.
+    pub fn mtime_as_duration(&self) -> SignedDuration {
+        self.mtime.to_duration()
     }
 
     /// Get the file type portion of the mode bitfield.
@@ -449,12 +476,7 @@ impl From<&std::fs::Metadata> for Entry {
             .mode(meta.mode() as u64);
 
         let this = match meta.modified() {
-            Ok(mtime) => this.mtime(
-                mtime
-                    .duration_since(std::time::SystemTime::UNIX_EPOCH)
-                    .map(|dur| dur.as_nanos() as u64)
-                    .unwrap_or(0u64),
-            ),
+            Ok(mtime) => this.mtime(mtime.into()),
             Err(_) => this,
         };
 
diff --git a/src/lib.rs b/src/lib.rs
index 5d1b781..a58ec30 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,7 +2,6 @@
 //!
 //! This implements a reader and writer for the proxmox archive format (.pxar).
 
-use std::convert::TryFrom;
 use std::ffi::OsStr;
 use std::mem;
 use std::path::{Path, PathBuf};
@@ -20,7 +19,6 @@ pub mod accessor;
 pub mod binary_tree_array;
 pub mod decoder;
 pub mod encoder;
-pub mod errors;
 
 /// Reexport of `format::Entry`. Since this conveys mostly information found via the `stat` syscall
 /// we mostly use this name for public interfaces.
@@ -125,8 +123,9 @@ impl Metadata {
         self.stat.is_socket()
     }
 
-    /// Get the mtime as duration since the epoch.
-    pub fn mtime_as_duration(&self) -> std::time::Duration {
+    /// Get the mtime as duration since the epoch. an `Ok` value is a positive duration, an `Err`
+    /// value is a negative duration.
+    pub fn mtime_as_duration(&self) -> format::SignedDuration {
         self.stat.mtime_as_duration()
     }
 
@@ -165,7 +164,7 @@ impl MetadataBuilder {
                     flags: 0,
                     uid: 0,
                     gid: 0,
-                    mtime: 0,
+                    mtime: format::StatxTimestamp::zero(),
                 },
                 xattrs: Vec::new(),
                 acl: Acl {
@@ -198,17 +197,20 @@ impl MetadataBuilder {
         self
     }
 
-    /// Set the modification time from a system time.
-    pub fn mtime(self, mtime: std::time::SystemTime) -> Result<Self, errors::TimeError> {
-        self.mtime_unix(mtime.duration_since(std::time::SystemTime::UNIX_EPOCH)?)
+    /// Set the modification time from a statx timespec value.
+    pub fn mtime_full(mut self, mtime: format::StatxTimestamp) -> Self {
+        self.inner.stat.mtime = mtime;
+        self
     }
 
     /// Set the modification time from a duration since the epoch (`SystemTime::UNIX_EPOCH`).
-    pub fn mtime_unix(mut self, mtime: std::time::Duration) -> Result<Self, errors::TimeError> {
-        let nanos =
-            u64::try_from(mtime.as_nanos()).map_err(|_| errors::TimeError::Overflow(mtime))?;
-        self.inner.stat.mtime = nanos;
-        Ok(self)
+    pub fn mtime_unix(self, mtime: std::time::Duration) -> Self {
+        self.mtime_full(format::StatxTimestamp::from_duration_since_epoch(mtime))
+    }
+
+    /// Set the modification time from a system time.
+    pub fn mtime(self, mtime: std::time::SystemTime) -> Self {
+        self.mtime_full(mtime.into())
     }
 
     /// Set the ownership information.
-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] [PATCH pxar 5/6] add entry v1 compatiblity test
  2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
                   ` (4 preceding siblings ...)
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 4/6] implement Entry v2 Wolfgang Bumiller
@ 2020-07-28 10:33 ` Wolfgang Bumiller
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 6/6] bump version to 0.3.0-1 Wolfgang Bumiller
  2020-07-29  6:14 ` [pbs-devel] applied: [PATCH pxar/backup 0/6] bump timestamps to 96 bit Dietmar Maurer
  7 siblings, 0 replies; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 10:33 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 tests/compat.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 136 insertions(+)
 create mode 100644 tests/compat.rs

diff --git a/tests/compat.rs b/tests/compat.rs
new file mode 100644
index 0000000..53e2d09
--- /dev/null
+++ b/tests/compat.rs
@@ -0,0 +1,136 @@
+//! Test for old timestamp format compatibility.
+
+use std::io::{self, Read};
+
+use endian_trait::Endian;
+
+use pxar::{decoder, format, EntryKind};
+
+fn write_raw_struct<T: Endian, W: io::Write + ?Sized>(output: &mut W, data: T) -> io::Result<()> {
+    let data = data.to_le();
+    output.write_all(unsafe {
+        std::slice::from_raw_parts(&data as *const T as *const u8, std::mem::size_of::<T>())
+    })
+}
+
+fn write_data<W>(output: &mut W, htype: u64, data: &[u8]) -> io::Result<()>
+where
+    W: io::Write + ?Sized,
+{
+    let header = format::Header::with_content_size(htype, data.len() as u64);
+    header.check_header_size()?;
+    write_raw_struct(output, header)?;
+    output.write_all(data)
+}
+
+fn write_entry<T, W>(output: &mut W, htype: u64, data: T) -> io::Result<()>
+where
+    T: Endian,
+    W: io::Write + ?Sized,
+{
+    let data = data.to_le();
+    let data = unsafe {
+        std::slice::from_raw_parts(&data as *const T as *const u8, std::mem::size_of::<T>())
+    };
+    write_data(output, htype, data)
+}
+
+const MAY_1_2015_1530: u64 = 1430487000u64;
+
+const FILE_NAME: &str = "file.txt";
+const FILE_NAME_BYTES: &[u8] = b"file.txt\0";
+const FILE_CONTENT: &[u8] = b"This is a small text file.\n";
+const ROOT_STAT: format::Entry_V1 = format::Entry_V1 {
+    mode: format::mode::IFDIR | 0o755,
+    flags: 0,
+    uid: 1000,
+    gid: 1000,
+    mtime: MAY_1_2015_1530 * 1_000_000_000u64,
+};
+const FILE_STAT: format::Entry_V1 = format::Entry_V1 {
+    mode: format::mode::IFREG | 0o644,
+    flags: 0,
+    uid: 1000,
+    gid: 1000,
+    mtime: MAY_1_2015_1530 * 1_000_000_000u64,
+};
+
+fn create_archive() -> io::Result<Vec<u8>> {
+    let mut out = Vec::new();
+
+    write_entry(&mut out, format::PXAR_ENTRY_V1, ROOT_STAT.clone())?;
+
+    let file_offset = out.len();
+    write_data(&mut out, format::PXAR_FILENAME, FILE_NAME_BYTES)?;
+    write_entry(&mut out, format::PXAR_ENTRY_V1, FILE_STAT.clone())?;
+    write_data(&mut out, format::PXAR_PAYLOAD, FILE_CONTENT)?;
+
+    let mut gbt = Vec::new();
+    write_raw_struct(
+        &mut gbt,
+        format::GoodbyeItem::new(
+            FILE_NAME.as_bytes(),
+            file_offset as u64,
+            FILE_CONTENT.len() as u64,
+        ),
+    )?;
+
+    let gbt_size = gbt.len();
+    write_raw_struct(
+        &mut gbt,
+        format::GoodbyeItem {
+            hash: format::PXAR_GOODBYE_TAIL_MARKER,
+            offset: out.len() as u64,
+            size: gbt_size as u64,
+        },
+    )?;
+
+    write_data(&mut out, format::PXAR_GOODBYE, &{ gbt })?;
+
+    Ok(out)
+}
+
+#[test]
+fn test_archive() {
+    let archive = create_archive().expect("failed to create test archive");
+    let mut input = &archive[..];
+    let mut decoder = decoder::Decoder::from_std(&mut input).expect("failed to create decoder");
+
+    let item = decoder
+        .next()
+        .expect("missing root directory in test archive")
+        .expect("failed to extract root directory from test archive");
+    match item.kind() {
+        EntryKind::Directory => (),
+        other => panic!("unexpected root entry in archive: {:?}", other),
+    }
+    assert_eq!(item.file_name(), "");
+    assert_eq!(item.metadata().stat, ROOT_STAT.into());
+    assert_eq!(
+        item.metadata().stat.mtime,
+        format::StatxTimestamp {
+            secs: MAY_1_2015_1530 as i64,
+            nanos: 0,
+        },
+    );
+
+    let item = decoder
+        .next()
+        .expect("missing file entry in test archive")
+        .expect("failed to extract file entry from test archive");
+    match item.kind() {
+        EntryKind::File { size, .. } => assert_eq!(*size, FILE_CONTENT.len() as u64),
+        other => panic!("unexpected file entry in archive: {:?}", other),
+    }
+    assert_eq!(item.file_name(), FILE_NAME);
+    assert_eq!(item.metadata().stat, FILE_STAT.into());
+    let mut content = Vec::new();
+    decoder
+        .contents()
+        .expect("failed to get contents for file entry")
+        .read_to_end(&mut content)
+        .expect("failed to read test file contents");
+    assert_eq!(&content[..], FILE_CONTENT);
+
+    assert!(decoder.next().is_none(), "expected end of test archive");
+}
-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] [PATCH pxar 6/6] bump version to 0.3.0-1
  2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
                   ` (5 preceding siblings ...)
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 5/6] add entry v1 compatiblity test Wolfgang Bumiller
@ 2020-07-28 10:33 ` Wolfgang Bumiller
  2020-07-29  6:14 ` [pbs-devel] applied: [PATCH pxar/backup 0/6] bump timestamps to 96 bit Dietmar Maurer
  7 siblings, 0 replies; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 10:33 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 Cargo.toml       | 2 +-
 debian/changelog | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index c2f0f96..c1b53fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pxar"
-version = "0.2.1"
+version = "0.3.0"
 authors = ["Wolfgang Bumiller <w.bumiller@proxmox.com>"]
 edition = "2018"
 license = "AGPL-3"
diff --git a/debian/changelog b/debian/changelog
index 5901b61..b1a1852 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+rust-pxar (0.3.0-1) pve; urgency=medium
+
+  * introduce 96 bit time stamp type with support for negative timestamps
+
+  * introduce PXAR_ENTRY version 2 header type
+
+ -- Proxmox Support Team <support@proxmox.com>  Tue, 28 Jul 2020 11:56:47 +0200
+
 rust-pxar (0.2.1-1) pve; urgency=medium
 
   * sync encoder: fix metadata lifetime leaking into encoder
-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] [PATCH pxar v2 2/6] introduce StatxTimestamp helper type
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 2/6] introduce StatxTimestamp helper type Wolfgang Bumiller
@ 2020-07-28 14:05   ` Wolfgang Bumiller
  0 siblings, 0 replies; 11+ messages in thread
From: Wolfgang Bumiller @ 2020-07-28 14:05 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
changes to v1:
  Add testcase with nanos=0
  Fix nanos=0 case in `from_duration_before_epoch`.
  (added comment about why `to_duration` doesn't need special handling,
  that is because std::time::Duration already does that)

 src/format/mod.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 115 insertions(+)

diff --git a/src/format/mod.rs b/src/format/mod.rs
index 0f9db79..1a38157 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -43,6 +43,7 @@ use std::io;
 use std::mem::size_of;
 use std::os::unix::ffi::OsStrExt;
 use std::path::Path;
+use std::time::{Duration, SystemTime};
 
 use endian_trait::Endian;
 use siphasher::sip::SipHasher24;
@@ -194,6 +195,120 @@ impl Display for Header {
     }
 }
 
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum SignedDuration {
+    Positive(Duration),
+    Negative(Duration),
+}
+
+#[derive(Clone, Debug, Default, Endian, Eq, PartialEq)]
+#[repr(C)]
+pub struct StatxTimestamp {
+    /// Seconds since the epoch (unix time).
+    pub secs: i64,
+
+    /// Nanoseconds since this struct's `secs`.
+    pub nanos: u32,
+}
+
+impl From<SystemTime> for StatxTimestamp {
+    fn from(time: SystemTime) -> Self {
+        match time.duration_since(SystemTime::UNIX_EPOCH) {
+            Ok(positive) => Self::from_duration_since_epoch(positive),
+            Err(negative) => Self::from_duration_before_epoch(negative.duration()),
+        }
+    }
+}
+
+impl StatxTimestamp {
+    /// `const` version of `Default`
+    pub const fn zero() -> Self {
+        Self { secs: 0, nanos: 0 }
+    }
+
+    /// Turn a positive duration relative to the unix epoch into a time stamp.
+    pub fn from_duration_since_epoch(duration: Duration) -> Self {
+        Self {
+            secs: duration.as_secs() as i64,
+            nanos: duration.subsec_nanos(),
+        }
+    }
+
+    /// Turn a *negative* duration from relative to the unix epoch into a time stamp.
+    pub fn from_duration_before_epoch(duration: Duration) -> Self {
+        match duration.subsec_nanos() {
+            0 => Self {
+                secs: -(duration.as_secs() as i64),
+                nanos: 0,
+            },
+            nanos => Self {
+                secs: -(duration.as_secs() as i64) - 1,
+                nanos: 1_000_000_000 - nanos,
+            },
+        }
+    }
+
+    /// Get the duration since the epoch. an `Ok` value is a positive duration, an `Err` value is a
+    /// negative duration.
+    pub fn to_duration(&self) -> SignedDuration {
+        if self.secs >= 0 {
+            SignedDuration::Positive(Duration::new(self.secs as u64, self.nanos))
+        } else {
+            // this handles the nanos=0 case as `Duration::new()` performs the carry-over.
+            SignedDuration::Negative(Duration::new(
+                -(self.secs + 1) as u64,
+                1_000_000_000 - self.nanos,
+            ))
+        }
+    }
+
+    /// Get a `std::time::SystemTime` from this time stamp.
+    pub fn system_time(&self) -> SystemTime {
+        match self.to_duration() {
+            SignedDuration::Positive(positive) => SystemTime::UNIX_EPOCH + positive,
+            SignedDuration::Negative(negative) => SystemTime::UNIX_EPOCH - negative,
+        }
+    }
+}
+
+#[test]
+fn test_statx_timestamp() {
+    const MAY_1_2015_1530: i64 = 1430487000;
+    let system_time = SystemTime::UNIX_EPOCH + Duration::new(MAY_1_2015_1530 as u64, 1_000_000);
+    let tx = StatxTimestamp::from(system_time);
+    assert_eq!(
+        tx,
+        StatxTimestamp {
+            secs: MAY_1_2015_1530,
+            nanos: 1_000_000,
+        }
+    );
+    assert_eq!(tx.system_time(), system_time);
+
+    const MAY_1_1960_1530: i64 = -305112600;
+    let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 1_000_000);
+    let tx = StatxTimestamp::from(system_time);
+    assert_eq!(
+        tx,
+        StatxTimestamp {
+            secs: MAY_1_1960_1530 - 1,
+            nanos: 999_000_000,
+        }
+    );
+    assert_eq!(tx.system_time(), system_time);
+
+    let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 0);
+    let tx = StatxTimestamp::from(system_time);
+    assert_eq!(
+        tx,
+        StatxTimestamp {
+            secs: MAY_1_1960_1530,
+            nanos: 0,
+        }
+    );
+    assert_eq!(tx.system_time(), system_time);
+}
+
 #[derive(Clone, Debug, Default, Endian)]
 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
 #[repr(C)]
-- 
2.20.1





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] applied: [PATCH pxar/backup 0/6] bump timestamps to 96 bit
  2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
                   ` (6 preceding siblings ...)
  2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 6/6] bump version to 0.3.0-1 Wolfgang Bumiller
@ 2020-07-29  6:14 ` Dietmar Maurer
  7 siblings, 0 replies; 11+ messages in thread
From: Dietmar Maurer @ 2020-07-29  6:14 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Wolfgang Bumiller

applied

> On 07/28/2020 12:33 PM Wolfgang Bumiller <w.bumiller@proxmox.com> wrote:
> 
>  
> So apparently modification time values of *before* Jan 1 1970 are a
> thing, so here's support for that...
> 
> This bumps the `Entry` struct in pxar (meaning an API bump), and still
> supports reading old archives.
> 
> Note that I've introduced a new `StatxTimestamp` struct. I chose this
> name as the `statx(2)` manpage's `struct statx_timestamp` is the only
> struct which actually documents the fact that only the seconds are
> signed, and the nanoseconds are *positive* and *relative* to the
> seconds, iow. a timestamp of "-3.5 seconds" is represented as "-4
> seconds, plus 500_000_000 nanoseconds". (The only other time I found
> this to be explicitly mentioned is in the `chrono` crate's
> `TimeZone::timestamp` method which explicitly creates a "DateTime from
> the number of non-leap seconds since (...) 1970 (...) and the number of
> nanoseconds since the last whole non-leap second.".
> 
> Wolfgang Bumiller (6):
>  pxar:
>   add format description to format module
>   introduce StatxTimestamp helper type
>   update mk-format-hashes for a new ENTRY
>   implement Entry v2
>   add entry v1 compatiblity test
>   bump version to 0.3.0-1
> 
>  Cargo.toml                   |   2 +-
>  debian/changelog             |   8 ++
>  examples/mk-format-hashes.rs |  11 ++-
>  src/decoder/mod.rs           |  21 +++-
>  src/errors.rs                |  25 -----
>  src/format/mod.rs            | 179 ++++++++++++++++++++++++++++++++---
>  src/lib.rs                   |  28 +++---
>  tests/compat.rs              | 136 ++++++++++++++++++++++++++
>  8 files changed, 353 insertions(+), 57 deletions(-)
>  delete mode 100644 src/errors.rs
>  create mode 100644 tests/compat.rs
> 
>  backup:
>   update to pxar 0.3 to support negative timestamps
> 
> -- 
> 2.20.1
> 
> 
> 
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel




^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pbs-devel] applied: [PATCH backup] update to pxar 0.3 to support negative timestamps
  2020-07-28 10:33 ` [pbs-devel] [PATCH backup] update to pxar 0.3 to support negative timestamps Wolfgang Bumiller
@ 2020-07-29  6:32   ` Dietmar Maurer
  0 siblings, 0 replies; 11+ messages in thread
From: Dietmar Maurer @ 2020-07-29  6:32 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Wolfgang Bumiller

applied




^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2020-07-29  6:33 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-28 10:33 [pbs-devel] [PATCH pxar/backup 0/6] bump timestamps to 96 bit Wolfgang Bumiller
2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 1/6] add format description to format module Wolfgang Bumiller
2020-07-28 10:33 ` [pbs-devel] [PATCH backup] update to pxar 0.3 to support negative timestamps Wolfgang Bumiller
2020-07-29  6:32   ` [pbs-devel] applied: " Dietmar Maurer
2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 2/6] introduce StatxTimestamp helper type Wolfgang Bumiller
2020-07-28 14:05   ` [pbs-devel] [PATCH pxar v2 " Wolfgang Bumiller
2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 3/6] update mk-format-hashes for a new ENTRY Wolfgang Bumiller
2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 4/6] implement Entry v2 Wolfgang Bumiller
2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 5/6] add entry v1 compatiblity test Wolfgang Bumiller
2020-07-28 10:33 ` [pbs-devel] [PATCH pxar 6/6] bump version to 0.3.0-1 Wolfgang Bumiller
2020-07-29  6:14 ` [pbs-devel] applied: [PATCH pxar/backup 0/6] bump timestamps to 96 bit Dietmar Maurer

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal