* [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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox