sub pve_verify_vmid {
my ($vmid, $noerr) = @_;
- if ($vmid !~ m/^[1-9][0-9]+$/) {
+ if ($vmid !~ m/^[1-9][0-9]{2,8}$/) {
return undef if $noerr;
die "value does not look like a valid VM ID\n";
}
sub pve_verify_cidrv4 {
my ($cidr, $noerr) = @_;
- if ($cidr =~ m!^(?:$IPV4RE)(?:/(\d+))$! && ($1 > 7) && ($1 < 32)) {
+ if ($cidr =~ m!^(?:$IPV4RE)(?:/(\d+))$! && ($1 > 7) && ($1 <= 32)) {
return $cidr;
}
};
sub parse_property_string {
- my ($format, $data, $path) = @_;
+ my ($format, $data, $path, $additional_properties) = @_;
+
+ # In property strings we default to not allowing additional properties
+ $additional_properties = 0 if !defined($additional_properties);
my $default_key;
if ($part =~ /^([^=]+)=(.+)$/) {
my ($k, $v) = ($1, $2);
- die "duplicate key in comma-separated list property: $k" if defined($res->{$k});
+ die "duplicate key in comma-separated list property: $k\n" if defined($res->{$k});
my $schema = $format->{$k};
- die "invalid key in comma-separated list property: $k" if !$schema;
+ if (my $alias = $schema->{alias}) {
+ $k = $alias;
+ $schema = $format->{$k};
+ }
+ die "invalid key in comma-separated list property: $k\n" if !$schema;
if ($schema->{type} && $schema->{type} eq 'boolean') {
$v = 1 if $v =~ m/^(1|on|yes|true)$/i;
$v = 0 if $v =~ m/^(0|off|no|false)$/i;
}
$res->{$k} = $v;
} elsif ($part !~ /=/) {
- die "duplicate key in comma-separated list property: $default_key" if $default_key;
+ die "duplicate key in comma-separated list property: $default_key\n" if $default_key;
foreach my $key (keys %$format) {
if ($format->{$key}->{default_key}) {
$default_key = $key;
$res->{$default_key} = $part;
last;
}
- die "duplicate key in comma-separated list property: $default_key";
+ die "duplicate key in comma-separated list property: $default_key\n";
}
}
+ die "value without key, but schema does not define a default key\n" if !$default_key;
} else {
- die "missing key in comma-separated list property";
+ die "missing key in comma-separated list property\n";
}
}
my $errors = {};
- check_object($path, $format, $res, undef, $errors);
+ check_object($path, $format, $res, $additional_properties, $errors);
if (scalar(%$errors)) {
- raise "format error", errors => $errors;
+ raise "format error\n", errors => $errors;
}
return $res;
}
+sub print_property_string {
+ my ($data, $format, $skip, $path) = @_;
+
+ if (ref($format) ne 'HASH') {
+ my $schema = $format_list->{$format};
+ die "not a valid format: $format" if !$schema;
+ $format = $schema;
+ }
+
+ my $errors = {};
+ check_object($path, $format, $data, undef, $errors);
+ if (scalar(%$errors)) {
+ raise "format error", errors => $errors;
+ }
+
+ my $default_key;
+ my %skipped = map { $_ => 1 } @$skip;
+ my %allowed;
+ my %required; # this is a set, all present keys are required regardless of value
+ foreach my $key (keys %$format) {
+ $allowed{$key} = 1;
+ if (!$format->{$key}->{optional} && !$format->{$key}->{alias} && !$skipped{$key}) {
+ $required{$key} = 1;
+ }
+
+ # Skip default keys
+ if ($format->{$key}->{default_key}) {
+ if ($default_key) {
+ warn "multiple default keys in schema ($default_key, $key)";
+ } else {
+ $default_key = $key;
+ $skipped{$key} = 1;
+ }
+ }
+ }
+
+ my ($text, $comma);
+ if ($default_key) {
+ $text = "$data->{$default_key}";
+ $comma = ',';
+ } else {
+ $text = '';
+ $comma = '';
+ }
+
+ foreach my $key (sort keys %$data) {
+ delete $required{$key};
+ next if $skipped{$key};
+ die "invalid key: $key" if !$allowed{$key};
+
+ my $typeformat = $format->{$key}->{format};
+ my $value = $data->{$key};
+ next if !defined($value);
+ $text .= $comma;
+ $comma = ',';
+ if ($typeformat && $typeformat eq 'disk-size') {
+ $text .= "$key=" . format_size($value);
+ } else {
+ $text .= "$key=$value";
+ }
+ }
+
+ if (my $missing = join(',', keys %required)) {
+ die "missing properties: $missing";
+ }
+
+ return $text;
+}
+
sub add_error {
my ($errors, $path, $msg) = @_;
if (!defined ($value)) {
return if $schema->{type} && $schema->{type} eq 'null';
- if (!$schema->{optional}) {
+ if (!$schema->{optional} && !$schema->{alias}) {
add_error($errors, $path, "property is missing and it is not optional");
}
return;
optional => 1,
description => "Whether this is the default key in a comma separated list property string.",
},
+ alias => {
+ type => 'string',
+ optional => 1,
+ description => "When a key represents the same property as another it can be an alias to it, causing the parsed datastructure to use the other key to store the current value under.",
+ },
default => {
type => "any",
optional => 1,