From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id E7D1E1FF13B for ; Wed, 22 Apr 2026 13:18:34 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 6709C19593; Wed, 22 Apr 2026 13:14:56 +0200 (CEST) From: "Max R. Carrara" To: pve-devel@lists.proxmox.com Subject: [PATCH pve-storage v1 24/54] tree-wide: replace usages of VZTMPL_EXT_RE_1 with parsing functions Date: Wed, 22 Apr 2026 13:12:50 +0200 Message-ID: <20260422111322.257380-25-m.carrara@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260422111322.257380-1-m.carrara@proxmox.com> References: <20260422111322.257380-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: 1776856341037 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.084 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy 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 Message-ID-Hash: MA555LWNOJPGZSB7VHQMMK54BH5KOZ2L X-Message-ID-Hash: MA555LWNOJPGZSB7VHQMMK54BH5KOZ2L 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, keep the `VZTMPL_EXT_RE_1` regex around for now, since it's public and therefore also part of the storage API. On an APIVER + APIAGE bump, this regex should be marked for removal. Signed-off-by: Max R. Carrara --- src/PVE/API2/Storage/Status.pm | 6 +- src/PVE/Storage.pm | 4 +- src/PVE/Storage/Common/Parse.pm | 23 +++++ src/PVE/Storage/Common/test/parser_tests.pl | 109 ++++++++++++++++++++ src/PVE/Storage/Plugin.pm | 16 +-- 5 files changed, 147 insertions(+), 11 deletions(-) diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm index cf60c148..03929d26 100644 --- a/src/PVE/API2/Storage/Status.pm +++ b/src/PVE/API2/Storage/Status.pm @@ -85,11 +85,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_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 d7d683ad..d5b0b637 100755 --- a/src/PVE/Storage.pm +++ b/src/PVE/Storage.pm @@ -761,9 +761,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_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 bcc6f9fc..b18ef576 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) { diff --git a/src/PVE/Storage/Common/test/parser_tests.pl b/src/PVE/Storage/Common/test/parser_tests.pl index 0167ca74..31575b66 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 590ba3e0..6bfa5c11 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -826,10 +826,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)$!) { @@ -1708,11 +1708,15 @@ my sub get_subdir_files { } if ($vtype eq 'vztmpl') { - return if $filename !~ m!/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!; + my $parts = parse_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