diff --git a/backend/GNUmakefile.in b/backend/GNUmakefile.in index 4c64d1a81e8d06e4d8267b702fd58c8d1da3fc17..0920c92f4fe38a2eecda3fb55e05dda27bc3d425 100644 --- a/backend/GNUmakefile.in +++ b/backend/GNUmakefile.in @@ -12,8 +12,10 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@ include $(OBJDIR)/Makeconf -BIN_SCRIPTS = moduserinfo newgroup newmmlist editexp editimageid -WEB_BIN_SCRIPTS = webmoduserinfo webnewgroup webnewmmlist webeditimageid +BIN_SCRIPTS = moduserinfo newgroup newmmlist editexp editimageid \ + editnodetype +WEB_BIN_SCRIPTS = webmoduserinfo webnewgroup webnewmmlist webeditimageid \ + webeditnodetype WEB_SBIN_SCRIPTS= LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS) diff --git a/backend/editnodetype.in b/backend/editnodetype.in new file mode 100644 index 0000000000000000000000000000000000000000..01b3d4ce12de9f4294190dd750c9186fc5d7ae5a --- /dev/null +++ b/backend/editnodetype.in @@ -0,0 +1,552 @@ +#!/usr/bin/perl -wT +# +# EMULAB-COPYRIGHT +# Copyright (c) 2000-2007 University of Utah and the Flux Group. +# All rights reserved. +# +use English; +use strict; +use Getopt::Std; +use XML::Simple; +use Data::Dumper; + +# +# Back-end script to create or edit a nodetype. +# +sub usage() +{ + print("Usage: editnodetype [-v] <xmlfile>\n"); + exit(-1); +} +my $optlist = "dv"; +my $debug = 0; +my $verify = 0; # Check data and return status only. + +# +# Configure variables +# +my $TB = "@prefix@"; +my $TBOPS = "@TBOPSEMAIL@"; +my $TBAUDIT = "@TBAUDITEMAIL@"; + +# +# Untaint the path +# +$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin"; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + +# +# Turn off line buffering on output +# +$| = 1; + +# +# Load the Testbed support stuff. +# +use lib "@prefix@/lib"; +use libdb; +use libtestbed; +use User; +use Project; +use OSinfo; + +# Protos +sub fatal($); +sub UserError(;$); +sub escapeshellarg($); + +# +# Parse command arguments. Once we return from getopts, all that should be +# left are the required arguments. +# +my %options = (); +if (! getopts($optlist, \%options)) { + usage(); +} +if (defined($options{"d"})) { + $debug = 1; +} +if (defined($options{"v"})) { + $verify = 1; +} +if (@ARGV != 1) { + usage(); +} +my $xmlfile = shift(@ARGV); + +# +# Map invoking user to object. +# If invoked as "nobody" we are coming from the web interface and the +# current user context is "implied" (see tbauth.php3). +# +my $this_user; + +if (getpwuid($UID) ne "nobody") { + $this_user = User->ThisUser(); + + if (! defined($this_user)) { + fatal("You ($UID) do not exist!"); + } + fatal("You must have admin privledges to ...") + if (!$this_user->IsAdmin()); +} +else { + # + # Check the filename when invoked from the web interface; must be a + # file in /tmp. + # + if ($xmlfile =~ /^([-\w\.\/]+)$/) { + $xmlfile = $1; + } + else { + fatal("Bad data in pathname: $xmlfile"); + } + + # Use realpath to resolve any symlinks. + my $translated = `realpath $xmlfile`; + if ($translated =~ /^(\/tmp\/[-\w\.\/]+)$/) { + $xmlfile = $1; + } + else { + fatal("Bad data in translated pathname: $xmlfile"); + } + + # The web interface (and in the future the xmlrpc interface) sets this. + $this_user = User->ImpliedUser(); + + if (! defined($this_user)) { + fatal("Cannot determine implied user!"); + } +} + +# +# These are the fields that we allow to come in from the XMLfile. +# +my $SLOT_OPTIONAL = 0x1; # The field is not required. +my $SLOT_REQUIRED = 0x2; # The field is required and must be non-null. +my $SLOT_ADMINONLY = 0x4; # Only admins can set this field. +# +# XXX We should encode all of this in the DB so that we can generate the +# forms on the fly, as well as this checking code. +# +my %xmlfields = + # XML Field Name DB slot name Flags Default + ("node_type" => ["node_type", $SLOT_REQUIRED], + + # Presence of new_type commands creation of a new nodetype. + "new_type" => ["attr_boolean", $SLOT_OPTIONAL], + + # Class may only be changed while making a new class. + "class" => ["class", $SLOT_OPTIONAL], + + # Fixed attributes. + "isvirtnode" => ["boolean", $SLOT_OPTIONAL], + "isjailed" => ["boolean", $SLOT_OPTIONAL], + "isdynamic" => ["boolean", $SLOT_OPTIONAL], + "isremotenode" => ["boolean", $SLOT_OPTIONAL], + "issubnode" => ["boolean", $SLOT_OPTIONAL], + "isplabdslice" => ["boolean", $SLOT_OPTIONAL], + "issimnode" => ["boolean", $SLOT_OPTIONAL], + + # Dynamic attributes with wildcards. + "attr_boolean_*" => ["attr_boolean", $SLOT_OPTIONAL], + "attr_integer_*" => ["attr_int", $SLOT_OPTIONAL], + "attr_float_*" => ["attr_float", $SLOT_OPTIONAL], + "attr_string_*" => ["attr_string", $SLOT_OPTIONAL], + + # Old-style osid and image names. + "attr_string_*_osid" => ["attr_osid", $SLOT_OPTIONAL], + "attr_string_*_imageid" => ["attr_imageid",$SLOT_OPTIONAL], + # New-style indices. + "attr_integer_*_osid" => ["attr_int", $SLOT_OPTIONAL], + "attr_integer_*_imageid"=> ["attr_int", $SLOT_OPTIONAL], + + # The name of a single attribute to add to the list. + "new_attr" => ["attr_name", $SLOT_OPTIONAL], + # Multiple attributes can be deleted from the list. + "delete_*" => ["attr_boolean", $SLOT_OPTIONAL]); + +# +# Must wrap the parser in eval since it exits on error. +# +my $xmlparse = eval { XMLin($xmlfile, + VarAttr => 'name', + ContentKey => '-content', + SuppressEmpty => undef); }; +fatal($@) + if ($@); + +# +# Process and dump the errors (formatted for the web interface). +# We should probably XML format the errors instead but not sure I want +# to go there yet. +# +my %errors = (); + +# +# Make sure all the required arguments were provided. +# +my $key; +foreach $key (keys(%xmlfields)) { + my (undef, $required, undef) = @{$xmlfields{$key}}; + + $errors{$key} = "Required value not provided" + if ($required & $SLOT_REQUIRED && + ! exists($xmlparse->{'attribute'}->{"$key"})); +} +UserError() + if (keys(%errors)); + +# +# We build up an array of arguments to pass to () as we check +# the attributes. +# +my %editnodetype_args = (); + +# +# Wildcard keys have one or more *'s in them like simple glob patterns. +# This allows multiple key instances for categories of attributes, and +# putting a "type signature" in the key for arg checking, as well. +# +# Wildcards are made into regex's by anchoring the ends and changing each * to +# a "word" (group of alphahumeric.) A tail * means "the rest", allowing +# multiple words separated by underscores or dashes. +# +my $wordpat = '[a-zA-Z0-9]+'; +my $tailpat = '[-\w]+'; +my %wildcards; +foreach $key (keys(%xmlfields)) { + if (index($key, "*") >= 0) { + my $regex = '^' . $key . '$'; + $regex =~ s/\*\$$/$tailpat/; + $regex =~ s/\*/$wordpat/g; + $wildcards{$key} = $regex; + } +} +# Key ordering is lost in a hash. +# Put longer matching wildcard keys before their prefix. +my @wildkeys = reverse(sort(keys(%wildcards))); + +foreach $key (keys(%{ $xmlparse->{'attribute'} })) { + my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'}; + + print STDERR "User attribute: '$key' -> '$value'\n" + if ($debug); + + my $field = $key; + my $wild; + if (!exists($xmlfields{$key})) { + + # Not a regular key; look for a wildcard regex match. + foreach my $wildkey (@wildkeys) { + my $regex = $wildcards{$wildkey}; + if ($wild = $key =~ /$regex/) { + $field = $wildkey; + print STDERR "Wildcard: '$key' matches '$wildkey'\n" + if ($debug); + last; # foreach $wildkey + } + } + if (!$wild) { + $errors{$key} = "Unknown attribute"; + next; # foreach $key + } + } + + my ($dbslot, $required, $default) = @{$xmlfields{$field}}; + + if ($required & $SLOT_REQUIRED) { + # A slot that must be provided, so do not allow a null value. + if (!defined($value)) { + $errors{$key} = "Must provide a non-null value"; + next; + } + } + if ($required & $SLOT_OPTIONAL) { + # Optional slot. If value is null skip it. Might not be the correct + # thing to do all the time? + if (!defined($value)) { + next + if (!defined($default)); + $value = $default; + } + } + if ($required & $SLOT_ADMINONLY) { + # Admin implies optional, but thats probably not correct approach. + $errors{$key} = "Administrators only" + if (! $this_user->IsAdmin()); + } + + # Now check that the value is legal. + if (! TBcheck_dbslot($value, "node_types", $dbslot, TBDB_CHECKDBSLOT_ERROR)) { + $errors{$key} = TBFieldErrorString(); + next; + } + + $editnodetype_args{$key} = $value; +} +UserError() + if (keys(%errors)); + +# +# Now do special checks. +# +my $query_result; +my $node_type = $editnodetype_args{'node_type'}; +my $new_type = exists($editnodetype_args{"new_type"}); + +# +# Check whether the node type already exists. +# +$query_result = + DBQueryFatal("select * from node_types where type='$node_type'"); +my $node_type_exists = $query_result->numrows; +my $prev_nodetype_data; +if ($new_type) { + + # Found. But it's supposed to be new. + UserError("New NodeType: $node_type is already used!") + if ($node_type_exists); +} +else { + # Not found, but it was supposed to be old. + UserError("NodeType: $node_type is not a valid nodetype!") + if (!$node_type_exists); + + # Found an existing one, grab its data. + $prev_nodetype_data = $query_result->fetchrow_hashref(); +} + +# +# Check attributes of the node type, building an insert list as we go. +# +my @nodetype_data; + +# First check fixed (non-attr*) attributes that are in the node_types table. +# Class may only be set while making a new nodetype. +my $class; +if (exists($editnodetype_args{"class"})) { + my $newclass = $editnodetype_args{"class"}; + if ($new_type) { + $class = $newclass; + if ($class eq "") { + $class = "pc"; # Default to pc class. + } + push(@nodetype_data, "class='$class'"); + } + else { + $class = $prev_nodetype_data->{"class"}; + + # It's okay to specify it to be the same as it was before. + UserError("NodeType: Can't change class ($class) of existing node.") + if ($class ne $newclass); + } +} + +# The rest of them all have names starting with "is" at present. +my @fixed_args = grep(/^is/, keys(%editnodetype_args)); +foreach my $name (@fixed_args) { + if (exists($editnodetype_args{$name})) { + my $value = $editnodetype_args{$name}; + push(@nodetype_data, "$name='$value'"); + } +} +# Needed below. +my $isremotenode = exists($editnodetype_args{"isremotenode"}) ? + $editnodetype_args{"isremotenode"} : + $prev_nodetype_data->{"isremotenode"}; + +# Get previous dynamic attrs from the node_type_attributes table. +$query_result = + DBQueryFatal("select * from node_type_attributes ". + "where type='$node_type'"); +my $prev_attrs = $query_result->fetchall_hashref("attrkey"); + +# Dynamic attributes to be changed or deleted. Possibly one new one added. +my $new_attr_name = ""; +if (my $new_attr = exists($editnodetype_args{'new_attr'})) { + $new_attr_name = $editnodetype_args{'new_attr'}; + + # The new attr must not already exist. + UserError("New NodeType Attr: $new_attr_name is already used!") + if (exists($prev_attrs->{$new_attr_name})); +} + +# Get lists of ids for checking the special "attr_*_*id" attributes. +$query_result = + DBQueryFatal("select osid,osname,pid from os_info ". + "where (path='' or path is NULL) ". + "order by pid,osname"); +my $osids = $query_result->fetchall_hashref("osid"); +$query_result = + DBQueryFatal("select osid,osname,pid from os_info ". + "where (path is not NULL and path!='') ". + "order by pid,osname"); +my $mfsosids = $query_result->fetchall_hashref("osid"); +$query_result = + DBQueryFatal("select imageid,imagename,pid from images ". + "order by pid,imagename"); +my $imageids = $query_result->fetchall_hashref("imageid"); + +# Separate out the attr types and names from the other argument keys. +my ($attr_name, $attr_type, $attr_value); +my (@attr_names, %attr_types, %attr_values, %attr_dels); +foreach my $argkey (keys(%editnodetype_args)) { + next + if (!($argkey =~ /^attr_/)); + + $attr_name = $attr_type = $argkey; + $attr_name =~ s/^attr_${wordpat}_(.*)$/$1/; + if ($argkey =~ /_(os|image)id$/) { + # Special case: the type is the LAST part of the name for ID attrs. + $attr_type = $1; + } + else { + # Normal ones are like "attr_type_name". + $attr_type =~ s/^attr_($wordpat)_.*$/$1/; + } + $attr_value = $editnodetype_args{$argkey}; + + if ($debug) { + print STDERR "Dynamic attr: $attr_name($attr_type) = '$attr_value'\n"; + } + + push(@attr_names, $attr_name); + $attr_types{$attr_name} = $attr_type; + $attr_values{$attr_name} = $attr_value; +} + +# Check all of the dynamic attrs that are to be set. +foreach $attr_name (@attr_names) { + + # Skip checks on attrs that are scheduled for deletion anyway. + my $del = $attr_dels{$attr_name} = + exists($editnodetype_args{"delete_${attr_name}"}) && + $editnodetype_args{"delete_${attr_name}"} eq "1"; + next + if $del; + + # An attr to be set must pre-exist, unless it's the new attr. + UserError("NodeType Attr: $attr_name is not set in nodetype $node_type!") + if (!exists($prev_attrs->{$attr_name}) && + $attr_name ne $new_attr_name); + + $attr_type = $attr_types{$attr_name}; + $attr_value = $attr_values{$attr_name}; + + # Check the osid and imageid attribute values against the id lists. + # Under the web page interface, these come to us from selectors. + if ($attr_type eq "osid") { + if ($attr_name =~ /mfs$/) { + UserError("NodeType MFS OSID Attr: $attr_name is not an mfs_osid.") + if (!exists($mfsosids->{$attr_value})); + } + else { + UserError("NodeType OSID Attr: $attr_name is not an osid.") + if (!exists($osids->{$attr_value})); + } + } + elsif ($attr_type eq "imageid") { + UserError("NodeType Image ID Attr: $attr_name is not an imageid.") + if (!exists($imageids->{$attr_value})); + } +} + +exit(0) + if ($verify); + +# +# Now safe to put the nodetype info into the DB. +# +my ($type, $value); +if ($new_type) { + DBQueryFatal("insert into node_types set type='$node_type', ". + join(",", @nodetype_data)); + + if ($class eq "pc" || $isremotenode eq "1") { + my $vnode_type = $node_type; + $vnode_type =~ s/pc/pcvm/; + if ($vnode_type eq $node_type) { + $vnode_type = "$vnode_type-vm"; + } + my $pcvmtype = ($isremotenode eq "1" ? "pcvwa" : "pcvm"); + + DBQueryFatal("insert into node_types_auxtypes set " . + " auxtype='$vnode_type', type='$pcvmtype'"); + } + + foreach $attr_name (@attr_names) { + # Skip adding an attr if it is also scheduled for deletion. + next + if ($attr_dels{$attr_name}); + + $key = escapeshellarg($attr_name); + $type = escapeshellarg($attr_types{$attr_name}); + $value = escapeshellarg($attr_values{$attr_name}); + + DBQueryFatal("insert into node_type_attributes set ". + " type='$node_type', ". + " attrkey='$key', attrtype='$type', ". + " attrvalue='$value' "); + } +} +else { + DBQueryFatal("update node_types set ". + join(",", @nodetype_data) . " ". + "where type='$node_type'"); + + foreach $attr_name (@attr_names) { + $key = escapeshellarg($attr_name); + $type = escapeshellarg($attr_types{$attr_name}); + $value = escapeshellarg($attr_values{$attr_name}); + + # Remove an attr from the DB if scheduled for deletion. + if ($attr_dels{$attr_name}) { + DBQueryFatal("delete from node_type_attributes ". + "where type='$node_type' and attrkey='$key'"); + } + else { + DBQueryFatal("replace into node_type_attributes set ". + " type='$node_type', ". + " attrkey='$key', attrtype='$type', ". + " attrvalue='$value' "); + } + } +} + +exit(0); + +sub fatal($) +{ + my ($mesg) = @_; + + print STDERR "*** $0:\n". + " $mesg\n"; + # Exit with negative status so web interface treats it as system error. + exit(-1); +} + +sub UserError(;$) +{ + my ($mesg) = @_; + + if (keys(%errors)) { + foreach my $key (keys(%errors)) { + my $val = $errors{$key}; + print "${key}: $val\n"; + } + } + print "$mesg\n" + if (defined($mesg)); + + # Exit with positive status so web interface treats it as user error. + exit(1); +} + +sub escapeshellarg($) +{ + my ($str) = @_; + + $str =~ s/[^-_[:alnum:]]/\\$&/g; + return $str; +} diff --git a/configure b/configure index bd60efa9b12db5a44ba03d3ed38a9ca5799229ba..725c8ead1e0cd4d4b92a30ddc561c62e6c5c4946 100755 --- a/configure +++ b/configure @@ -2428,7 +2428,7 @@ outfiles="$outfiles Makeconf GNUmakefile \ account/addpubkey account/addsfskey account/genpubkeys \ account/quotamail account/mkusercert account/newproj account/newuser \ backend/GNUmakefile backend/moduserinfo backend/newgroup \ - backend/newmmlist backend/editexp backend/editimageid \ + backend/newmmlist backend/editexp backend/editimageid backend/editnodetype \ tbsetup/GNUmakefile tbsetup/console_setup tbsetup/spewlogfile \ tbsetup/spewrpmtar tbsetup/gentopofile tbsetup/power_sgmote.pm \ tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \ diff --git a/configure.in b/configure.in index 6436cb5f669cdb23511253c56cb55d638fe6b860..78230730aa97bdefa0049cea66df719341ea48cc 100755 --- a/configure.in +++ b/configure.in @@ -808,7 +808,7 @@ outfiles="$outfiles Makeconf GNUmakefile \ account/addpubkey account/addsfskey account/genpubkeys \ account/quotamail account/mkusercert account/newproj account/newuser \ backend/GNUmakefile backend/moduserinfo backend/newgroup \ - backend/newmmlist backend/editexp backend/editimageid \ + backend/newmmlist backend/editexp backend/editimageid backend/editnodetype \ tbsetup/GNUmakefile tbsetup/console_setup tbsetup/spewlogfile \ tbsetup/spewrpmtar tbsetup/gentopofile tbsetup/power_sgmote.pm \ tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \ diff --git a/sql/database-fill.sql b/sql/database-fill.sql index 3af4b603b36e7faef0d9d54b854fd92de11e394a..67e41d8c849fc83855beaa4350656e1a4ce7e7c1 100644 --- a/sql/database-fill.sql +++ b/sql/database-fill.sql @@ -798,6 +798,18 @@ REPLACE INTO table_regex VALUES ('images','osid','text','redirect','os_info:osid REPLACE INTO table_regex VALUES ('images','load_address','text','redirect','default:text',0,0,NULL); REPLACE INTO table_regex VALUES ('images','frisbee_pid','text','redirect','default:int',0,0,NULL); +REPLACE INTO table_regex VALUES ('node_types','new_type','text','redirect','default:tinytext',0,0,NULL); +REPLACE INTO table_regex VALUES ('node_types','node_type','text','regex','^[-\\w]+$',1,30,NULL); +REPLACE INTO table_regex VALUES ('node_types','class','text','regex','^[\\w]+$',1,30,NULL); +REPLACE INTO table_regex VALUES ('node_types','boolean','text','redirect','default:boolean',0,0,NULL); +REPLACE INTO table_regex VALUES ('node_types','attr_name','text','regex','^[-\\w]+$',1,32,NULL); +REPLACE INTO table_regex VALUES ('node_types','attr_osid','text','redirect','os_info:osid',0,0,NULL); +REPLACE INTO table_regex VALUES ('node_types','attr_imageid','text','redirect','images:imageid',0,0,NULL); +REPLACE INTO table_regex VALUES ('node_types','attr_boolean','text','redirect','default:boolean',0,0,NULL); +REPLACE INTO table_regex VALUES ('node_types','attr_integer','text','redirect','default:int',0,0,NULL); +REPLACE INTO table_regex VALUES ('node_types','attr_float','text','redirect','default:float',0,0,NULL); +REPLACE INTO table_regex VALUES ('node_types','attr_string','text','redirect','default:tinytext',0,0,NULL); + REPLACE INTO table_regex VALUES ('experiments','security_level','int','redirect','default:tinyuint',0,4,NULL); REPLACE INTO table_regex VALUES ('experiments','elabinelab_eid','text','redirect','experiments:eid',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_node_startloc','pid','text','redirect','projects:pid',0,0,NULL); diff --git a/www/editnodetype.php3 b/www/editnodetype.php3 index 0cc78d6f1dda3cb3ddc31c435e0746dbc6f54f35..5cdc26a70857a2a1a3bd3e80a1404225328f7e7b 100644 --- a/www/editnodetype.php3 +++ b/www/editnodetype.php3 @@ -23,9 +23,14 @@ if (! $isadmin) { } $optargs = OptionalPageArguments("submit", PAGEARG_STRING, - "node_type", PAGEARG_STRING, - "new_type", PAGEARG_STRING, "formfields", PAGEARG_ARRAY, + + # Send new_type=1 to create new nodetype. + "new_type", PAGEARG_STRING, + # Optional if new_type, required if not. + "node_type", PAGEARG_STRING, + + # Attribute creation and deletion. "deletes", PAGEARG_ARRAY, "attributes", PAGEARG_ARRAY, "newattribute_type", PAGEARG_STRING, @@ -249,6 +254,10 @@ function SPITFORM($node_type, $formfields, $attributes, $deletes, $errors) <td align=center colspan=2><b>Node Attributes</b></td></tr>\n"; while (list ($key, $val) = each ($attributes)) { + if (!isset($deletes[$key])) { + # Somehow this doesn't get initialized in the Create Node case. + $deletes[$key] = ""; + } if ($key == "default_osid" || $key == "jail_osid" || $key == "delay_osid") { @@ -385,7 +394,7 @@ else { } # -# We need a list of osids and imageids. +# We need lists of osids and imageids for selection. # $osid_result = DBQueryFatal("select osid,osname,pid from os_info ". @@ -419,251 +428,212 @@ if (!isset($new_type)) { } # -# Otherwise, must validate and redisplay if errors. Build up a DB insert -# string as we go. +# Otherwise, must validate and redisplay if errors. # $errors = array(); -$inserts = array(); -# Class (only for new types) -if (isset($new_type) && - isset($formfields['class']) && $formfields['class'] != "") { - if (! TBvalid_description($formfields['class'])) { - $errors["Class"] = TBFieldErrorString(); +# Check the attributes. +while (list ($key, $val) = each ($attributes)) { + # Skip checks if scheduled for deletion + if (isset($deletes[$key]) && $deletes[$key] == "checked") + continue; + + if (!isset($attribute_types[$key])) { + $errors[$key] = "Unknown Attribute"; + continue; } - else { - $inserts["class"] = addslashes($formfields["class"]); + + if ($val == "") { + $errors[$key] = "No value provided for $key"; + continue; + } + + # Probably redundant with the XML keyfields checking... + $attrtype = $attribute_types[$key]; + if ($attrtype == "") { # Shouldn't happen... + $attrtype = $attribute_types[$key] = "integer"; + } + if (strpos(":boolean:float:integer:string:", ":$attrtype:")===FALSE) { + $errors[$key] = "Invalid type information: $attrtype"; + continue; + } + + # New attributes require type and value. + if (isset($newattribute_name) && $newattribute_name != "" && + !(isset($newattribute_type) && $newattribute_type != "")) { + $errors[$newattribute_name] = "Missing type"; + } + if (isset($newattribute_name) && $newattribute_name != "" && + !(isset($newattribute_value) && $newattribute_value != "")) { + $errors[$newattribute_name] = "Missing value"; } } +# +# If any errors, respit the form with the current values and the +# error messages displayed. Iterate until happy. +# +if (count($errors)) { + SPITFORM($node_type, $formfields, $attributes, $deletes, $errors); + PAGEFOOTER(); + return; +} + +# +# Build up argument array to pass along. +# +$args = array(); + +# Class (only for new types.) +if (isset($new_type) && + isset($formfields['class']) && $formfields['class'] != "") { + $args["new_type"] = "1"; + $args["class"] = $formfields["class"]; +} + # isvirtnode if (isset($formfields["isvirtnode"]) && $formfields["isvirtnode"] != "") { - if (! TBvalid_boolean($formfields["isvirtnode"])) { - $errors["isvirtnode"] = TBFieldErrorString(); - } - else { - $inserts["isvirtnode"] = $formfields["isvirtnode"]; - } + $args["isvirtnode"] = $formfields["isvirtnode"]; } # isjailed if (isset($formfields["isjailed"]) && $formfields["isjailed"] != "") { - if (! TBvalid_boolean($formfields["isjailed"])) { - $errors["isjailed"] = TBFieldErrorString(); - } - else { - $inserts["isjailed"] = $formfields["isjailed"]; - } + $args["isjailed"] = $formfields["isjailed"]; } # isdynamic if (isset($formfields["isdynamic"]) && $formfields["isdynamic"] != "") { - if (! TBvalid_boolean($formfields["isdynamic"])) { - $errors["isdynamic"] = TBFieldErrorString(); - } - else { - $inserts["isdynamic"] = $formfields["isdynamic"]; - } + $args["isdynamic"] = $formfields["isdynamic"]; } # isremotenode if (isset($formfields["isremotenode"]) && $formfields["isremotenode"] != "") { - if (! TBvalid_boolean($formfields["isremotenode"])) { - $errors["isremotenode"] = TBFieldErrorString(); - } - else { - $inserts["isremotenode"] = $formfields["isremotenode"]; - } + $args["isremotenode"] = $formfields["isremotenode"]; } # issubnode if (isset($formfields["issubnode"]) && $formfields["issubnode"] != "") { - if (! TBvalid_boolean($formfields["issubnode"])) { - $errors["issubnode"] = TBFieldErrorString(); - } - else { - $inserts["issubnode"] = $formfields["issubnode"]; - } + $args["issubnode"] = $formfields["issubnode"]; } # isplabdslice if (isset($formfields["isplabdslice"]) && $formfields["isplabdslice"] != "") { - if (! TBvalid_boolean($formfields["isplabdslice"])) { - $errors["isplabdslice"] = TBFieldErrorString(); - } - else { - $inserts["isplabdslice"] = $formfields["isplabdslice"]; - } + $args["isplabdslice"] = $formfields["isplabdslice"]; } # issimnode if (isset($formfields["issimnode"]) && $formfields["issimnode"] != "") { - if (! TBvalid_boolean($formfields["issimnode"])) { - $errors["issimnode"] = TBFieldErrorString(); - } - else { - $inserts["issimnode"] = $formfields["issimnode"]; - } + $args["issimnode"] = $formfields["issimnode"]; } -# Check the attributes -while (list ($key, $val) = each ($attributes)) { - # Skip checks if scheduled for deletion - if (isset($deletes[$key]) && $deletes[$key] == "checked") - continue; - - if (!isset($attribute_types[$key])) { - $errors[$key] = "Unknown Attribute"; - continue; - } - - $attrtype = $attribute_types[$key]; - $valid = 1; - - if ($val == "") { - $errors[$key] = "No value provided for $key"; - continue; - } - elseif ($attrtype == "boolean") { - $valid = TBvalid_boolean($val); - } - elseif ($attrtype == "float") { - $valid = TBvalid_float($val); - } - elseif ($attrtype == "integer") { - $valid = TBvalid_integer($val); - } - elseif ($attrtype == "string") { - $valid = TBvalid_description($val); - } - else { - $errors[$key] = "Invalid type information: $attrtype"; - continue; - } - if (!$valid) { - $errors[$key] = TBFieldErrorString(); - } +# Existing attributes. +foreach ($attributes as $attr_key => $attr_val) { + if (isset($deletes[$attr_key]) && $deletes[$attr_key] == "checked") + $args["delete_${attr_key}"] = "1"; + $attr_type = $attribute_types[$attr_key]; + $args["attr_${attr_type}_${attr_key}"] = $attr_val; } # -# Spit any errors now. +# Form allows for adding a single new attribute, but someday be more fancy. # -if (count($errors)) { +if (isset($newattribute_name) && $newattribute_name != "" && + isset($newattribute_value) && $newattribute_value != "" && + isset($newattribute_type) && $newattribute_type != "") { + + $args["new_attr"] = $newattribute_name; + # The following is matched by wildcards on the other side of XML, + # including checking its type and value, just like existing attributes. + $args["attr_${newattribute_type}_$newattribute_name"] = $newattribute_value; +} + +if (! ($result = SetNodeType($node_type, $args, $errors))) { + # Always respit the form so that the form fields are not lost. + # I just hate it when that happens so lets not be guilty of it ourselves. SPITFORM($node_type, $formfields, $attributes, $deletes, $errors); PAGEFOOTER(); return; } +PAGEHEADER(isset($new_type) ? "Create" : "Edit" . "Node Type"); + # -# Form allows for a single new attribute, but someday be more fancy. +# Spit out a redirect so that the history does not include a post +# in it. The back button skips over the post and to the form. # -if (isset($newattribute_name) && $newattribute_name != "" && - isset($newattribute_value) && $newattribute_value != "" && - isset($newattribute_type) && $newattribute_type != "") { +PAGEREPLACE("editnodetype.php3?node_type=$node_type"); - if (!preg_match("/^[-\w]+$/", $newattribute_name)) { - $errors["New Attribute Name"] = "Invalid characters in attribute name"; - } - else { - $valid = 1; - - if ($newattribute_type == "boolean") { - $valid = TBvalid_boolean($newattribute_value); - } - elseif ($newattribute_type == "float") { - $valid = TBvalid_float($newattribute_value); - } - elseif ($newattribute_type == "integer") { - $valid = TBvalid_integer($newattribute_value); - } - elseif ($newattribute_type == "string") { - $valid = TBvalid_description($newattribute_value); - } - else { - $errors["New Attribute Type"] = "Invalid type: $newattribute_type"; - } - if (!$valid) { - $errors["New Attribute Type"] = TBFieldErrorString(); - } - } - - # - # Spit any errors now. - # - if (count($errors)) { - SPITFORM($node_type, $formfields, $attributes, $deletes, $errors); - PAGEFOOTER(); - return; - } - # Set up for loops below. - $attributes[$newattribute_name] = $newattribute_value; - $attribute_types[$newattribute_name] = $newattribute_type; -} +# +# Standard Testbed Footer +# +PAGEFOOTER(); # -# Otherwise, do the inserts. +# Create or edit a nodetype. (No class for that at present.) # -$insert_data = array(); -foreach ($inserts as $name => $value) { - $insert_data[] = "$name='$value'"; -} +function SetNodeType($node_type, $args, &$errors) { + global $suexec_output, $suexec_output_array; -if (isset($new_type)) { - DBQueryFatal("insert into node_types set type='$node_type', ". - implode(",", $insert_data)); - if ($formfields["class"] == "pc" || $formfields["isremotenode"] == 1) { - $vnode_type = $node_type; - $vnode_type = preg_replace("/pc/","pcvm",$vnode_type); - if ($vnode_type == $node_type) { - $vnode_type = "$vnode_type-vm"; - } - $pcvmtype = ($formfields["isremotenode"] == 1 ? "pcvwa" : "pcvm"); - - DBQueryFatal("insert into node_types_auxtypes set " . - " auxtype='$vnode_type', type='$pcvmtype'"); + # + # Generate a temporary file and write in the XML goo. + # + $xmlname = tempnam("/tmp", "editnodetype"); + if (! $xmlname) { + TBERROR("Could not create temporary filename", 0); + $errors[] = "Transient error(1); please try again later."; + return null; } - foreach ($attributes as $key => $value) { - # Skip if scheduled for deletion - if (isset($deletes[$key]) && $deletes[$key] == "checked") - continue; - - $key = addslashes($key); - $type = addslashes($attribute_types[$key]); - $value = addslashes($value); - - DBQueryFatal("insert into node_type_attributes set ". - " type='$node_type', ". - " attrkey='$key', attrtype='$type', ". - " attrvalue='$value' "); + if (! ($fp = fopen($xmlname, "w"))) { + TBERROR("Could not open temp file $xmlname", 0); + $errors[] = "Transient error(2); please try again later."; + return null; } -} else { - DBQueryFatal("update node_types set ". - implode(",", $insert_data) . " ". - "where type='$node_type'"); - - foreach ($attributes as $key => $value) { - $key = addslashes($key); - $type = addslashes($attribute_types[$key]); - $value = addslashes($value); - - # Remove if scheduled for deletion - if (isset($deletes[$key]) && $deletes[$key] == "checked") { - DBQueryFatal("delete from node_type_attributes ". - "where type='$node_type' and attrkey='$key'"); + + # Add these. Maybe caller should do this? + $args["node_type"] = $node_type; + + fwrite($fp, "<nodetype>\n"); + foreach ($args as $name => $value) { + fwrite($fp, "<attribute name=\"$name\">"); + fwrite($fp, " <value>" . htmlspecialchars($value) . "</value>"); + fwrite($fp, "</attribute>\n"); + } + fwrite($fp, "</nodetype>\n"); + fclose($fp); + chmod($xmlname, 0666); + + $retval = SUEXEC("nobody", "nobody", "webeditnodetype $xmlname", + SUEXEC_ACTION_IGNORE); + + if ($retval) { + if ($retval < 0) { + $errors[] = "Transient error(3); please try again later."; + SUEXECERROR(SUEXEC_ACTION_CONTINUE); } else { - DBQueryFatal("replace into node_type_attributes set ". - " type='$node_type', ". - " attrkey='$key', attrtype='$type', ". - " attrvalue='$value' "); + # unlink($xmlname); + if (count($suexec_output_array)) { + for ($i = 0; $i < count($suexec_output_array); $i++) { + $line = $suexec_output_array[$i]; + if (preg_match("/^([-\w]+):\s*(.*)$/", + $line, $matches)) { + $errors[$matches[1]] = $matches[2]; + } + else + $errors[] = $line; + } + } + else + $errors[] = "Transient error(4); please try again later."; } + return null; } -} + # There are no return value(s) to parse at the end of the output. -# -# Spit out a redirect so that the history does not include a post -# in it. The back button skips over the post and to the form. -# -header("Location: editnodetype.php3?node_type=$node_type"); + # Unlink this here, so that the file is left behind in case of error. + # We can then create the nodetype by hand from the xmlfile, if desired. + unlink($xmlname); + return true; +} ?>