From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 94A4C1FF0AB for ; Wed, 23 Sep 2026 17:16:31 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 3632E216BC; Wed, 23 Sep 2026 17:16:19 +0200 (CEST) From: "Max R. Carrara" To: pve-devel@lists.proxmox.com Subject: [PATCH pve-storage v2 45/50] fix #2884: support nested subdir scanning for 'vztmpl' volume type Date: Wed, 23 Sep 2026 17:16:05 +0200 Message-ID: <20260923151611.544333-2-m.carrara@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260923151611.544333-1-m.carrara@proxmox.com> References: <20260923150606.531239-1-m.carrara@proxmox.com> <20260923151611.544333-1-m.carrara@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1790176573842 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.374 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: ZWSHXAB5RT4SQS553LGNXWCAYWQCL56Y X-Message-ID-Hash: ZWSHXAB5RT4SQS553LGNXWCAYWQCL56Y X-MailFrom: m.carrara@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add support for parsing nested subdirectories for the 'vztmpl' volume type by adapting its corresponding regex used by parsing helpers. Add additional test cases wherever applicable to account for nested subdirectories for 'vztmpl' volumes, as done for the 'iso' volume type. Originally-by: Noel Ullreich Signed-off-by: Max R. Carrara --- src/PVE/Storage/Common/Parse.pm | 3 +- src/PVE/Storage/Common/test/parser_tests.pl | 54 ++++++++++++++ src/PVE/Storage/Plugin.pm | 4 +- src/test/filesystem_path_test.pm | 18 +++++ src/test/list_volumes_test.pm | 81 +++++++++++++++++++++ src/test/parse_volname_test.pm | 57 +++++++++++++++ src/test/path_to_volume_id_test.pm | 8 ++ 7 files changed, 222 insertions(+), 3 deletions(-) diff --git a/src/PVE/Storage/Common/Parse.pm b/src/PVE/Storage/Common/Parse.pm index 1d021afa..86787e75 100644 --- a/src/PVE/Storage/Common/Parse.pm +++ b/src/PVE/Storage/Common/Parse.pm @@ -87,6 +87,7 @@ my $RE_ISO_FILE_PATH = qr! my $RE_VZTMPL_FILE_PATH = qr! (? + (? $RE_DIRECTORY_COMPONENTS )? (? [^/]+ \. @@ -328,7 +329,7 @@ These are: Volume types that support subdirectories in their C part also have a C part if a subdirectory is specified. This is currently limited to the -C volume type. +C and C volume types. The following volume types have more specific parts in addition to the common ones: diff --git a/src/PVE/Storage/Common/test/parser_tests.pl b/src/PVE/Storage/Common/test/parser_tests.pl index 204002d0..7508d0e5 100755 --- a/src/PVE/Storage/Common/test/parser_tests.pl +++ b/src/PVE/Storage/Common/test/parser_tests.pl @@ -230,6 +230,36 @@ my $volname_cases_vztmpl_valid = [ volname => 'vztmpl/Alpine 3.10 default_20190626_amd64.Tar.xZ', }, }, + + # subdirectories + { + path => 'subdir/debian-10.0-standard_10.0-1_amd64.tar.zst', + expected => { + file => 'debian-10.0-standard_10.0-1_amd64.tar.zst', + ext => 'tar.zst', + 'ext-archive' => 'tar', + 'ext-compression' => 'zst', + dir => 'subdir', + 'disk-path' => 'subdir/debian-10.0-standard_10.0-1_amd64.tar.zst', + path => 'subdir/debian-10.0-standard_10.0-1_amd64.tar.zst', + vtype => 'vztmpl', + volname => 'vztmpl/subdir/debian-10.0-standard_10.0-1_amd64.tar.zst', + }, + }, + { + path => 'deeply/nested/dir/debian-11.0-standard_11.0-1_amd64.tar.bz2', + expected => { + file => 'debian-11.0-standard_11.0-1_amd64.tar.bz2', + ext => 'tar.bz2', + 'ext-archive' => 'tar', + 'ext-compression' => 'bz2', + dir => 'deeply/nested/dir', + 'disk-path' => 'deeply/nested/dir/debian-11.0-standard_11.0-1_amd64.tar.bz2', + path => 'deeply/nested/dir/debian-11.0-standard_11.0-1_amd64.tar.bz2', + vtype => 'vztmpl', + volname => 'vztmpl/deeply/nested/dir/debian-11.0-standard_11.0-1_amd64.tar.bz2', + }, + }, ]; my $volname_cases_vztmpl_invalid = [ @@ -241,6 +271,30 @@ my $volname_cases_vztmpl_invalid = [ }, expected => undef, }, + { + description => "Parent dir reference in path (beginning) (vztmpl)", + args => { + path => '../archlinux-base_20190924-1_amd64.tar.gz', + vtype => 'vztmpl', + }, + expected => undef, + }, + { + description => "Parent dir reference in path (middle) (vztmpl)", + args => { + path => 'subdir/../archlinux-base_20190924-1_amd64.tar.gz', + vtype => 'vztmpl', + }, + expected => undef, + }, + { + description => "Parent dir reference in path (end) (vztmpl)", + args => { + path => 'subdir/archlinux-base_20190924-1_amd64.tar.gz/..', + vtype => 'vztmpl', + }, + expected => undef, + }, ]; my $volname_cases_backup_valid = [ diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm index e96ab2d5..2e22e6b7 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -236,7 +236,7 @@ my $defaultData = { }, 'max-scan-depth' => { description => "Maximum depth of subdirectories to traverse when searching for" - . " ISOs in directories.", + . " ISOs and container templates in directories.", type => 'integer', default => 0, minimum => 0, @@ -1856,7 +1856,7 @@ sub list_volumes { } if ($type eq 'vztmpl' && !defined($vmid)) { - return get_subdir_files($storeid, $scfg, 'vztmpl', undef); + return get_subdir_files($storeid, $scfg, 'vztmpl', undef, $depth); } if ($type eq 'backup') { diff --git a/src/test/filesystem_path_test.pm b/src/test/filesystem_path_test.pm index 26a74a0d..b5c1ab33 100644 --- a/src/test/filesystem_path_test.pm +++ b/src/test/filesystem_path_test.pm @@ -56,6 +56,24 @@ my $tests = [ 'iso', ], }, + { + volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.tar.gz', + snapname => undef, + expected => [ + "$DEFAULT_STORAGE_DIR/template/cache/debian-10.0-standard_10.0-1_amd64.tar.gz", + undef, + 'vztmpl', + ], + }, + { + volname => 'vztmpl/foo/bar/baz/debian-10.0-standard_10.0-1_amd64.tar.gz', + snapname => undef, + expected => [ + "$DEFAULT_STORAGE_DIR/template/cache/foo/bar/baz/debian-10.0-standard_10.0-1_amd64.tar.gz", + undef, + 'vztmpl', + ], + }, { volname => "backup/vzdump-qemu-1234-2020_03_30-21_12_40.vma", snapname => undef, diff --git a/src/test/list_volumes_test.pm b/src/test/list_volumes_test.pm index b1f72091..4dcadb87 100644 --- a/src/test/list_volumes_test.pm +++ b/src/test/list_volumes_test.pm @@ -1253,6 +1253,25 @@ my $test_param_list = [ file => "$DEFAULT_STORAGE_PATH/template/iso/1/2/3/4/5/some-installer.iso", expected => undef, }, + { + file => "$DEFAULT_STORAGE_PATH/template/cache/some-lxc-template.tar.gz", + expected => { + content => 'vztmpl', + ctime => $DEFAULT_CTIME, + format => 'tgz', + size => $DEFAULT_SIZE, + volid => 'local:vztmpl/some-lxc-template.tar.gz', + }, + }, + { + file => "$DEFAULT_STORAGE_PATH/template/cache/1/some-lxc-template.tar.gz", + expected => undef, + }, + { + file => + "$DEFAULT_STORAGE_PATH/template/cache/1/2/3/4/5/some-lxc-template.tar.gz", + expected => undef, + }, ], }, { @@ -1298,6 +1317,31 @@ my $test_param_list = [ file => "$DEFAULT_STORAGE_PATH/template/iso/1/2/some-installer.iso", expected => undef, }, + { + file => "$DEFAULT_STORAGE_PATH/template/cache/some-lxc-template.tar.gz", + expected => { + content => 'vztmpl', + ctime => $DEFAULT_CTIME, + format => 'tgz', + size => $DEFAULT_SIZE, + volid => 'local:vztmpl/some-lxc-template.tar.gz', + }, + }, + { + file => "$DEFAULT_STORAGE_PATH/template/cache/1/some-lxc-template.tar.gz", + expected => { + content => 'vztmpl', + ctime => $DEFAULT_CTIME, + format => 'tgz', + size => $DEFAULT_SIZE, + volid => 'local:vztmpl/1/some-lxc-template.tar.gz', + }, + }, + { + # Exceeds max-scan-depth + file => "$DEFAULT_STORAGE_PATH/template/cache/1/2/some-lxc-template.tar.gz", + expected => undef, + }, ], }, { @@ -1353,6 +1397,43 @@ my $test_param_list = [ file => "$DEFAULT_STORAGE_PATH/template/iso/1/2/3/4/5/6/some-installer.iso", expected => undef, }, + { + file => "$DEFAULT_STORAGE_PATH/template/cache/some-lxc-template.tar.gz", + expected => { + content => 'vztmpl', + ctime => $DEFAULT_CTIME, + format => 'tgz', + size => $DEFAULT_SIZE, + volid => 'local:vztmpl/some-lxc-template.tar.gz', + }, + }, + { + file => "$DEFAULT_STORAGE_PATH/template/cache/1/some-lxc-template.tar.gz", + expected => { + content => 'vztmpl', + ctime => $DEFAULT_CTIME, + format => 'tgz', + size => $DEFAULT_SIZE, + volid => 'local:vztmpl/1/some-lxc-template.tar.gz', + }, + }, + { + file => + "$DEFAULT_STORAGE_PATH/template/cache/1/2/3/4/5/some-lxc-template.tar.gz", + expected => { + content => 'vztmpl', + ctime => $DEFAULT_CTIME, + format => 'tgz', + size => $DEFAULT_SIZE, + volid => 'local:vztmpl/1/2/3/4/5/some-lxc-template.tar.gz', + }, + }, + { + # Exceeds max-scan-depth + file => + "$DEFAULT_STORAGE_PATH/template/cache/1/2/3/4/5/6/some-lxc-template.tar.gz", + expected => undef, + }, ], }, ]; diff --git a/src/test/parse_volname_test.pm b/src/test/parse_volname_test.pm index 24d1ed0e..b90815c2 100644 --- a/src/test/parse_volname_test.pm +++ b/src/test/parse_volname_test.pm @@ -245,10 +245,67 @@ my $tests = [ 'vztmpl', "$file_name", undef, undef, undef, undef, 'raw', ], }, + { + description => "Container template, $suffix, subdirectory", + volname => "vztmpl/foo/$file_name", + expected => [ + 'vztmpl', "foo/$file_name", undef, undef, undef, undef, 'raw', + ], + }, + { + description => "Container template, $suffix, nested subdirectories", + volname => "vztmpl/foo/bar/baz/$file_name", + expected => [ + 'vztmpl', "foo/bar/baz/$file_name", undef, undef, undef, undef, 'raw', + ], + }, + { + description => + "Container template, $suffix, subdirectory with same name as file", + volname => "vztmpl/$file_name/$file_name", + expected => [ + 'vztmpl', "$file_name/$file_name", undef, undef, undef, undef, 'raw', + ], + }, ); push($tests->@*, @extra_tests); } + + # Failed tests + { + my $file_name = "$prefix.tar.gz"; + + my @extra_failed_tests = ( + { + description => + "Container template, tar.gz, parent directory reference before volume type prefix", + volname => "../vztmpl/$file_name", + expected => "unable to parse directory volume name '../vztmpl/$file_name'\n", + }, + { + description => + "Container template, tar.gz, parent directory reference at beginning of volume path", + volname => "vztmpl/../$file_name", + expected => "unable to parse directory volume name 'vztmpl/../$file_name'\n", + }, + { + description => + "Container template, tar.gz, parent directory reference at end of volume path", + volname => "vztmpl/$file_name/..", + expected => "unable to parse directory volume name 'vztmpl/$file_name/..'\n", + }, + { + description => + "Container template, tar.gz, parent directory reference between dir components of volume path", + volname => "vztmpl/foo/../bar/$file_name", + expected => + "unable to parse directory volume name 'vztmpl/foo/../bar/$file_name'\n", + }, + ); + + push($tests->@*, @extra_failed_tests); + } } # Additional tests for backup files diff --git a/src/test/path_to_volume_id_test.pm b/src/test/path_to_volume_id_test.pm index bc87d289..4dfc68e1 100644 --- a/src/test/path_to_volume_id_test.pm +++ b/src/test/path_to_volume_id_test.pm @@ -137,6 +137,14 @@ my $tests = [ 'vztmpl', 'local:vztmpl/debian-10.0-standard_10.0-1_amd64.tar.gz', ], }, + { + description => 'CT template, tar.gz, nested subdirectories', + file => + "$DEFAULT_STORAGE_DIR/template/cache/foo/bar/debian-10.0-standard_10.0-1_amd64.tar.gz", + expected => [ + 'vztmpl', 'local:vztmpl/foo/bar/debian-10.0-standard_10.0-1_amd64.tar.gz', + ], + }, { description => 'CT template, wrong ending, tar bz2', file => "$DEFAULT_STORAGE_DIR/template/cache/debian-10.0-standard_10.0-1_amd64.tar.bz2", -- 2.47.3