* [pbs-devel] [PATCH v2 many] fix #4995: Include symlinks in zip file restore
@ 2023-11-23 13:06 Filip Schauer
2023-11-23 13:06 ` [pbs-devel] [PATCH v2 proxmox 1/1] fix #4995: compression: " Filip Schauer
2023-11-23 13:06 ` [pbs-devel] [PATCH v2 backup 1/1] fix #4995: pxar: " Filip Schauer
0 siblings, 2 replies; 6+ messages in thread
From: Filip Schauer @ 2023-11-23 13:06 UTC (permalink / raw)
To: pbs-devel
Include symlinks when restoring files from a backup as a zip file.
Changes since v1:
* Use P instead of &Path
* Fix compile error due to misplaced comma
* Check content before symlink_target, since regular files are more
common than symlinks
proxmox:
Filip Schauer (1):
fix #4995: compression: Include symlinks in zip file restore
proxmox-compression/src/zip.rs | 46 ++++++++++++++++++++++++++--------
1 file changed, 35 insertions(+), 11 deletions(-)
proxmox-backup:
Filip Schauer (1):
fix #4995: pxar: Include symlinks in zip file restore
pbs-client/src/pxar/extract.rs | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
--
2.39.2
^ permalink raw reply [flat|nested] 6+ messages in thread
* [pbs-devel] [PATCH v2 proxmox 1/1] fix #4995: compression: Include symlinks in zip file restore
2023-11-23 13:06 [pbs-devel] [PATCH v2 many] fix #4995: Include symlinks in zip file restore Filip Schauer
@ 2023-11-23 13:06 ` Filip Schauer
2023-11-24 7:56 ` Dominik Csapak
2023-11-23 13:06 ` [pbs-devel] [PATCH v2 backup 1/1] fix #4995: pxar: " Filip Schauer
1 sibling, 1 reply; 6+ messages in thread
From: Filip Schauer @ 2023-11-23 13:06 UTC (permalink / raw)
To: pbs-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
proxmox-compression/src/zip.rs | 46 ++++++++++++++++++++++++++--------
1 file changed, 35 insertions(+), 11 deletions(-)
diff --git a/proxmox-compression/src/zip.rs b/proxmox-compression/src/zip.rs
index d2d3fd8..e30f50a 100644
--- a/proxmox-compression/src/zip.rs
+++ b/proxmox-compression/src/zip.rs
@@ -204,6 +204,7 @@ pub struct ZipEntry {
offset: u64,
is_file: bool,
is_utf8_filename: bool,
+ symlink_target: Option<OsString>,
}
impl ZipEntry {
@@ -211,7 +212,13 @@ impl ZipEntry {
///
/// if is_file is false the path will contain an trailing separator,
/// so that the zip file understands that it is a directory
- pub fn new<P: AsRef<Path>>(path: P, mtime: i64, mode: u16, is_file: bool) -> Self {
+ pub fn new<P: AsRef<Path>>(
+ path: P,
+ mtime: i64,
+ mode: u16,
+ is_file: bool,
+ symlink_target: Option<P>,
+ ) -> Self {
let mut relpath = PathBuf::new();
for comp in path.as_ref().components() {
@@ -226,6 +233,7 @@ impl ZipEntry {
let filename: OsString = relpath.into();
let is_utf8_filename = filename.to_str().is_some();
+ let symlink_target_osstr = symlink_target.map(|x| x.as_ref().into());
Self {
filename,
@@ -237,6 +245,7 @@ impl ZipEntry {
offset: 0,
is_file,
is_utf8_filename,
+ symlink_target: symlink_target_osstr,
}
}
@@ -360,7 +369,9 @@ impl ZipEntry {
comment_len: 0,
start_disk: 0,
internal_flags: 0,
- external_flags: (self.mode as u32) << 16 | (!self.is_file as u32) << 4,
+ external_flags: (self.mode as u32) << 16
+ | (self.symlink_target.is_some() as u32) << 5
+ | (!self.is_file as u32) << 4,
offset,
},
)
@@ -486,23 +497,30 @@ impl<W: AsyncWrite + Unpin> ZipEncoder<W> {
.ok_or_else(|| format_err!("had no target during add entry"))?;
entry.offset = self.byte_count.try_into()?;
self.byte_count += entry.write_local_header(&mut target).await?;
- if let Some(content) = content {
- let mut reader = HashWrapper::new(content);
+
+ if content.is_some() || entry.symlink_target.is_some() {
let mut enc = DeflateEncoder::with_quality(target, Level::Fastest);
- enc.compress(&mut reader).await?;
+ if let Some(content) = content {
+ let mut reader = HashWrapper::new(content);
+ enc.compress(&mut reader).await?;
+ entry.crc32 = reader.finish().0;
+ } else if let Some(symlink_target) = entry.symlink_target.as_ref() {
+ let cursor = std::io::Cursor::new(symlink_target.as_bytes());
+ let mut reader = HashWrapper::new(cursor);
+ enc.compress(&mut reader).await?;
+ entry.crc32 = reader.finish().0;
+ }
+
let total_in = enc.total_in();
let total_out = enc.total_out();
target = enc.into_inner();
- let (crc32, _reader) = reader.finish();
-
self.byte_count += total_out as usize;
entry.compressed_size = total_out;
entry.uncompressed_size = total_in;
-
- entry.crc32 = crc32;
}
+
self.byte_count += entry.write_data_descriptor(&mut target).await?;
self.target = Some(target);
@@ -658,10 +676,16 @@ where
if entry.file_type().is_file() {
let file = tokio::fs::File::open(entry.path()).await?;
- let ze = ZipEntry::new(entry_path_no_base, mtime, mode, true);
+ let ze = ZipEntry::new(entry_path_no_base, mtime, mode, true, None);
Ok(Some((ze, Some(file))))
} else if entry.file_type().is_dir() {
- let ze = ZipEntry::new(entry_path_no_base, mtime, mode, false);
+ let ze = ZipEntry::new(entry_path_no_base, mtime, mode, false, None);
+ let content: Option<tokio::fs::File> = None;
+ Ok(Some((ze, content)))
+ } else if entry.file_type().is_symlink() {
+ let target = std::fs::read_link(entry.path())?;
+ let ze =
+ ZipEntry::new(entry_path_no_base, mtime, mode, true, Some(target.as_ref()));
let content: Option<tokio::fs::File> = None;
Ok(Some((ze, content)))
} else {
--
2.39.2
^ permalink raw reply [flat|nested] 6+ messages in thread
* [pbs-devel] [PATCH v2 backup 1/1] fix #4995: pxar: Include symlinks in zip file restore
2023-11-23 13:06 [pbs-devel] [PATCH v2 many] fix #4995: Include symlinks in zip file restore Filip Schauer
2023-11-23 13:06 ` [pbs-devel] [PATCH v2 proxmox 1/1] fix #4995: compression: " Filip Schauer
@ 2023-11-23 13:06 ` Filip Schauer
1 sibling, 0 replies; 6+ messages in thread
From: Filip Schauer @ 2023-11-23 13:06 UTC (permalink / raw)
To: pbs-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
| 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
--git a/pbs-client/src/pxar/extract.rs b/pbs-client/src/pxar/extract.rs
index f78e06c2..6f23c144 100644
--- a/pbs-client/src/pxar/extract.rs
+++ b/pbs-client/src/pxar/extract.rs
@@ -998,6 +998,7 @@ where
metadata.stat.mtime.secs,
metadata.stat.mode as u16,
false,
+ None,
);
zip.add_entry::<FileContents<T>>(entry, None).await?;
}
@@ -1017,6 +1018,7 @@ where
metadata.stat.mtime.secs,
metadata.stat.mode as u16,
true,
+ None,
);
zip.add_entry(entry, decoder.contents())
.await
@@ -1035,6 +1037,7 @@ where
metadata.stat.mtime.secs,
metadata.stat.mode as u16,
true,
+ None,
);
zip.add_entry(entry, decoder.contents())
.await
@@ -1047,9 +1050,24 @@ where
metadata.stat.mtime.secs,
metadata.stat.mode as u16,
false,
+ None,
);
zip.add_entry::<FileContents<T>>(entry, None).await?;
}
+ EntryKind::Symlink(link) => {
+ log::debug!("adding '{}' to zip", path.display());
+ let realpath = Path::new(link);
+
+ let entry = ZipEntry::new(
+ path,
+ metadata.stat.mtime.secs,
+ metadata.stat.mode as u16,
+ true,
+ Some(realpath),
+ );
+
+ zip.add_entry::<FileContents<T>>(entry, None).await?;
+ }
_ => {} // ignore all else
};
}
--
2.39.2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [pbs-devel] [PATCH v2 proxmox 1/1] fix #4995: compression: Include symlinks in zip file restore
2023-11-23 13:06 ` [pbs-devel] [PATCH v2 proxmox 1/1] fix #4995: compression: " Filip Schauer
@ 2023-11-24 7:56 ` Dominik Csapak
2023-11-24 11:03 ` Lukas Wagner
0 siblings, 1 reply; 6+ messages in thread
From: Dominik Csapak @ 2023-11-24 7:56 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Filip Schauer
a few high level comments (did not look too closely on the code):
* if we change the function/struct parameters anyway wouldn't it make more sense
to add a 'filetype' enum instead of having a 'is_file' bool and a symlink option?
i used the bool because we only had files + dirs, but now we add a third type,
but imho representing the types properly would be better
we even could put the content into the various enum parts
(or even make the ZipEntry an enum altogether?)
i know this refactoring is much more work than slapping just a new parameter on,
but it makes it easier to understand the code and expand it if we need it
(honestly i probably should have done so initially when adding the code)
* what i'm missing here a bit is the source on how to encode symlinks in zip.
the "official" zip spec[0] only talks about (symbolic) links in the description
of a "-UNIX Extra Field" but you simply encode it here into the content
how did you arrive at that solution?
(also generally a commit message is a good idea ;) )
* for these things i'd also like a short comment (does not have to be in the
commit message) on which systems you did test this, e.g. zipinfo/zip/unar on linux
explorer on windows, mac (?), etc.
* if you want to go the extra mile, i guess this would be a good time to add tests
that create a new zip from test data, to see if they don't break with your changes
0: https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.9.TXT
On 11/23/23 14:06, Filip Schauer wrote:
> Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
> ---
> proxmox-compression/src/zip.rs | 46 ++++++++++++++++++++++++++--------
> 1 file changed, 35 insertions(+), 11 deletions(-)
>
> diff --git a/proxmox-compression/src/zip.rs b/proxmox-compression/src/zip.rs
> index d2d3fd8..e30f50a 100644
> --- a/proxmox-compression/src/zip.rs
> +++ b/proxmox-compression/src/zip.rs
> @@ -204,6 +204,7 @@ pub struct ZipEntry {
> offset: u64,
> is_file: bool,
> is_utf8_filename: bool,
> + symlink_target: Option<OsString>,
> }
>
> impl ZipEntry {
> @@ -211,7 +212,13 @@ impl ZipEntry {
> ///
> /// if is_file is false the path will contain an trailing separator,
> /// so that the zip file understands that it is a directory
> - pub fn new<P: AsRef<Path>>(path: P, mtime: i64, mode: u16, is_file: bool) -> Self {
> + pub fn new<P: AsRef<Path>>(
> + path: P,
> + mtime: i64,
> + mode: u16,
> + is_file: bool,
> + symlink_target: Option<P>,
> + ) -> Self {
> let mut relpath = PathBuf::new();
>
> for comp in path.as_ref().components() {
> @@ -226,6 +233,7 @@ impl ZipEntry {
>
> let filename: OsString = relpath.into();
> let is_utf8_filename = filename.to_str().is_some();
> + let symlink_target_osstr = symlink_target.map(|x| x.as_ref().into());
>
> Self {
> filename,
> @@ -237,6 +245,7 @@ impl ZipEntry {
> offset: 0,
> is_file,
> is_utf8_filename,
> + symlink_target: symlink_target_osstr,
> }
> }
>
> @@ -360,7 +369,9 @@ impl ZipEntry {
> comment_len: 0,
> start_disk: 0,
> internal_flags: 0,
> - external_flags: (self.mode as u32) << 16 | (!self.is_file as u32) << 4,
> + external_flags: (self.mode as u32) << 16
> + | (self.symlink_target.is_some() as u32) << 5
> + | (!self.is_file as u32) << 4,
> offset,
> },
> )
> @@ -486,23 +497,30 @@ impl<W: AsyncWrite + Unpin> ZipEncoder<W> {
> .ok_or_else(|| format_err!("had no target during add entry"))?;
> entry.offset = self.byte_count.try_into()?;
> self.byte_count += entry.write_local_header(&mut target).await?;
> - if let Some(content) = content {
> - let mut reader = HashWrapper::new(content);
> +
> + if content.is_some() || entry.symlink_target.is_some() {
> let mut enc = DeflateEncoder::with_quality(target, Level::Fastest);
>
> - enc.compress(&mut reader).await?;
> + if let Some(content) = content {
> + let mut reader = HashWrapper::new(content);
> + enc.compress(&mut reader).await?;
> + entry.crc32 = reader.finish().0;
> + } else if let Some(symlink_target) = entry.symlink_target.as_ref() {
> + let cursor = std::io::Cursor::new(symlink_target.as_bytes());
> + let mut reader = HashWrapper::new(cursor);
> + enc.compress(&mut reader).await?;
> + entry.crc32 = reader.finish().0;
> + }
> +
> let total_in = enc.total_in();
> let total_out = enc.total_out();
> target = enc.into_inner();
>
> - let (crc32, _reader) = reader.finish();
> -
> self.byte_count += total_out as usize;
> entry.compressed_size = total_out;
> entry.uncompressed_size = total_in;
> -
> - entry.crc32 = crc32;
> }
> +
> self.byte_count += entry.write_data_descriptor(&mut target).await?;
> self.target = Some(target);
>
> @@ -658,10 +676,16 @@ where
>
> if entry.file_type().is_file() {
> let file = tokio::fs::File::open(entry.path()).await?;
> - let ze = ZipEntry::new(entry_path_no_base, mtime, mode, true);
> + let ze = ZipEntry::new(entry_path_no_base, mtime, mode, true, None);
> Ok(Some((ze, Some(file))))
> } else if entry.file_type().is_dir() {
> - let ze = ZipEntry::new(entry_path_no_base, mtime, mode, false);
> + let ze = ZipEntry::new(entry_path_no_base, mtime, mode, false, None);
> + let content: Option<tokio::fs::File> = None;
> + Ok(Some((ze, content)))
> + } else if entry.file_type().is_symlink() {
> + let target = std::fs::read_link(entry.path())?;
> + let ze =
> + ZipEntry::new(entry_path_no_base, mtime, mode, true, Some(target.as_ref()));
> let content: Option<tokio::fs::File> = None;
> Ok(Some((ze, content)))
> } else {
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [pbs-devel] [PATCH v2 proxmox 1/1] fix #4995: compression: Include symlinks in zip file restore
2023-11-24 7:56 ` Dominik Csapak
@ 2023-11-24 11:03 ` Lukas Wagner
2023-12-14 14:50 ` Filip Schauer
0 siblings, 1 reply; 6+ messages in thread
From: Lukas Wagner @ 2023-11-24 11:03 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Dominik Csapak,
Filip Schauer
On 11/24/23 08:56, Dominik Csapak wrote:
> * if you want to go the extra mile, i guess this would be a good time to
> add tests
> that create a new zip from test data, to see if they don't break with
> your changes
>
I agree!
I think writing tests for changes should be the default and not be
considered as 'the extra mile' - especially considering that it should
be relatively easy to write tests for this crate (no weird
dependencies, no need to run as a certain user, etc.)
--
- Lukas
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [pbs-devel] [PATCH v2 proxmox 1/1] fix #4995: compression: Include symlinks in zip file restore
2023-11-24 11:03 ` Lukas Wagner
@ 2023-12-14 14:50 ` Filip Schauer
0 siblings, 0 replies; 6+ messages in thread
From: Filip Schauer @ 2023-12-14 14:50 UTC (permalink / raw)
To: Lukas Wagner, Proxmox Backup Server development discussion,
Dominik Csapak
Patch v3 available:
https://lists.proxmox.com/pipermail/pbs-devel/2023-December/007538.html
On 24/11/2023 12:03, Lukas Wagner wrote:
> On 11/24/23 08:56, Dominik Csapak wrote:
>> * if you want to go the extra mile, i guess this would be a good time
>> to add tests
>> that create a new zip from test data, to see if they don't break
>> with your changes
>>
>
> I agree!
>
> I think writing tests for changes should be the default and not be
> considered as 'the extra mile' - especially considering that it should
> be relatively easy to write tests for this crate (no weird
> dependencies, no need to run as a certain user, etc.)
>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2023-12-14 14:50 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-23 13:06 [pbs-devel] [PATCH v2 many] fix #4995: Include symlinks in zip file restore Filip Schauer
2023-11-23 13:06 ` [pbs-devel] [PATCH v2 proxmox 1/1] fix #4995: compression: " Filip Schauer
2023-11-24 7:56 ` Dominik Csapak
2023-11-24 11:03 ` Lukas Wagner
2023-12-14 14:50 ` Filip Schauer
2023-11-23 13:06 ` [pbs-devel] [PATCH v2 backup 1/1] fix #4995: pxar: " Filip Schauer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox