* [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 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