From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id D3582A3BB for ; Mon, 4 Apr 2022 18:22:16 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id C9384181BB for ; Mon, 4 Apr 2022 18:21:46 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 680DE181B0 for ; Mon, 4 Apr 2022 18:21:45 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 3179D458EB for ; Mon, 4 Apr 2022 18:21:45 +0200 (CEST) From: Dylan Whyte To: pbs-devel@lists.proxmox.com Date: Mon, 4 Apr 2022 18:19:20 +0200 Message-Id: <20220404161920.241543-1-d.whyte@proxmox.com> X-Mailer: git-send-email 2.30.2 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.358 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pbs-devel] [PATCH proxmox-backup] fix #3613: catalog_shell: include matched dir's contents on restore X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 04 Apr 2022 16:22:16 -0000 Prior to this, during an interactive restore, if a directory was matched via a pattern match or selection, only the empty directory would be restored, and not its contents. Now the entire contents is restored on directory match Signed-off-by: Dylan Whyte --- pbs-client/src/catalog_shell.rs | 56 ++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/pbs-client/src/catalog_shell.rs b/pbs-client/src/catalog_shell.rs index c9c9da67..d0658def 100644 --- a/pbs-client/src/catalog_shell.rs +++ b/pbs-client/src/catalog_shell.rs @@ -997,6 +997,9 @@ impl Shell { &self.accessor, )?; + // Clear match list following restore + self.selected.clear(); + extractor.extract().await } } @@ -1007,9 +1010,7 @@ struct ExtractorState<'a> { path_len_stack: Vec, dir_stack: Vec, - - matches: bool, - matches_stack: Vec, + match_pos: Option, read_dir: as IntoIterator>::IntoIter, read_dir_stack: Vec< as IntoIterator>::IntoIter>, @@ -1029,6 +1030,7 @@ impl<'a> ExtractorState<'a> { match_list: &'a [MatchEntry], accessor: &'a Accessor, ) -> Result { + let match_pos = None; let read_dir = catalog .read_dir(&dir_stack.last().unwrap().catalog)? .into_iter(); @@ -1038,9 +1040,7 @@ impl<'a> ExtractorState<'a> { path_len_stack: Vec::new(), dir_stack, - - matches: match_list.is_empty(), - matches_stack: Vec::new(), + match_pos, read_dir, read_dir_stack: Vec::new(), @@ -1084,11 +1084,6 @@ impl<'a> ExtractorState<'a> { None => return Ok(ControlFlow::Break(())), // out of root directory }; - self.matches = self - .matches_stack - .pop() - .ok_or_else(|| format_err!("internal iterator error (matches_stack)"))?; - self.dir_stack .pop() .ok_or_else(|| format_err!("internal iterator error (dir_stack)"))?; @@ -1106,14 +1101,14 @@ impl<'a> ExtractorState<'a> { async fn handle_new_directory( &mut self, entry: catalog::DirEntry, - match_result: Option, + create_dir: bool, ) -> Result<(), Error> { // enter a new directory: self.read_dir_stack.push(mem::replace( &mut self.read_dir, self.catalog.read_dir(&entry)?.into_iter(), )); - self.matches_stack.push(self.matches); + self.dir_stack.push(PathStackEntry::new(entry)); self.path_len_stack.push(self.path_len); self.path_len = self.path.len(); @@ -1121,23 +1116,46 @@ impl<'a> ExtractorState<'a> { Shell::walk_pxar_archive(self.accessor, &mut self.dir_stack).await?; let dir_pxar = self.dir_stack.last().unwrap().pxar.as_ref().unwrap(); let dir_meta = dir_pxar.entry().metadata().clone(); - let create = self.matches && match_result != Some(MatchType::Exclude); - self.extractor.enter_directory(dir_pxar.file_name().to_os_string(), dir_meta, create)?; + self.extractor.enter_directory(dir_pxar.file_name().to_os_string(), dir_meta, create_dir)?; Ok(()) } pub async fn handle_entry(&mut self, entry: catalog::DirEntry) -> Result<(), Error> { let match_result = self.match_list.matches(&self.path, entry.get_file_mode()); + + let current_pos = self.dir_stack.len(); + let did_match = match match_result { - Some(MatchType::Include) => true, - Some(MatchType::Exclude) => false, - None => self.matches, + Some(MatchType::Include) => { + // Only update match position for items lower in the directory stack + if let Some(match_pos) = self.match_pos { + self.match_pos = Some(usize::min(current_pos, match_pos)); + } else { + self.match_pos = Some(current_pos) + } + true + }, + Some(MatchType::Exclude) => return Ok(()), // no need to continue for excluded item + None => { + // Restore items contained within a matched directory + if let Some(match_pos) = self.match_pos { + if current_pos > match_pos { + true + } else { + self.match_pos = None; + false + } + } else { + // Restore everything if no matches were specified + self.match_list.is_empty() + } + } }; match (did_match, &entry.attr) { (_, DirEntryAttribute::Directory { .. }) => { - self.handle_new_directory(entry, match_result).await?; + self.handle_new_directory(entry, did_match).await?; } (true, DirEntryAttribute::File { .. }) => { self.dir_stack.push(PathStackEntry::new(entry)); -- 2.30.2