public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [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>
---
 pbs-client/src/pxar/extract.rs | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal