From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 8B1D11FF0AB for ; Wed, 23 Sep 2026 17:10:13 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id C006921671; Wed, 23 Sep 2026 17:07:22 +0200 (CEST) From: "Max R. Carrara" To: pve-devel@lists.proxmox.com Subject: [PATCH pve-storage v2 24/50] tree-wide: replace usages of VZTMPL_EXT_RE_1 with parsing functions Date: Wed, 23 Sep 2026 17:05:38 +0200 Message-ID: <20260923150606.531239-25-m.carrara@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260923150606.531239-1-m.carrara@proxmox.com> References: <20260923150606.531239-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: 1790176017013 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.423 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: FXF3LBVTN3E4IPZ4LXA7JCNVL2HKYXZG X-Message-ID-Hash: FXF3LBVTN3E4IPZ4LXA7JCNVL2HKYXZG 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 the 'vztmpl' volume type in `PVE::Storage::Common::Parse`. Also add corresponding test cases. Use the module's parsing functions to replace usages of the `PVE::Storage::VZTMPL_EXT_RE_1` regex across the repository. As done with the `ISO_EXT_RE_0` regex, note the removal of the `VZTMPL_EXT_RE_1` regex in `ApiChangeLog`, since it's now completely unused. Add a FIXME to note that it should be removed on the next APIAGE reset. Signed-off-by: Max R. Carrara --- ApiChangeLog | 1 + src/PVE/API2/Storage/Status.pm | 6 +- src/PVE/Storage.pm | 5 +- src/PVE/Storage/Common/Parse.pm | 54 +++++++++- src/PVE/Storage/Common/test/parser_tests.pl | 109 ++++++++++++++++++++ src/PVE/Storage/Plugin.pm | 16 +-- 6 files changed, 179 insertions(+), 12 deletions(-) diff --git a/ApiChangeLog b/ApiChangeLog index 7f79efb5..ffe3725c 100644 --- a/ApiChangeLog +++ b/ApiChangeLog @@ -30,6 +30,7 @@ Future changes should be documented in here. * Remove the following regular expressions: * `$PVE::Storage::ISO_EXT_RE_0` (`iso` volume type) + * `$PVE::Storage::VZTMPL_EXT_RE_1` (`vztmpl` volume type) These regular expressions were mostly used for parsing file paths and volume names corresponding to their volume types, noted in parentheses above. diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm index 310677d6..60f08628 100644 --- a/src/PVE/API2/Storage/Status.pm +++ b/src/PVE/API2/Storage/Status.pm @@ -87,11 +87,13 @@ my sub parse_transferred_file_path_extension : prototype($$) { } if ($vtype eq 'vztmpl') { - if ($path !~ m![^/]+$PVE::Storage::VZTMPL_EXT_RE_1$!) { + my $parts = parse_rel_path_as_volname_parts($path, $vtype); + + if (!defined($parts)) { raise_param_exc({ filename => "wrong file extension" }); } - my $ext = $1; + my $ext = $parts->{ext}; return $ext; } diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm index 5358be3f..afe29178 100755 --- a/src/PVE/Storage.pm +++ b/src/PVE/Storage.pm @@ -123,6 +123,7 @@ PVE::Storage::Plugin->init(); # FIXME: remove this regex on the next APIAGE reset. our $ISO_EXT_RE_0 = qr/\.(?:iso|img)/i; +# FIXME: remove this regex on the next APIAGE reset. our $VZTMPL_EXT_RE_1 = qr/\.(?|(tar)(?!\.)|tar\.(gz|xz|zst|bz2))/i; our $BACKUP_EXT_RE_2 = qr/\.(tgz|(?:tar|vma)(?:\.(${\PVE::Storage::Plugin::COMPRESSOR_RE}))?)/; @@ -762,9 +763,7 @@ sub path_to_volume_id { } if ($vtype eq 'vztmpl') { - return if $filename !~ m!/([^/]+$VZTMPL_EXT_RE_1)$!; - my $name = $1; - return "$sid:vztmpl/$name"; + return parse_abs_path_as_volid($sid, $scfg, $path, $vtype); } if ($vtype eq 'backup') { diff --git a/src/PVE/Storage/Common/Parse.pm b/src/PVE/Storage/Common/Parse.pm index 1141a715..207f287b 100644 --- a/src/PVE/Storage/Common/Parse.pm +++ b/src/PVE/Storage/Common/Parse.pm @@ -33,6 +33,14 @@ primarily related to. =cut +my @VZTMPL_COMPRESSION_EXTENSIONS = ('gz', 'xz', 'zst', 'bz2'); + +my sub join_to_re_alternations(@list) { + return join('|', map { quotemeta } @list); +} + +my $RE_VZTMPL_COMPRESSION_EXTENSIONS = join_to_re_alternations(@VZTMPL_COMPRESSION_EXTENSIONS); + my $RE_PARENT_DIR = quotemeta('..'); my $RE_CONTAINS_PARENT_DIR = qr! ( ^$RE_PARENT_DIR/ ) # ../ --> Beginning of path @@ -48,12 +56,27 @@ my $RE_ISO_FILE_PATH = qr! ) !xn; +my $RE_VZTMPL_FILE_PATH = qr! + (? + (? + [^/]+ + \. + (? + (? (?i: tar) ) + ( \. (? (?i: $RE_VZTMPL_COMPRESSION_EXTENSIONS)) )? + ) + ) + ) +!xn; + my $RE_FILE_PATH_FOR_VTYPE = { iso => qr/^$RE_ISO_FILE_PATH$/, + vztmpl => qr/^$RE_VZTMPL_FILE_PATH$/, }; my $RE_VOLNAME_FOR_VTYPE = { iso => qr/^$RE_ISO_FILE_PATH$/, + vztmpl => qr/^$RE_VZTMPL_FILE_PATH$/, }; my sub contains_parent_dir($path) { @@ -132,6 +155,31 @@ These are: volname => 'iso/custom-debian.iso', } +The following volume types have more specific parts in addition to the common +ones: + +=over + +=item C + +C, C (optional): + + { + file => 'archlinux-base_20190924-1_amd64.tar.gz', + ext => 'tar.gz', + + # Used to further differentiate 'ext' + 'ext-archive' => 'tar', + 'ext-compression' => 'gz', + + 'disk-path' => 'archlinux-base_20190924-1_amd64.tar.gz', + path => 'archlinux-base_20190924-1_amd64.tar.gz', + vtype => 'vztmpl', + volname => 'vztmpl/archlinux-base_20190924-1_amd64.tar.gz', + } + +=back + =cut =head3 parse_rel_path_as_volname_parts @@ -151,7 +199,7 @@ into: volname => 'iso/custom-debian.iso', } -See L for more details. +See L for more details and special cases. On failure or when the provided C<$vtype> is not supported or does not exist, returns C in scalar context, and an empty list in list context. @@ -281,6 +329,10 @@ These are: volid => 'local:iso/custom-debian.iso', } +The special cases described in L also apply for the parser +functions in this section. In fact, the volid parsing functions here build on +the volname ones. + =cut my $RE_VOLID = qr! diff --git a/src/PVE/Storage/Common/test/parser_tests.pl b/src/PVE/Storage/Common/test/parser_tests.pl index c19456de..1ea8df1a 100755 --- a/src/PVE/Storage/Common/test/parser_tests.pl +++ b/src/PVE/Storage/Common/test/parser_tests.pl @@ -86,12 +86,121 @@ my $volname_cases_iso_invalid = [ }, ]; +my $volname_cases_vztmpl_valid = [ + # plain paths + { + path => 'archlinux-base_20190924-1_amd64.tar', + expected => { + file => 'archlinux-base_20190924-1_amd64.tar', + ext => 'tar', + 'ext-archive' => 'tar', + 'disk-path' => 'archlinux-base_20190924-1_amd64.tar', + path => 'archlinux-base_20190924-1_amd64.tar', + vtype => 'vztmpl', + volname => 'vztmpl/archlinux-base_20190924-1_amd64.tar', + }, + }, + { + path => 'archlinux-base_20190924-1_amd64.tar.gz', + expected => { + file => 'archlinux-base_20190924-1_amd64.tar.gz', + ext => 'tar.gz', + 'ext-archive' => 'tar', + 'ext-compression' => 'gz', + 'disk-path' => 'archlinux-base_20190924-1_amd64.tar.gz', + path => 'archlinux-base_20190924-1_amd64.tar.gz', + vtype => 'vztmpl', + volname => 'vztmpl/archlinux-base_20190924-1_amd64.tar.gz', + }, + }, + { + path => 'alpine-3.10-default_20190626_amd64.tar.xz', + expected => { + file => 'alpine-3.10-default_20190626_amd64.tar.xz', + ext => 'tar.xz', + 'ext-archive' => 'tar', + 'ext-compression' => 'xz', + 'disk-path' => 'alpine-3.10-default_20190626_amd64.tar.xz', + path => 'alpine-3.10-default_20190626_amd64.tar.xz', + vtype => 'vztmpl', + volname => 'vztmpl/alpine-3.10-default_20190626_amd64.tar.xz', + }, + }, + { + path => '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', + 'disk-path' => 'debian-10.0-standard_10.0-1_amd64.tar.zst', + path => 'debian-10.0-standard_10.0-1_amd64.tar.zst', + vtype => 'vztmpl', + volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.tar.zst', + }, + }, + { + path => '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', + 'disk-path' => 'debian-11.0-standard_11.0-1_amd64.tar.bz2', + path => 'debian-11.0-standard_11.0-1_amd64.tar.bz2', + vtype => 'vztmpl', + volname => 'vztmpl/debian-11.0-standard_11.0-1_amd64.tar.bz2', + }, + }, + + # case-insensitive file extensions + { + path => 'ARCHLINUX-BASE 20190924-1 AMD64.TAR.GZ', + expected => { + file => 'ARCHLINUX-BASE 20190924-1 AMD64.TAR.GZ', + ext => 'TAR.GZ', + 'ext-archive' => 'TAR', + 'ext-compression' => 'GZ', + 'disk-path' => 'ARCHLINUX-BASE 20190924-1 AMD64.TAR.GZ', + path => 'ARCHLINUX-BASE 20190924-1 AMD64.TAR.GZ', + vtype => 'vztmpl', + volname => 'vztmpl/ARCHLINUX-BASE 20190924-1 AMD64.TAR.GZ', + }, + }, + { + path => 'Alpine 3.10 default_20190626_amd64.Tar.xZ', + expected => { + file => 'Alpine 3.10 default_20190626_amd64.Tar.xZ', + ext => 'Tar.xZ', + 'ext-archive' => 'Tar', + 'ext-compression' => 'xZ', + 'disk-path' => 'Alpine 3.10 default_20190626_amd64.Tar.xZ', + path => 'Alpine 3.10 default_20190626_amd64.Tar.xZ', + vtype => 'vztmpl', + volname => 'vztmpl/Alpine 3.10 default_20190626_amd64.Tar.xZ', + }, + }, +]; + +my $volname_cases_vztmpl_invalid = [ + { + description => "Invalid file extension (vztmpl)", + args => { + path => 'archlinux-base_20190924-1_amd64.zip', + vtype => 'vztmpl', + }, + expected => undef, + }, +]; + my $cases_valid_all = [ $volname_cases_iso_valid, + $volname_cases_vztmpl_valid, ]; my $cases_invalid_all = [ $volname_cases_iso_invalid, + $volname_cases_vztmpl_invalid, ]; { diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm index d4860344..b539ef78 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -831,10 +831,10 @@ sub parse_volname { if ($vtype eq 'iso') { return ($vtype, $volume_path, undef, undef, undef, undef, 'raw'); } - } - if ($volname =~ m!^vztmpl/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!) { - return ('vztmpl', $1, undef, undef, undef, undef, 'raw'); + if ($vtype eq 'vztmpl') { + return ($vtype, $volume_path, undef, undef, undef, undef, 'raw'); + } } if ($volname =~ m!^backup/([^/]+$PVE::Storage::BACKUP_EXT_RE_2)$!) { @@ -1734,11 +1734,15 @@ my sub get_subdir_files { } if ($vtype eq 'vztmpl') { - return if $filename !~ m!/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!; + my $parts = parse_abs_path_as_volid_parts($storeid, $scfg, $path, $vtype); + return if !defined($parts); + + my ($ext, $ext_compression) = $parts->@{qw(ext ext-compression)}; + my $format = $ext eq 'tar' ? $ext : ('t' . $ext_compression); return { - volid => "$storeid:vztmpl/$1", - format => $2 eq 'tar' ? $2 : "t$2", + volid => $parts->{volid}, + format => $format, }; } -- 2.47.3