From 212b08e8ec8115390aac7992be629eac19b22cd0 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Wed, 16 May 2012 07:19:54 +0200 Subject: [PATCH] added SectionConfig.pm, base class for Storage and Auth plugins. --- Makefile | 2 +- data/Makefile | 1 + data/PVE/SectionConfig.pm | 436 ++++++++++++++++++++++++++++++++++++++ debian/changelog | 6 + 4 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 data/PVE/SectionConfig.pm diff --git a/Makefile b/Makefile index 5579b3c..f07f79b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ RELEASE=2.0 VERSION=1.0 -PKGREL=27 +PKGREL=28 PACKAGE=libpve-common-perl diff --git a/data/Makefile b/data/Makefile index 67759f9..6d5c5d9 100644 --- a/data/Makefile +++ b/data/Makefile @@ -7,6 +7,7 @@ MAN1DIR=${MANDIR}/man1/ PERLDIR=${PREFIX}/share/perl5 LIB_SOURCES= \ + SectionConfig.pm \ Network.pm \ ProcFSTools.pm \ PodParser.pm \ diff --git a/data/PVE/SectionConfig.pm b/data/PVE/SectionConfig.pm new file mode 100644 index 0000000..c338a93 --- /dev/null +++ b/data/PVE/SectionConfig.pm @@ -0,0 +1,436 @@ +package PVE::SectionConfig; + +use strict; +use warnings; +use Digest::SHA; +use PVE::Exception qw(raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); + +use Data::Dumper; + +my $defaultData = { + options => {}, + plugins => {}, + plugindata => {}, + propertyList => {}, +}; + +sub private { + die "overwrite me"; + return $defaultData; +} + +sub register { + my ($class) = @_; + + my $type = $class->type(); + my $pdata = $class->private(); + + my $plugindata = $class->plugindata(); + $pdata->{plugindata}->{$type} = $plugindata; + $pdata->{plugins}->{$type} = $class; +} + +sub type { + die "overwrite me"; +} + +sub properties { + return {}; +} + +sub options { + return {}; +} + +sub plugindata { + return {}; +} + +sub createSchema { + my ($class) = @_; + + my $pdata = $class->private(); + my $propertyList = $pdata->{propertyList}; + + return { + type => "object", + additionalProperties => 0, + properties => $propertyList, + }; +} + +sub updateSchema { + my ($class) = @_; + + my $pdata = $class->private(); + my $propertyList = $pdata->{propertyList}; + my $plugins = $pdata->{plugins}; + + my $props = {}; + + foreach my $p (keys %$propertyList) { + next if $p eq 'type'; + if (!$propertyList->{$p}->{optional}) { + $props->{$p} = $propertyList->{$p}; + next; + } + foreach my $t (keys %$plugins) { + my $opts = $pdata->{options}->{$t}; + next if !defined($opts->{$p}); + if (!$opts->{$p}->{fixed}) { + $props->{$p} = $propertyList->{$p}; + } + } + } + + $props->{digest} = { + type => 'string', + description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.', + maxLength => 40, + optional => 1, + }; + + $props->{delete} = { + type => 'string', format => 'pve-configid-list', + description => "A list of settings you want to delete.", + maxLength => 4096, + optional => 1, + }; + + return { + type => "object", + additionalProperties => 0, + properties => $props, + }; +} + +sub init { + my ($class) = @_; + + my $pdata = $class->private(); + + foreach my $k (qw(options plugins plugindata propertyList)) { + $pdata->{$k} = {} if !$pdata->{$k}; + } + + my $plugins = $pdata->{plugins}; + my $propertyList = $pdata->{propertyList}; + + foreach my $type (keys %$plugins) { + my $props = $plugins->{$type}->properties(); + foreach my $p (keys %$props) { + die "duplicate property" if defined($propertyList->{$p}); + my $res = $propertyList->{$p} = {}; + my $data = $props->{$p}; + for my $a (keys %$data) { + $res->{$a} = $data->{$a}; + } + $res->{optional} = 1; + } + } + + foreach my $type (keys %$plugins) { + my $opts = $plugins->{$type}->options(); + foreach my $p (keys %$opts) { + die "undefined property '$p'" if !$propertyList->{$p}; + } + $pdata->{options}->{$type} = $opts; + } + + $propertyList->{type}->{type} = 'string'; + $propertyList->{type}->{enum} = [keys %$plugins]; +} + +sub lookup { + my ($class, $type) = @_; + + my $pdata = $class->private(); + my $plugin = $pdata->{plugins}->{$type}; + + die "unknown section type '$type'\n" if !$plugin; + + return $plugin; +} + +sub lookup_types { + my ($class) = @_; + + my $pdata = $class->private(); + + return [ keys %{$pdata->{plugins}} ]; +} + +sub decode_value { + my ($class, $type, $key, $value) = @_; + + return $value; +} + +sub encode_value { + my ($class, $type, $key, $value) = @_; + + return $value; +} + +sub check_value { + my ($class, $type, $key, $value, $storeid, $skipSchemaCheck) = @_; + + my $pdata = $class->private(); + + return $value if $key eq 'type' && $type eq $value; + + my $opts = $pdata->{options}->{$type}; + die "unknown section type '$type'\n" if !$opts; + + die "unexpected property '$key'\n" if !defined($opts->{$key}); + + my $schema = $pdata->{propertyList}->{$key}; + die "unknown property type\n" if !$schema; + + my $ct = $schema->{type}; + + $value = 1 if $ct eq 'boolean' && !defined($value); + + die "got undefined value\n" if !defined($value); + + die "property contains a line feed\n" if $value =~ m/[\n\r]/; + + if (!$skipSchemaCheck) { + my $errors = {}; + PVE::JSONSchema::check_prop($value, $schema, '', $errors); + if (scalar(keys %$errors)) { + die "$errors->{$key}\n" if $errors->{$key}; + die "$errors->{_root}\n" if $errors->{_root}; + die "unknown error\n"; + } + } + + return $value; +} + +sub parse_section_header { + my ($class, $line) = @_; + + if ($line =~ m/^(\S+):\s*(\S+)\s*$/) { + my ($type, $sectionId) = ($1, $2); + my $errmsg = undef; # set if you want to skip whole section + my $config = {}; # to return additional attributes + return ($type, $sectionId, $errmsg, $config); + } + return undef; +} + +sub format_section_header { + my ($class, $type, $sectionId) = @_; + + return "$type: $sectionId\n"; +} + + +sub parse_config { + my ($class, $filename, $raw) = @_; + + my $pdata = $class->private(); + + my $ids = {}; + my $order = {}; + + my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : ''); + + my $pri = 1; + + my $lineno = 0; + + while ($raw && $raw =~ s/^(.*?)(\n|$)//) { + my $line = $1; + $lineno++; + + next if $line =~ m/^\#/; + next if $line =~ m/^\s*$/; + + my $errprefix = "file $filename line $lineno"; + + my ($type, $sectionId, $errmsg, $config) = $class->parse_section_header($line); + if ($config) { + my $ignore = 0; + + my $plugin; + + if ($errmsg) { + $ignore = 1; + chomp $errmsg; + warn "$errprefix (skip section '$sectionId'): $errmsg\n"; + } elsif (!$type) { + $ignore = 1; + warn "$errprefix (skip section '$sectionId'): missing type - internal error\n"; + } else { + if (!($plugin = $pdata->{plugins}->{$type})) { + $ignore = 1; + warn "$errprefix (skip section '$sectionId'): unsupported type '$type'\n"; + } + } + + while ($raw && $raw =~ s/^(.*?)(\n|$)//) { + $line = $1; + $lineno++; + + next if $line =~ m/^\#/; + last if $line =~ m/^\s*$/; + + next if $ignore; # skip + + $errprefix = "file $filename line $lineno"; + + if ($line =~ m/^\s+(\S+)(\s+(.*\S))?\s*$/) { + my ($k, $v) = ($1, $3); + + eval { + die "duplicate attribute\n" if defined($config->{$k}); + $config->{$k} = $plugin->check_value($type, $k, $v, $sectionId); + }; + warn "$errprefix (section '$sectionId') - unable to parse value of '$k': $@" if $@; + + } else { + warn "$errprefix (section '$sectionId') - ignore config line: $line\n"; + } + } + + if (!$ignore && $type && $plugin && $config) { + $config->{type} = $type; + eval { $ids->{$sectionId} = $plugin->check_config($sectionId, $config, 1, 1); }; + warn "$errprefix (skip section '$sectionId'): $@" if $@; + $order->{$sectionId} = $pri++; + } + + } else { + warn "$errprefix - ignore config line: $line\n"; + } + } + + + my $cfg = { ids => $ids, order => $order, digest => $digest}; + + return $cfg; +} + +sub check_config { + my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_; + + my $type = $class->type(); + my $pdata = $class->private(); + my $opts = $pdata->{options}->{$type}; + + my $settings = { type => $type }; + + foreach my $k (keys %$config) { + my $value = $config->{$k}; + + die "can't change value of fixed parameter '$k'\n" + if !$create && $opts->{$k}->{fixed}; + + if (defined($value)) { + my $tmp = $class->check_value($type, $k, $value, $sectionId, $skipSchemaCheck); + $settings->{$k} = $class->decode_value($type, $k, $tmp); + } else { + die "got undefined value for option '$k'\n"; + } + } + + if ($create) { + # check if we have a value for all required options + foreach my $k (keys %$opts) { + next if $opts->{$k}->{optional}; + die "missing value for required option '$k'\n" + if !defined($config->{$k}); + } + } + + return $settings; +} + +my $format_config_line = sub { + my ($schema, $key, $value) = @_; + + my $ct = $schema->{type}; + + if ($ct eq 'boolean') { + return $value ? "\t$key\n" : ''; + } else { + return "\t$key $value\n" if "$value" ne ''; + } +}; + +sub write_config { + my ($class, $filename, $cfg) = @_; + + my $pdata = $class->private(); + my $propertyList = $pdata->{propertyList}; + + my $out = ''; + + my $ids = $cfg->{ids}; + my $order = $cfg->{order}; + + my $maxpri = 0; + foreach my $sectionId (keys %$ids) { + my $pri = $order->{$sectionId}; + $maxpri = $pri if $pri && $pri > $maxpri; + } + foreach my $sectionId (keys %$ids) { + if (!defined ($order->{$sectionId})) { + $order->{$sectionId} = ++$maxpri; + } + } + + foreach my $sectionId (sort {$order->{$a} <=> $order->{$b}} keys %$ids) { + my $scfg = $ids->{$sectionId}; + my $type = $scfg->{type}; + my $opts = $pdata->{options}->{$type}; + + die "unknown section type '$type'\n" if !$opts; + + my $data = $class->format_section_header($type, $sectionId); + if ($scfg->{comment}) { + my $k = 'comment'; + my $v = $class->encode_value($type, $k, $scfg->{$k}); + $data .= &$format_config_line($propertyList->{$k}, $k, $v); + } + + $data .= "\tdisable\n" if $scfg->{disable}; + + my $done_hash = { comment => 1, disable => 1}; + + foreach my $k (keys %$opts) { + next if $opts->{$k}->{optional}; + $done_hash->{$k} = 1; + my $v = $scfg->{$k}; + die "section '$sectionId' - missing value for required option '$k'\n" + if !defined ($v); + $v = $class->encode_value($type, $k, $v); + $data .= &$format_config_line($propertyList->{$k}, $k, $v); + } + + foreach my $k (keys %$opts) { + next if defined($done_hash->{$k}); + my $v = $scfg->{$k}; + next if !defined($v); + $v = $class->encode_value($type, $k, $v); + $data .= &$format_config_line($propertyList->{$k}, $k, $v); + } + + $out .= "$data\n"; + } + + return $out; +} + +sub assert_if_modified { + my ($cfg, $digest) = @_; + + if ($digest && ($cfg->{digest} ne $digest)) { + die "detected modified configuration - file change by other user? Try again.\n"; + } +} + +1; diff --git a/debian/changelog b/debian/changelog index 504f26a..ce85e0d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +libpve-common-perl (1.0-28) unstable; urgency=low + + * added SectionConfig.pm, base class for Storage and Auth plugins. + + -- Proxmox Support Team Wed, 16 May 2012 07:18:53 +0200 + libpve-common-perl (1.0-27) unstable; urgency=low * fix bug #172: use DNS compatible regex in upid parser -- 2.39.2