#!/usr/bin/perl -w

use strict;
use IO::File;
use Digest::SHA;

# script to upgrade V0.9.1 to V0.9.2 format

my $confvars_0_9_1 = {
    onboot => 'bool',
    autostart => 'bool',
    reboot => 'bool',
    cpulimit => 'natural',
    cpuunits => 'natural',
    hda => 'file',
    hdb => 'file',
    sda => 'file',
    sdb => 'file',
    cdrom => 'file',
    memory => 'natural',
    keyboard => 'lang',
    name => 'string',
    ostype => 'ostype',
    boot => 'boot',
    smp => 'natural',
    acpi => 'bool',
    network => 'network',
};

sub load_config_0_9_1 {
    my ($vmid, $filename) = @_;

    my $fh = new IO::File ($filename, "r") ||
        return undef;

    my $res = {};

    while (my $line = <$fh>) {

        next if $line =~ m/^\#/;

        next if $line =~ m/^\s*$/;

        if ($line =~ m/^([a-z]+):\s*(\S+)\s*$/) {
            my $key = $1;
            my $value = $2;
            if (my $type = $confvars_0_9_1->{$key}) {
		$res->{$key} = $value;
            } else {
                return undef; # unknown setting
            }
        }
    }

    return $res;
}

sub parse_network_0_9_1 {
    my ($data) = @_;

    my $res = {
        type => 'tap',
    };
    foreach my $rec (split (/\s*,\s*/, $data)) {
        if ($rec =~ m/^(tap|user)$/) {
            $res->{type} = $rec;
        } elsif ($rec =~ m/^model\s*=\s*(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)$/) {
            $res->{model} = $1;
        } elsif ($rec =~ m/macaddr\s*=\s*([0-9a-f:]+)/i) {
            $res->{macaddr} = $1;
        } else {
            return undef;
        }
    }

    return $res;
}

sub random_ether_addr {

    my $rand = Digest::SHA::sha1_hex (rand(), time());

    my $mac = '';
    for (my $i = 0; $i < 6; $i++) {
        my $ss = hex (substr ($rand, $i*2, 2));
        if (!$i) {
            $ss &= 0xfe; # clear multicast
            $ss |= 2; # set local id
        }
        $ss = sprintf ("%02X", $ss);

        if (!$i) {
            $mac .= "$ss";
        } else {
            $mac .= ":$ss";
        }
    }

    return $mac;
}

sub convert_0_9_1_to_0_9_2 {
    my ($vmid, $cfile, $conf) = @_;

    print "Upgrading VM $vmid to new format\n";

    die "undefined vm id" if !$vmid || $vmid !~ m/^\d+$/;

    my $dmap = {
	hda => 'ide0',
	hdb => 'ide1',
	sda => 'scsi0',
	sdb => 'scsi1',
    };

    my $tmpdir = "/var/lib/vz/images/$vmid.upgrade";
    my $tmpconf = "$cfile.upgrade";

    my $images = [];

    eval {
	mkdir $tmpdir || die "unable to create dir '$tmpdir'\n";

	my $fh = new IO::File ($cfile, "r") ||
	    die "unable to read config for VM $vmid\n";
	my $newfh = new IO::File ($tmpconf, "w") ||
	    die "unable to create file '$tmpconf'\n";

	while (my $line = <$fh>) {

	    next if $line =~ m/^\#/;
	    
	    next if $line =~ m/^\s*$/;

	    if ($line =~ m/^([a-z]+):\s*(\S+)\s*$/) {
		my $key = $1;
		my $value = $2;
		if (my $type = $confvars_0_9_1->{$key}) {
		    if ($key eq 'network') {
			my $onw = parse_network_0_9_1 ($value);
			if ($onw && ($onw->{type} eq 'tap')) {
			    if (!$onw->{macaddr}) {
				$onw->{macaddr} = random_ether_addr ();
			    }
			    print $newfh "vlan0: $onw->{model}=$onw->{macaddr}\n";
			} elsif ($onw && ($onw->{type} eq 'user')) {
			    if (!$onw->{macaddr}) {
				$onw->{macaddr} = random_ether_addr ();
			    }
			    print $newfh "vlanu: $onw->{model}=$onw->{macaddr}\n";
			} else {
			    die "unable to convert network specification\n";
			}
		    } elsif ($key eq 'cdrom') {
			$value =~ s|^/.*/||;
			print $newfh "ide2: $value,media=cdrom\n";
		    } elsif (defined ($dmap->{$key})) {
			if ($value =~ m|^/var/lib/vz/images/([^/]+)$|) {
			    $value = $1;
			} elsif ($value !~ m|/|) {
			    # no nothing
			} else {
			    die "wrong image path";
			}
		    
			link "/var/lib/vz/images/$value", "$tmpdir/$value";

			(-f "$tmpdir/$value") ||
			    die "unable to create image link\n";

			push @$images, $value;

			print $newfh "$dmap->{$key}: $value\n";
		    } else {
			print $newfh "$key: $value\n";
		    }
		} else {
		    die "unknown setting '$key'\n";
		}
	    }
	}

	if ($conf->{hda}) {
	    print $newfh "bootdisk: ide0\n";
	} elsif ($conf->{hdb}) {
	    print $newfh "bootdisk: ide1\n";
	} elsif ($conf->{sda}) {
	    print $newfh "bootdisk: scsi0\n";
	} elsif ($conf->{sdb}) {
	    print $newfh "bootdisk: scsi1\n";
	}
    };

    my $err = $@;

    if ($err) {
	system ("rm -rf $tmpdir $tmpconf");
    } else {

	if (!rename $tmpdir, "/var/lib/vz/images/$vmid") {
	    system ("rm -rf $tmpdir $tmpconf");
	    die "commiting '/var/lib/vz/images/$vmid' failed - $!\n";
	}
	if (!rename $tmpconf, $cfile) {
	    system ("rm -rf /var/lib/vz/images/$vmid $tmpconf");
	    die "commiting new configuration '$cfile' failed - $!\n";
	}

	foreach my $img (@$images)  {
	    unlink "/var/lib/vz/images/$img";
	}
    }
    die $err if $err;
}

foreach my $vmconf (</etc/qemu-server/*.conf>) {
    next if $vmconf !~ m|/etc/qemu-server/(\d+)\.conf|;
    my $vmid = $1;
    next if -d "/var/lib/vz/images/$vmid"; # already new format

    eval {
	my $res = load_config_0_9_1 ($vmid, $vmconf); 

	if ($res && ($res->{network} || $res->{hda} || $res->{hdb} || 
		     $res->{sda} || $res->{sda} || $res->{cdrom})) {
	    convert_0_9_1_to_0_9_2 ($vmid, $vmconf, $res);
	}
    };

    warn $@ if $@;
}

exit 0;

