#
# Copyright 1998 VMware, Inc.  All rights reserved. -- VMware Confidential
#

use VMware::Control::Server;
use VMware::Control::Keycode;
package VMware::Control::VM;

use strict;

require DynaLoader;

use vars qw($VERSION @ISA);

BEGIN{$VERSION = '1.01';}

@ISA = qw(DynaLoader);

use VMware::Control $VERSION;
use VMware::Control::Profiler;

my $instance_name = 
    $ENV{vmware_MUI} ? "mui" : 
    $ENV{vmware_VMSERVERD} ? "serverd" :
    "vm";
my $p = VMware::Control::Profiler::getInstance($instance_name);

# Preloaded methods go here.

sub new {
    my $stub = $p->profile();
    my $server = shift;
    my $vm_id = shift;
    return New($server, $vm_id);
}

sub get_last_error {
    my $stub = $p->profile();
    my $vm = shift;
    my ($ret, $string) = $vm->GetLastError();
    if (wantarray) {
        return ($ret, $string);
    } else {
        return $ret;
    }
}

sub is_connected {
    my $stub = $p->profile();
    my $vm = shift;
    return $vm->IsConnected();
}

my %useServerd;    # VM -> server version mapping
sub connect {
    my $stub = $p->profile();
    my $vm = shift;
    my $mks = 0;

    $mks = shift if $#_ >= 0;

    my $ret = $vm->Connect($mks);
    if ($ret) {
        $useServerd{$vm} = 1 if($vm->get("Status.version") =~ m/ESX/);
        return $ret;
    } else {
        return undef;
    }
}

sub start {
    my $stub = $p->profile();
    my $vm = shift;
    my $mode = shift;
    if (!defined($mode)) {
        $mode = VM_POWEROP_MODE_SOFT;
    } else {
        $mode = VM_POWEROP_MODE_HARD;
    }

    my $ret = $vm->Start($mode);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub stop {
    my $stub = $p->profile();
    my $vm = shift;
    my $force = shift;
    if (!defined($force)) {
        $force = 0;
    }
    my $ret = $vm->Stop($force);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub request_stop {
    my $stub = $p->profile();
    my $vm = shift;
    my $force = shift;
    if (!defined($force)) {
        $force = 0;
    }
    my $ret = $vm->RequestStop($force);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub reset {
    my $stub = $p->profile();
    my $vm = shift;
    my $force = shift;
    if (!defined($force)) {
        $force = 0;
    }
    my $ret = $vm->Reset($force);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub request_reset {
    my $stub = $p->profile();
    my $vm = shift;
    my $force = shift;
    if (!defined($force)) {
        $force = 0;
    }
    my $ret = $vm->RequestReset($force);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub suspend_to_disk {
    my $stub = $p->profile();
    my $vm = shift;
    my $mode = shift;
    if (!defined($mode)) {
        $mode = VM_POWEROP_MODE_SOFT;
    } else {
        $mode = VM_POWEROP_MODE_HARD;
    }

    my $ret = $vm->SuspendToDisk($mode);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub suspend {
    my $stub = $p->profile();
    return suspend_to_disk(@_);
}

sub resume {
    my $stub = $p->profile();
    my $vm = shift;
    my $mode = shift;
    if (!defined($mode)) {
        $mode = VM_POWEROP_MODE_SOFT;
    } else {
        $mode = VM_POWEROP_MODE_HARD;
    }

    my $state = $vm->get("Status.power");

#    if (!defined($state)) {
#        # Error (e.g. disconnect)
#        return undef;
#    }
        
    if (defined($state) && $state ne "suspended") {
        $vm->SetError(VM_E_BADSTATE, $state);
        return undef;
    }

    my $ret = $vm->Start($mode);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub commit {
    my $stub = $p->profile();
    my $vm = shift;
    my $disk = shift;
    my $level = shift;
    my $freeze = shift;
    my $wait = shift;
    my $ret = $vm->Commit($disk, $level, $freeze, $wait);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub add_redo {
    my $stub = $p->profile();
    my $vm = shift;
    my $disk = shift;
    my $ret = $vm->AddRedo($disk);
    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub info {
    my $stub = $p->profile();
    my $vm = shift;
    my @list = $vm->Info();
    my %hash;
    my ($k, $v);

    if ($#list <= 0) {
        return undef;
    }

    if (! ($#list % 2)) {
        print STDERR "VMware::Control::VM::info: Warning: hash has odd number of elements\n";
    }

    keys(%hash) = $#list;
    while($#list > 0) {
        ($k, $v) = splice(@list, 0, 2);
        $hash{$k} = $v;
    }
    return \%hash;
}

sub disconnect {
    my $stub = $p->profile();
    my $vm = shift;
    my $ret = $vm->Disconnect();

    undef($useServerd{$vm});

    return 1;
}

sub esxStatsGet($$) {
   my $vm = shift;
   my $var = shift;

   # Only go to vmx in very specific cases:
   # XXX should really query the vmx which stats
   # it exports.
   if ($var =~ m/^Status.stats.vm.(heartbeat|uptime)/) {
      return GetAsString($vm,$var);
   }

   if ($var =~ m/^Status.stats.vm.stats$/) {
      my $serverdResult = GetAsStringServerd($vm,$var);
      if( defined($serverdResult) )  {
         #XXX should really query for which stats
         $serverdResult .= ",heartbeat,uptime";
      }
      return $serverdResult;
   }

   return GetAsStringServerd($vm,$var);
}

sub get ($$@) {
    my $stub = $p->profile();
    my $vm = shift;
    my $var = shift;
    my $getFn =\&GetAsString;
    

    if ( $var =~ m/^Status\.stats\./ ) {
        my $ret;

        my ($units, $allocation, $limit, @values);

        if(defined($useServerd{$vm})) {
          $getFn = \&esxStatsGet;
        }

        if ($#_ >= 0) {
            $var .= "[" . shift;
            while($_ = shift) {
                $var .= "," . $_;
            }
            $var .= "]";
        }

        #
        # Status.stats.*
        #
        if ($var =~ m/^Status\.stats\.([^\.]+)\.([^\.]+)\[(.+)\]$/ ) {
            my $interval_string = $3;
            my $device = $1;
            my $statname = $2;
            my @intervals;

            while ($interval_string =~ s/,*([^,]+)//) {
                push(@intervals, $1);
            }


            #print STDERR "  Perl API debug Get  device=", $device, 
            #" statname=", $statname, " intervals=", @intervals,"\n";

            $ret = &$getFn($vm,$var);

            #print STDERR "  Perl API debug Get(",$var,") = ", $ret,"\n";

            if (!defined($ret) || $ret =~ /^Error:/) {
                $vm->SetError(VM_E_NOPROPERTY, $ret);
                return undef;
            }

            # Convert a list of the form functions[:values]+
            # to a hash
            if ($ret =~ s/^([^:]+)://) {
                my @functions = split(/,/, $1);
                my %h;

                my @value_lists = split (/:/, $ret);
            
                for my $valuelist (@value_lists) {
                    my @values = split (/,/, $valuelist);
                    my $interval = shift (@intervals);
                    foreach my $function (@functions) {
                        my $ref = $h{$function}; #Reference to a hash
                        my %fvalues;
                        if (defined($ref)) {
                            %fvalues = %{$h{$function}};
                        }
                        $fvalues{$interval} = shift(@values);
                        $h{$function} =  \%fvalues; 
                    }
                }

                return \%h;
            } else {
                #Error
                print STDERR "VMware::Control::VM::get: Invalid property $var: ", $ret, "\n";
                $vm->SetError(VM_E_NOPROPERTY, $ret);
                return undef;
            }
        }

        $ret = &$getFn($vm,$var);

        #print STDERR "  Perl API debug Get(",$var,") = ", $ret,"\n";

        if (!defined($ret) || $ret =~ /^Error:/) {
          $vm->SetError(VM_E_NOPROPERTY, $ret);
          return undef;
        } elsif ($var =~ /\.devices$/ || $var =~ /\.stats$/) {
            my @retlist = split (/,/, $ret);
            return \@retlist;
        } elsif ($var =~ /\.info$/) {
            my %h;
            while ($ret =~ s/^([^:]+):([^:]*):*//) {
                if ($1 eq "functions") {
                    my @l = split(/,/, $2);
                    $h{$1} = \@l;
                } else {
                    $h{$1} = $2;
                }
            }
            return \%h;
        } else {
            return $ret;
        }
    } else {
        my $ret = &$getFn($vm,$var);

        if (!defined($ret)) {
            return undef;
        } elsif ($var eq "Status.devices") {
            my @retlist = split (/,/, $ret);
            return \@retlist;
        } else {
            return $ret;
        }
    }
}

sub answer_question {
    my $stub = $p->profile();
    my $vm = shift;
    my $seq = shift;
    my $choicen = shift;
    my $ret = $vm->AnswerQuestion($seq, $choicen);

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub reload_config {
    my $stub = $p->profile();
    my $vm = shift;
    my $ret = $vm->ReloadConfig();

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub save_config {
    my $stub = $p->profile();
    my $vm = shift;
    my $ret = $vm->SaveConfig();

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub device_connect {
    my $stub = $p->profile();
    my $vm = shift;
    my $device = shift;

    my $fileName = shift;
    my $deviceType = shift;
    my $fileType = shift;

    my $state = $vm->get("Status.power");

    # XXX do a device_disconnect() first?

    my @assignments;

    push @assignments, $device . ".present=TRUE";

    if (defined($fileName)) {
        push @assignments, $device . ".fileName=" . $fileName;
    }

    if (defined($deviceType)) {
        push @assignments, $device . ".deviceType=" . $deviceType;
    }

    if (defined($fileType)) {
        push @assignments, $device . ".fileType=" . $fileType;
    }

    if ($state ne "on") {
        push @assignments, $device . ".startConnected=TRUE";
    }

    my $ret = $vm->SetConfigChanges(@assignments);

    if (!$ret) {
        return undef;
    }

    if ($state eq "on") {
        $ret = $vm->DeviceConnect($device);
    }

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub device_disconnect {
    my $stub = $p->profile();
    my $vm = shift;
    my $device = shift;

    my $state = $vm->get("Status.power");

    my $ret;

    if ($state ne "on") {
        my @assignments;
        push @assignments, $device . ".startConnected=FALSE";
        $ret = $vm->SetConfigChanges(@assignments);
    } else {
        $ret = $vm->DeviceDisconnect($device);
    }

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub device_is_connected {
    my $stub = $p->profile();
    my $vm = shift;
    my $device = shift;

    return $vm->DeviceIsConnected($device);
}

sub tools_install_begin {
    my $stub = $p->profile();
    my $vm = shift;

    my $ret = $vm->ToolsInstallBegin();

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub tools_install_end {
    my $stub = $p->profile();
    my $vm = shift;

    my $ret = $vm->ToolsInstallEnd();

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

#sub set_config_values_from_strings {
#    my $vm = shift;
#
#    my $ret = $vm->SetConfigChanges(@_);
#
#    if ($ret) {
#        return $ret;
#    } else {
#        return undef;
#    }
#}

sub set_config_values {
    my $stub = $p->profile();
    my $vm = shift;

    my %pairs = @_;
    my @strings;

    foreach my $key (keys(%pairs)) {
        my $value = $pairs{$key};
        if ($key =~ m/=/ || $value =~ m/=/) {
            $vm->SetError(VM_E_INVALIDARGS,
                          "Equal sign ('=') not allowed in config key/value pairs");
            return undef;
        } 
        push @strings, ($key . '=' . $value);
    }
                                    
    my $ret = $vm->SetConfigChanges(@strings);

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub set {
    my $stub = $p->profile();
    my $vm = shift;

    my %pairs = @_;
    my @strings;
    my $ret;

    foreach my $key (keys(%pairs)) {
        my $value = $pairs{$key};
        if (!defined($value)) {
            $vm->SetError(VM_E_INVALIDARGS,
                          "uneven number of arguments -- set() requires a hash");
            return undef;
        }
        if ($key =~ m/=/ || $value =~ m/=/) {
            $vm->SetError(VM_E_INVALIDARGS,
                          "Equal sign ('=') not allowed in config key/value pairs");
            return undef;
        }
        if ($key =~ m/^Config\.(.+)$/) {
	    push @strings, ($1 . '=' . $value);
        } else {
            $ret = $vm->SetAsString($key, $value);
	    if (!$ret) {
		return undef;
	    }
	}
    }
    
    if ($#strings > -1) {
	$ret = $vm->SetConfigChanges(@strings);
    }

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}
    
# Convert a list of the form functions[:values]+
# to a hash.  If a value has a command (",") in it,
# it is split into a list itself
sub string_to_complex_hash {
    my $string = shift;
    my @pieces = split(/:/, $string);
    my %hash;

    if (!$#pieces) {
        return undef;
    }

    if (! ($#pieces % 2)) {
        print STDERR "VMware::Control::VM Error: hash has odd number of elements\n";
        return undef;
    }

    while( $#pieces > 0) {
        my $key = shift (@pieces);
        my $value = shift (@pieces);

        #print STDERR "Debug: $key = $value\n";

        my @sub_pieces = split(/,/, $value);
        if ($#sub_pieces) {
            $hash{$key} = \@sub_pieces;
        } else {
            $hash{$key} = $value;
        }
    }

    return %hash;
}

sub set_timeout {
    my $vm = shift;
    my $timeout = shift;
    return $vm->SetTimeout($timeout);
}

#########
#  MKS  #
#########
my %keyboard; # VM --> keyboard definition filename
my %keysDown;     # VM --> list of keys that are down

sub set_keyboard {
    my $stub = $p->profile();
    my $vm = shift;
    my $filename = shift;

    $keyboard{$vm} = VMware::Control::Keycode->new($filename);

    if (!defined($keyboard{$vm})) {
        $vm->SetError(VM_E_INVALIDARGS, "Keyboard definition file $filename could not be loaded.");
    }
        
    return $keyboard{$vm};
}

#Internal function
sub keycode_event {
    my $stub = $p->profile();
    my $vm = shift;
    my $keycode = shift;
    my $down = shift;

    #print STDERR "insert_key_event $keycode $down\n"; #DEBUG
    
    my $ret = $vm->InsertKeyEvent($keycode, $down);

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

sub keycode_is_down {
    my $stub = $p->profile();
    my $vm = shift;
    my $keycode = shift;
    my $keysref = $keysDown{$vm};

    if (defined($keysref)) {
        my @keys = @{ $keysref };

        if ( grep { $_ == $keycode } @keys ) {
            return 1;
        }
    }

    return 0;
}

sub keycode_down {
    my $stub = $p->profile();
    my $vm = shift;
    my $keycode = shift;

    if ($vm->keycode_is_down($keycode)) {
        $vm->SetError(VM_E_INVALIDARGS, "Key is already down");
        return undef;
    }

    my $keysref = $keysDown{$vm};
    my @keys = ();

    if (defined($keysref)) {
        @keys = @{ $keysref };
    }

    push(@keys, $keycode);
    $keysDown{$vm} = \@keys;
    
    return $vm->keycode_event($keycode, 1);
}

sub keycode_up {
    my $stub = $p->profile();
    my $vm = shift;
    my $keycode = shift;

    if (!$vm->keycode_is_down($keycode)) {
        $vm->SetError(VM_E_INVALIDARGS, "Key is already up");
        return undef;
    }

    my $keysref = $keysDown{$vm};
    my @keys = ();

    if (defined($keysref)) {
        @keys = @{ $keysref };
    } else {
        die "keycode_is_down is inconsistent";
    }

    @keys = grep { $_ != $keycode } @keys;
    $keysDown{$vm} = \@keys;

    return $vm->keycode_event($keycode, 0);
}

sub key_allup {
    my $stub = $p->profile();
    my $vm = shift;
    my $keysref = $keysDown{$vm};
    my @keys = ();

    if (defined($keysref)) {
        @keys = @{ $keysref };
    } else {
        return 1;
    }

    foreach my $keycode (@keys) {
        if (!defined($vm->keycode_up($keycode))) {
            return undef;
        }
    }

    return 1;
}
    
sub keycode_stroke {
    my $stub = $p->profile();
    my $vm = shift;
    my $keycode = shift;

    if (!defined($vm->keycode_down($keycode))) {
        return undef;
    }

    return $vm->keycode_up($keycode);
}  

sub key_event {
    my $stub = $p->profile();
    my $vm = shift;
    my $letter = shift;
    my $down = shift;
    my $keyboard = $keyboard{$vm};

    if (!defined($keyboard)) {
        $vm->SetError(VM_E_INVALIDARGS, "Please load a keyboard definition file");
        return undef;
    }

    # Identifiers ("Return", "F2", etc.), Lowercase letters and punc marks
    my $scancode = $keyboard->keystroke_to_scancode($letter);

    if (defined($scancode)) {
        if ($down) {
            return $vm->keycode_down($scancode);
        } else {
            return $vm->keycode_up($scancode);
        }
    }

    #Character conversion to scancode not found
    $vm->SetError(VM_E_INVALIDARGS, "Character conversion to scancode not found for \"$letter\"");

    return undef;
}

sub key_is_down {
    my $stub = $p->profile();
    my $vm = shift;
    my $letter = shift;
    my $keyboard = $keyboard{$vm};

    if (!defined($keyboard)) {
        $vm->SetError(VM_E_INVALIDARGS, "Please load a keyboard definition file");
        return undef;
    }

    # Identifiers ("Return", "F2", etc.), Lowercase letters and punc marks
    my $scancode = $keyboard->keystroke_to_scancode($letter);

    if (defined($scancode)) {
        return $vm->keycode_is_down($scancode);
    }

    #Character conversion to scancode not found
    $vm->SetError(VM_E_INVALIDARGS, "Character conversion to scancode not found for \"$letter\"");

    return undef;
}

sub key_down {
    my $stub = $p->profile();
    my $vm = shift;
    my $letter = shift;

    return $vm->key_event($letter, 1);
}

sub key_up {
    my $stub = $p->profile();
    my $vm = shift;
    my $letter = shift;

    return $vm->key_event($letter, 0);
}

sub key_stroke {
    my $stub = $p->profile();
    my $vm = shift;
    my $key = shift;

    if (!defined($vm->key_down($key))) {
        return undef;
    }

    return $vm->key_up($key);
}  

#Internal function
# Lazily changes modifier keys (Shift, etc.)
sub key_type_one {
    my $stub = $p->profile();
    my $vm = shift;
    my $letter = shift;
    my $keyboard = $keyboard{$vm};

    if (!defined($keyboard)) {
        $vm->SetError(VM_E_INVALIDARGS, "Please load a keyboard definition file");
        return undef;
    }

    #XXX Change this function to be compatible with more modifier keys
    #    (Shift, Alt, Meta, ...)

    # Identifiers ("Return", "F2", etc.), Lowercase letters and punc marks
    my $scancode = $keyboard->keystroke_to_scancode($letter);

    if (defined($scancode)) {
        if ($vm->key_is_down("Shift_L")) {
            $vm->key_up("Shift_L");
        }
        return $vm->keycode_stroke($scancode);
    }

    # Uppercase letters and shifted punc marks (!@#$%^&*()-+{}|:"<>?~)
    my $lc_letter = $keyboard->unshift_key($letter);

    if (defined($lc_letter)) {
        $scancode = $keyboard->keystroke_to_scancode($lc_letter);
    }

    if (defined($scancode)){
        if (!$vm->key_is_down("Shift_L")) {
            $vm->key_down("Shift_L");
        }
        return $vm->keycode_stroke($scancode);
    }

    #Character conversion to scancode not found
    $vm->SetError(VM_E_INVALIDARGS, "Character conversion to scancode sequence not found for \"$letter\"");

    return undef;
}  

sub key_type {
    my $stub = $p->profile();
    my $vm = shift;
    my $string = shift;
    my $letter;

    ## Save the state of the keyboard
    my $keysref = $keysDown{$vm};
    my @keys_sav = ( );

    if (defined($keysref)) {
        @keys_sav = @{ $keysref };
    }

    ## Type each letter
    while (length($letter = substr($string,0,1,""))) {
        if(!$vm->key_type_one($letter)) {
            return undef;
        }
    }

    ## Restore the state of the keyboard
    $keysref = $keysDown{$vm};
    my @keys = ( );

    if (defined($keysref)) {
        @keys = @{ $keysref };
    }

    # Release each extra key
    foreach my $keycode (@keys) {
        if ( ! (grep { $_ == $keycode } @keys_sav)) {
            $vm->keycode_up($keycode);
        }
    }

    # Press any keys that were unpressed during the keytype
    foreach my $keycode (@keys_sav) {
        if ( ! (grep { $_ == $keycode } @keys)) {
            $vm->keycode_down($keycode);
        }
    }

    # Sanity check
    $keysref = $keysDown{$vm};
    @keys = ( );

    if (defined($keysref)) {
        @keys = @{ $keysref };
    }

    foreach my $keycode (@keys) {
        my $k = shift @keys_sav;
        die "Problem restoring keyboard state" unless $k == $keycode;
    }

    return 1;
}

sub save_screenshot {
    my $stub = $p->profile();
    my $vm = shift;
    my $filename = shift;
    my $fileType = shift;

    if (!defined($fileType)) {
        $fileType = "PNG";
    }

    my $ret = $vm->SaveScreenshot($filename, $fileType);

    if ($ret) {
        return $ret;
    } else {
        return undef;
    }
}

#Initialize the VM portion of the API library and return 1 if OK
VMware::Control::VM::Init();

__END__

=head1 NAME

VMware::Control::VM - A perl module for controlling VMware virtual machines.

=head1 SYNOPSIS

  use VMware::Control::VM;

  $vm = VMware::Control::VM::new($server, $vm_identifier);
  $err = $vm->connect();
  if (!defined($err)) {
    ($errorNumber, $errorString) = $vm->get_last_error();
  }

  $ret = $vm->start();

  $ret = $vm->stop();
  $ret = $vm->stop(1);

  $ret = $vm->reset();

  $ret = $vm->suspend_to_disk();

  %info = %{ $vm->info() };

  $value = $vm->get("Status.power");

  $value = $vm->get("Config.log.filename");

  $ret = $vm->device_connect("ide0:1", "device", "/dev/cdrom");

  $ret = $vm->device_disconnect("ide0:1");

  $ret = $vm->reload_config();

  $ret = $vm->save_config();

  $vm->disconnect();

=head1 DESCRIPTION

This packages provides an interface for interacting with a VMware
virtual machine.  Information about the configuration and status of
the machine can be queried.  

CAUTION: This API is preliminary and will change significantly between
this Beta and the final release version.

=item VMware::Control::VM::new($server, $vm_id)

Establish a connection to the specified VM on the specified server.
Valid VM_IDENTIFIERs can be obtained using the
VMware::Control::Server::enumerate method.

=item $vm->connect()

Attempt to establish a connection to the specified VM on the server.

=item $vm->get_last_error()

When a method fails (that is, returns undef), this method will return
the error code corresponding to the failure, along with a string
description (often with more specific error information).

=item $vm->start()

Start (power on) the VM.

=item $vm->stop($force)

Stop (power off) the VM.  If $force is specified and true, perform
hard power-off rather than attempting a soft (clean) shutdown of the
guest OS.

=item $vm->reset($force)

Reset (power off and on) the VM.  If $force is specified and true,
perform hard reset rather than attempting a soft (clean) reboot of the
guest OS.

=item $vm->suspend_to_disk()

Suspend the VM to disk.

=item $vm->info()

Given a VM identifier, this allows us to get more detailed information
on the VM.

This function returns a list of property-value pairs (that is, a hash)
so as to be self describing and extensible.  An example return value
might be C<("Config.filename", "biff.cfg", "Config.name", "biff",
"Config.guestOS", "Novell", "Config.memorySize", 512)>.  Nothing is
available in the C<info> data that isn't available via the C<get>
method, it's just a little simpler to use C<info>.

[Eventually, the set of information returned by C<info> will be
customizable by modifying the C<Config.infoValues> property.]

The C<info> method is designed to simplify quick status summaries.

Returns a list of property values pairs.

=item $vm->get(PROPERTY_NAME)

Get a property from a VM.  

=item $vm->device_connect($device, $type, $pathname)

Connect a removable device to the virtual machine.  The device must be
present in the VM's configuration file and the VM whould be powered
on.  The last two arguments are optional.  $type can be either "file"
or "device", depending on whether the pathname is a file (floppy or
cd-rom images) or a real device.

=item $vm->device_disconnect($device)

Disconnect a removable device from a running virtual machine.  

=item $vm->reload_config()

Tell the VM to reload it's configuration file (e.g., if the config
file has been modified through some external means).  This should only
be used on a powered off virtual machine.

=item $vm->disconnect()

This disconnects from the remote Virtual Machine.

=head1 Name space for Properties

The name space for properties is divided into four top-level domains.
The C<Config.*> domain contains all settings and parameters and is
logically equivalent to the configuration database.  The
C<ServerStatus.*> and C<Status.*> domains contain status and
performance information for the server and specific VMs respectively.
The C<Server.*> domain describes the global configuration information
for the server.

=head2 Variables

The C<Config.*> name space models the existing configuration database.
For example, C<$vm-E<gt>get("Config.ethernet0.present", $foo)> would
set $foo to TRUE if ethernet0 was present.

=head2 Status

The C<Status.*> name space returns information on the state of the VM
and also performance information.  The C<ServerStatus.*> name space
returns information on the state of the server and also performance
information.  Both name spaces are read-only.

A lot more definition work will happen here, but as an example,
C<Status.power> has a value in the set C<("on", "off", "suspended")>.
Performance and statistical information is also available via the
C<Status.stats.*> namespace, although this is not yet well defined.

=head1 AUTHORS

John Haggerty, Daniel Kerns, Brett Vasconcellos.

=head1 COPYRIGHT

    (c) 2000 VMware Incorporated.  All rights reserved.

=head1 VERSION

Version 0.2	   21 Sep  2000

C<% pod2html --header --title="VMware::Control::Server" E<lt> Server.pm E<gt> perlAPI-server.html>

=head1 SEE ALSO

perl(1).

=cut
