]> git.proxmox.com Git - qemu-server.git/commitdiff
imported from svn 'qemu-server/pve2'
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 23 Aug 2011 05:47:04 +0000 (07:47 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 23 Aug 2011 05:47:04 +0000 (07:47 +0200)
27 files changed:
ChangeLog [new file with mode: 0644]
Makefile [new file with mode: 0644]
PVE/API2/Makefile [new file with mode: 0644]
PVE/API2/Qemu.pm [new file with mode: 0644]
PVE/Makefile [new file with mode: 0644]
PVE/QemuServer.pm [new file with mode: 0644]
PVE/VZDump/Makefile [new file with mode: 0644]
PVE/VZDump/QemuServer.pm [new file with mode: 0644]
TODO [new file with mode: 0644]
changelog.Debian [new file with mode: 0644]
control.in [new file with mode: 0644]
copyright [new file with mode: 0644]
gen-vmconf-pod.pl [new file with mode: 0755]
pcitest.pl [new file with mode: 0755]
postinst [new file with mode: 0644]
postrm [new file with mode: 0755]
pve-bridge [new file with mode: 0755]
pve-usb.cfg [new file with mode: 0644]
qemu.init.d [new file with mode: 0644]
qm [new file with mode: 0755]
qm.old [new file with mode: 0755]
qmigrate [new file with mode: 0755]
qmrestore [new file with mode: 0755]
qmupdate [new file with mode: 0755]
sparsecp.c [new file with mode: 0644]
utils.c [new file with mode: 0644]
vmtar.c [new file with mode: 0644]

diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..3af1d19
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,492 @@
+2011-08-19  Proxmox Support Team  <support@proxmox.com>
+
+       * qemu.init.d: create /var/run/qemu-server and
+       /var/lock/qemu-server
+
+2011-08-18  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (print_drive_full): enable aio by default.
+       (parse_drive): support aio option for drives.
+       (config_to_command): alway emit -vga option to kvm (else the vm
+       wont boot)
+
+2011-08-17  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (load_defaults): only read 'keyboard' from
+       datacenter.cfg
+
+2011-08-15  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (config_to_command): use -device instead of
+       -usbdevice, try to assigng fixed port to tablet device
+       (parse_usb_device): impl. new syntax for usb devices (-usb0,
+       -usb1, ...)
+       (parse_vm_config): fix parser for files without newline at eof
+
+2011-08-12  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (config_to_command): include usb2
+       configuration.
+
+       * pve-usb.cfg: use extra file for usb2 configuration
+
+       * PVE/QemuServer.pm (config_to_command): fix for 0.15.0 - supress
+       network boot
+
+2011-08-04  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (config_list): use PVE::Cluster::get_vmlist()
+       to avoid fuse filesystem overhead.
+
+2011-07-06  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/API2/Qemu.pm (update_vm): check to avoid '-$opt' and
+       '-delete $opt' at the same time.
+
+       * PVE/API2/Qemu.pm (update_vm): track unused disks when someone
+       overwrite disk settings
+
+2011-06-29  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (config_to_command): implement -cpu option
+
+2011-06-15  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (vmstatus): include IO stat
+
+       * qm (status): impl. verbose option 
+
+2011-03-09  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (vmstatus): report sizes in bytes, list disk
+       size.
+       (vmstatus): add network traffic
+
+2011-03-04  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (config_to_command): require kvm 0.14.0 
+       (config_to_command): use new "-device pci-assign,..." syntax
+
+2011-03-02  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (parse_net): new 'rate' option.
+
+       * pve-bridge: add traffic shaping
+
+2011-02-25  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm: changed network config systax. We now use
+       -net[n] instead of -vlan[n]. This allows us to add more options.
+
+2011-02-11  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/API2/Qemu.pm: renamed Config.pm to Qemu.pm (whole API inside
+       one file now)
+
+       * PVE/API2/Qemu/VNC.pm: moved code to Config.pm
+
+       * PVE/API2/Qemu/Status.pm: moved code to Config.pm
+
+       * PVE/API2/*: cleanup API Object hierarchiy
+
+       * PVE/API2/Qemu.pm: remove (no longer needed)
+
+2011-01-28  Proxmox Support Team  <support@proxmox.com>
+
+       * qm (vncticket): removed - we don't need that anymore 
+
+2010-10-13  Proxmox Support Team  <support@proxmox.com>
+
+       * qmrestore: set bs=256k for 'dd' 
+
+2010-10-12  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (parse_drive): add 'backup=[yes|no]' attribute.
+
+       * qmrestore (restore_qemu): comment out disks with backup=no
+
+       * PVE/VZDump/QemuServer.pm (prepare): skip disks with backup=no
+
+2010-10-05  Seth Lauzon <seth.lauzon@gmail.com>
+
+        * qmrestore (restore_qemu): new option --unique (create unique MAC
+        address on restore)
+
+2010-09-21  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/QemuServer.pm (new): remove unused method
+       (parse_options): remove unused method
+       (vm_wait_old): remove unused method
+       (vm_cdrom): remove unused method
+       (vm_startall_old): remove unused method
+       (load_diskinfo): rename to load_diskinfo_old (just in case someone
+       still wants to use it)
+
+       * PVE/VZDump/QemuServer.pm: use PVE::QemuServer::foreach_drive()
+
+       * qmigrate: use PVE::QemuServer::foreach_drive()
+
+       * PVE/QemuServer.pm (foreach_drive): new helper.
+
+       * qmigrate (logmsg): remove PVE::QemuServer->new() call.
+
+       * PVE/VZDump/QemuServer.pm (prepare): remove PVE::QemuServer->new() call.
+
+2010-09-17  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/API2/Qemu/Config.pm: impl. unlink/delete for disks.
+
+       * PVE/QemuServer.pm (change_config_nolock): remove 'unusedX'
+       settings if we re-use a previously unused volume.
+       (add_unused_volume): helper to add 'unusedX' settings. Used to
+       store references to volume IDs previously used by this VM.
+
+2010-09-16  Proxmox Support Team  <support@proxmox.com>
+
+       * nqm: renamed to qm
+
+       * qm: renamed to qm.old.
+
+       * PVE/QemuServer.pm (disknames): removed - no longer needed
+       (parse_config): remove diskinfo
+       (config_to_command): also return volume ID list
+       (activate_volumes): removed
+
+       * PVE/QemuServer.pm (parse_options_new): removed - no longer needed
+
+2010-09-14  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/API2/Qemu.pm: new index class
+
+       * PVE/API2/Qemu/Status.pm: use better class name,
+       impl. list cluster nodes
+
+       * PVE/API2/Qemu/Config.pm: use better class name,
+       impl. list cluster nodes
+
+2010-09-13  Proxmox Support Team  <support@proxmox.com>
+
+       * nqm: implement vncticket
+       (run_vnc_proxy): impl. vncproxy
+
+       * PVE/API2/QemuServer.pm: implement destroy_vm()
+
+       * PVE/API2/QemuServerStatus.pm: implement vm_command()
+
+       * nqm: implement monitor command
+
+       * QemuServer.pm (vm_monitor_command): remove $self - not needed at all.
+
+2010-09-10  Proxmox Support Team  <support@proxmox.com>
+
+       * nqm: implement wait, showcmd and  startall
+
+       * QemuServer.pm: register all options with
+       PVE::JSONSchema::register_standard_option()
+
+       * PVE/API2/QemuServer.pm: implement 'update_vm'
+
+       * QemuServer.pm: use a JSON Schema to describe all options. We can
+       now auto-generate the complete API doc.
+
+       * QemuServer.pm (parse_options_new): first try
+
+2010-09-08  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (check_type): register paramater formats with
+       PVE::JSONSchema::register_format()
+       (check_type): raise exceptions if something fail
+
+2010-09-07  Proxmox Support Team  <support@proxmox.com>
+
+       * nqm: temporary file to test new API - will replace 'qm' later.
+
+       * QemuServer.pm (vmstatus): moved from PVE::Qemu.pm
+
+2010-08-26  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/*: created directory hierachy for library compoments
+
+       * QemuServer.pm: use new libpve-common-perl
+
+2010-08-20  Proxmox Support Team  <support@proxmox.com>
+
+       * qmigrate (phase2): abort if migration status is 'failed'
+
+2010-07-19  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (change_config_nolock): carefully catch write
+       errors - avoid zero length config files when filesystem is full.
+       (change_config_nolock): remove tmp file if rename fails
+
+2010-06-29  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (parse_drive): add rerror/werror options (patch
+       from l.mierzwa)
+
+2010-06-25  Proxmox Support Team  <support@proxmox.com>
+
+       * utils.c (full_read): always try to read full blocks (fix vmtar
+       bug - endless growing archive)
+
+       * vmtar.c (scan_sparse_file): use full_read()
+       (dump_sparse_file): use full_read()
+
+2010-04-28  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_to_command): correct order of config option (use sort).
+
+2010-04-21  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm: allow vlan1-to vlan4094 (Since 802.1q allows VLAN
+       identifiers up to 4094).
+
+2010-04-16  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (print_drive_full): only add file=$path if we have
+       a path
+       (config_to_command): do not create default devices (use -nodefaults
+       option with newer qemu-kvm versions)
+
+2009-12-11  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_to_command): only use fairsched when the
+       kernel has openvz support
+
+2009-10-28  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_to_command): use new -boot syntax, always
+       enable boot menu (Press F12...)
+
+2009-10-23  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (vm_stopall): corretly read timeout parameter
+
+2009-10-16  Proxmox Support Team  <support@proxmox.com>
+
+       * qm (create_disks): allow to use /dev/XXX directly.
+
+       * QemuServer.pm (load_diskinfo): also disk size of devices      
+
+       * QemuServer.pm (config_to_command): disable fairsched when the VM
+       uses virtio devices.
+
+2009-10-15  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_to_command): disable fairsched when
+       cpuunits is zero
+       (load_defaults): allow '_' in config file keys.
+
+2009-10-06  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (vm_start): suppress syslog when setting migrate
+       downtime/speed
+
+2009-09-29  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm: new migrate_speed and migrate_downtime settings
+
+       * QemuServer.pm (vm_start): set migrate_speed and migrate_downtime
+
+2009-09-18  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_to_command): support up to 1000 vlans
+
+       * bridge-vlan: generic script - extract vlan from interface name
+       (passe as parameter by kvm)
+
+       * QemuServer.pm (config_to_command): use one generic bridge-vlan
+       script
+
+2009-09-15  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_file_lock): use /var/lock/qemu-server to
+       store lock files.
+       (config_to_command): introduce new 'sockets' and 'cores' settings
+       - replaces 'smp' setting.
+       (change_config_nolock): remove duplicates
+
+2009-09-08  Proxmox Support Team  <support@proxmox.com>
+
+       * VZDump::QemuServer.pm: vzdump plugin
+
+       * sparsecp.c: used by qmrestore
+
+       * vmtar.c: used by qmrestore
+
+       * utils.c: helper functions (vmtar, sparsecp)
+
+       * qmrestore: vzdump restore
+
+       * QemuServer.pm (lock_file): use one file handle per process
+
+       * qemu-server.pod: removed, because it contains no info
+       
+2009-09-07  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (destroy_vm): also remove unused disks
+
+2009-09-03  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (*): correctly lock VM everywhere 
+
+2009-08-18  Proxmox Support Team  <support@proxmox.com>
+
+       * qmigrate: complete rewrite using kvm live migration 
+
+2009-08-14  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (change_config_nolock): implemented new 'migrate'
+       option - protect VM during migration. start/modify VM is disabled
+       when this flag is set.
+
+2009-08-12  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (load_diskinfo): do not include infos about unused
+       disk images - this made problems with config file cache, because
+       list of images can change while the config file itself is
+       unchanged.
+
+2009-07-21  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (get_qemu_drive_options): bug fix - return value.
+
+2009-04-21  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm: fixes for debian lenny
+
+2009-02-26  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (split_args): allow white spaces inside args
+       - use normal shell quoting
+
+2009-02-11  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm: added new 'args' option to pass arbitrary
+       arguments to kvm.
+
+2009-02-02  Proxmox Support Team  <support@proxmox.com>
+
+       * qm: fix manual page 
+
+       * QemuServer.pm (config_to_command): added 'startdate' option
+       (config_to_command): added 'parallel' device pass through option
+
+2009-01-23  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_to_command): new serial device pass
+       through option
+
+2009-01-12  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_to_command): use exec migration protocol
+       instead of file
+
+       * qmigrate (cleanup): update migration protocol syntax (use exec
+       instead of file, because file is no longer supported)
+
+2008-12-19  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (config_to_command): use predefined names for tap
+       devices.
+
+2008-11-17  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm: added new hostusb option
+       (__read_avail): fix duplicate data bug
+
+2008-11-13  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (check_running): remove unreliable starttime check
+       (check_cmdline): better test to check if pidfile belong to
+       specific process
+
+2008-11-11  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (check_running): return undef instead of using
+       next (not inside a loop)
+
+2008-11-04  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (parse_config): added 'description' option
+
+2008-10-01  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm: new time drift fix (tdf) option
+       (vm_start): no timeout when restoring a vm
+
+2008-09-26  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm: new option 'vga' to select VGA type (Cirrus Logic
+       GD5446 PCI or VESA2.0 compatible)
+
+       * QemuServer.pm: new option kvm
+       
+2008-09-25  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (vm_monitor_command): set longer timeouts for
+       migrate and cdrom commands
+
+2008-08-22  Proxmox Support Team  <support@proxmox.com>
+
+       * qm (create_disks): new 'wait' command - wait until VM is stopped
+
+       * qemu-server: removed, mo more daemon needed
+       
+       * qm (print_usage): removed reboot option, because ist just a
+       short for shutdown and start
+
+2008-08-20  Proxmox Support Team  <support@proxmox.com>
+
+       * qemu-server (server_stop): display more infos when stopping the
+       server
+
+2008-08-18  Proxmox Support Team  <support@proxmox.com>
+
+       * qemu-server (vm_status): new status command
+
+2008-07-07  Proxmox Support Team  <support@proxmox.com>
+
+       * qmupdate (convert_0_9_1_to_0_9_2): new script to convert old
+       configuration files
+
+2008-06-27  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm: remove cdrom setting (use ide2 instead)
+       (parse_options): add alias cdrom => ide2
+
+2008-06-24  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (create_image): changed filesystem layout completely
+
+2008-06-20  Proxmox Support Team  <support@proxmox.com> 
+
+       * QemuServer.pm (lock_config): lock config files
+       support virtio block devices
+
+2008-06-18  Proxmox Support Team  <support@proxmox.com>
+
+       * qemu-server (vm_start): read startup error messages correctly
+
+       * QemuServer.pm (disknames): use 'ideX' and 'scsiX' instead of hdX
+       and sdX, complete new syntax
+       (default_img_file): changed image names
+       (create_image): support multiple formats
+       (destroy_vm): destroy all used images inside $imagedir
+
+2008-06-11  Proxmox Support Team  <support@proxmox.com>
+
+       * QemuServer.pm (create_conf): always create an unique mac
+       address, use 'vlanX' instead of 'network', support up to 10
+       bridges
+
+2008-06-10  Proxmox Support Team  <support@proxmox.com>
+
+       * qemu-server (read_proc_stat): fix cpu utilization (display
+       absulute values)
+
+2008-06-09  Proxmox Support Team  <support@proxmox.com>
+
+       * qemu-server (qemu_monitor_write): monitor output now uses '\r\n'
+       instead of '\n\n' (kvm69)
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..588415f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,141 @@
+RELEASE=2.0
+
+VERSION=2.0
+PACKAGE=qemu-server
+PKGREL=1
+
+DESTDIR=
+PREFIX=/usr
+BINDIR=${PREFIX}/bin
+SBINDIR=${PREFIX}/sbin
+BINDIR=${PREFIX}/bin
+LIBDIR=${PREFIX}/lib/${PACKAGE}
+VARLIBDIR=/var/lib/${PACKAGE}
+MANDIR=${PREFIX}/share/man
+DOCDIR=${PREFIX}/share/doc
+PODDIR=${PREFIX}/share/doc/${PACKAGE}/pod
+MAN1DIR=${MANDIR}/man1/
+export PERLDIR=${PREFIX}/share/perl5
+PERLINCDIR=${PERLDIR}/asm-x86_64
+
+ARCH:=$(shell dpkg-architecture -qDEB_BUILD_ARCH)
+DEB=${PACKAGE}_${VERSION}-${PKGREL}_${ARCH}.deb
+
+CDATE:=$(shell date +%F)
+SNAP=${PACKAGE}-${VERSION}-${CDATE}.tar.gz
+
+all: ${DEB}
+
+.PHONY: dinstall
+dinstall: deb
+       dpkg -i ${DEB}
+
+control: control.in
+       sed -e s/@@VERSION@@/${VERSION}/ -e s/@@PKGRELEASE@@/${PKGREL}/ -e s/@@ARCH@@/${ARCH}/<$< >$@
+
+
+vzsyscalls.ph: vzsyscalls.h
+        h2ph -d . vzsyscalls.h
+
+vmtar: vmtar.c utils.c
+       gcc -O2 -Wall -o vmtar vmtar.c
+
+sparsecp: sparsecp.c utils.c
+       gcc -O2 -Wall -o sparsecp sparsecp.c
+
+%.1.gz: %.1.pod
+       rm -f $@
+       cat $<|pod2man -n $* -s 1 -r ${VERSION} -c "Proxmox Documentation"|gzip -c9 >$@
+
+%.5.gz: %.5.pod
+       rm -f $@
+       cat $<|pod2man -n $* -s 5 -r ${VERSION} -c "Proxmox Documentation"|gzip -c9 >$@
+
+%.1.pod: %
+       podselect $*>$@
+
+qm.1.pod: qm PVE/QemuServer.pm
+       perl -I. ./qm printmanpod >$@
+
+vm.conf.5.pod: gen-vmconf-pod.pl PVE/QemuServer.pm 
+       perl -I. ./gen-vmconf-pod.pl >$@
+
+PKGSOURCES=qm qm.1.gz qm.1.pod qmigrate qmigrate.1.gz qmrestore qmrestore.1.gz sparsecp vmtar qemu.init.d qmupdate control vm.conf.5.pod vm.conf.5.gz
+
+.PHONY: install
+install: ${PKGSOURCES}
+       install -d ${DESTDIR}/${SBINDIR}
+       install -d ${DESTDIR}/etc/${PACKAGE}
+       install -d ${DESTDIR}${LIBDIR}
+       install -d ${DESTDIR}${VARLIBDIR}
+       install -d ${DESTDIR}${PODDIR}
+       install -d ${DESTDIR}/usr/share/man/man1
+       install -d ${DESTDIR}/usr/share/man/man5
+       install -d ${DESTDIR}/usr/share/${PACKAGE}
+       install -m 0644 pve-usb.cfg ${DESTDIR}/usr/share/${PACKAGE}
+       make -C PVE install
+       install -m 0755 qm ${DESTDIR}${SBINDIR}
+       install -m 0755 qmigrate ${DESTDIR}${SBINDIR}
+       install -m 0755 qmrestore ${DESTDIR}${SBINDIR}
+       install -D -m 0755 qmupdate ${DESTDIR}${VARLIBDIR}/qmupdate
+       install -D -m 0755 qemu.init.d ${DESTDIR}/etc/init.d/${PACKAGE}
+       install -m 0755 pve-bridge ${DESTDIR}${VARLIBDIR}/pve-bridge
+       install -s -m 0755 vmtar ${DESTDIR}${LIBDIR}
+       install -s -m 0755 sparsecp ${DESTDIR}${LIBDIR}
+#      pod2man -n qemu-server -s 1 -r "proxmox 1.0" -c "Proxmox Documentation" <qemu-server.pod | gzip -9 > ${DESTDIR}/usr/share/man/man1/qemu-server.1.gz
+       install -m 0644 qm.1.gz ${DESTDIR}/usr/share/man/man1/
+       install -m 0644 qm.1.pod ${DESTDIR}/${PODDIR}
+       install -m 0644 qmigrate.1.gz ${DESTDIR}/usr/share/man/man1/
+       install -m 0644 qmrestore.1.gz ${DESTDIR}/usr/share/man/man1/
+       install -m 0644 vm.conf.5.pod ${DESTDIR}/${PODDIR}
+       install -m 0644 vm.conf.5.gz ${DESTDIR}/usr/share/man/man5/
+
+.PHONY: deb ${DEB}
+deb ${DEB}: ${PKGSOURCES}
+       rm -rf debian
+       mkdir debian
+       make DESTDIR=${CURDIR}/debian install
+       perl -I. ./qm verifyapi
+       install -d -m 0755 debian/DEBIAN
+       install -m 0644 control debian/DEBIAN
+       install -m 0755 postinst debian/DEBIAN
+       install -m 0755 postrm debian/DEBIAN
+       echo "/etc/init.d/${PACKAGE}" >>debian/DEBIAN/conffiles
+       install -D -m 0644 copyright debian/${DOCDIR}/${PACKAGE}/copyright
+       install -m 0644 changelog.Debian debian/${DOCDIR}/${PACKAGE}/
+       gzip -9 debian/${DOCDIR}/${PACKAGE}/changelog.Debian
+       dpkg-deb --build debian 
+       mv debian.deb ${DEB}
+       rm -rf debian
+       -lintian ${DEB}
+
+.PHONY: upload
+upload:
+       umount /pve/${RELEASE}; mount /pve/${RELEASE} -o rw 
+       mkdir -p /pve/${RELEASE}/extra
+       rm -rf /pve/${RELEASE}/extra/${PACKAGE}_*.deb
+       rm -rf /pve/${RELEASE}/extra/Packages*
+       cp ${DEB} /pve/${RELEASE}/extra
+       cd /pve/${RELEASE}/extra; dpkg-scanpackages . /dev/null > Packages; gzip -9c Packages > Packages.gz
+       umount /pve/${RELEASE}; mount /pve/${RELEASE} -o ro
+
+.PHONY: clean
+clean:         
+       rm -rf debian *.deb qm.1.gz control vzsyscalls.ph _h2ph_pre.ph ${PACKAGE}-*.tar.gz dist *.1,gz *.pod
+       find . -name '*~' -exec rm {} ';'
+
+
+.PHONY: distclean
+distclean: clean
+
+.PHONY: dist
+${SNAP} dist: distclean
+       rm -rf ${SNAP} dist/qemu-server
+       mkdir -p dist/${PACKAGE}
+       svn co svn://proxdev/server/svn/qemu-server/trunc dist/${PACKAGE}
+       tar cvzf ${SNAP} -C dist --exclude .svn ${PACKAGE}
+       rm -rf dist
+
+.PHONY:
+uploaddist: ${SNAP}
+       scp ${SNAP} pve.proxmox.com:/home/ftp/sources/
diff --git a/PVE/API2/Makefile b/PVE/API2/Makefile
new file mode 100644 (file)
index 0000000..3fd85e2
--- /dev/null
@@ -0,0 +1,6 @@
+SOURCES=           \
+       Qemu.pm 
+
+.PHONY: install
+install:
+       install -D -m 0644 Qemu.pm ${DESTDIR}${PERLDIR}/PVE/API2/Qemu.pm
diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
new file mode 100644 (file)
index 0000000..5cbaa82
--- /dev/null
@@ -0,0 +1,742 @@
+package PVE::API2::Qemu;
+
+use strict;
+use warnings;
+
+use PVE::Cluster;
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param);
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::Storage;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RESTHandler;
+use PVE::QemuServer;
+use PVE::RPCEnvironment;
+use PVE::AccessControl;
+use PVE::INotify;
+
+use Data::Dumper; # fixme: remove
+
+use base qw(PVE::RESTHandler);
+
+my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
+
+my $resolve_cdrom_alias = sub {
+    my $param = shift;
+
+    if (my $value = $param->{cdrom}) {
+       $value .= ",media=cdrom" if $value !~ m/media=/;
+       $param->{ide2} = $value;
+       delete $param->{cdrom};
+    }
+};
+
+__PACKAGE__->register_method({
+    name => 'vmlist', 
+    path => '', 
+    method => 'GET',
+    description => "Virtual machine index (per node).",
+    proxyto => 'node',
+    protected => 1, # qemu pid files are only readable by root
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {},
+       },
+       links => [ { rel => 'child', href => "{vmid}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $vmstatus = PVE::QemuServer::vmstatus();
+
+       return PVE::RESTHandler::hash_to_array($vmstatus, 'vmid');
+
+    }});
+
+__PACKAGE__->register_method({
+    name => 'create_vm', 
+    path => '', 
+    method => 'POST',
+    description => "Create new virtual machine.",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::QemuServer::json_config_properties(
+           {
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
+           }),
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $node = extract_param($param, 'node');
+
+       # fixme: fork worker?
+
+       my $vmid = extract_param($param, 'vmid');
+
+       my $filename = PVE::QemuServer::config_file($vmid);
+       # first test (befor locking)
+       die "unable to create vm $vmid: config file already exists\n" 
+           if -f $filename;
+       
+       my $storecfg = PVE::Storage::config(); 
+
+       &$resolve_cdrom_alias($param);
+
+       foreach my $opt (keys %$param) {
+           if (PVE::QemuServer::valid_drivename($opt)) {
+               my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
+               raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
+
+               PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
+               $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
+           }
+       }
+
+       PVE::QemuServer::add_random_macs($param);
+
+       #fixme: ? syslog ('info', "VM $vmid creating new virtual machine");
+       
+       my $vollist = [];
+
+       my $createfn = sub {
+
+           # second test (after locking test is accurate)
+           die "unable to create vm $vmid: config file already exists\n" 
+               if -f $filename;
+
+           $vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param);
+
+           # try to be smart about bootdisk
+           my @disks = PVE::QemuServer::disknames();
+           my $firstdisk;
+           foreach my $ds (reverse @disks) {
+               next if !$param->{$ds};
+               my $disk = PVE::QemuServer::parse_drive($ds, $param->{$ds});
+               next if PVE::QemuServer::drive_is_cdrom($disk);
+               $firstdisk = $ds;
+           }
+
+           if (!$param->{bootdisk} && $firstdisk) {
+               $param->{bootdisk} = $firstdisk; 
+           }
+
+           PVE::QemuServer::create_conf_nolock($vmid, $param);
+       };
+
+       eval { PVE::QemuServer::lock_config($vmid, $createfn); };
+       my $err = $@;
+
+       if ($err) {
+           foreach my $volid (@$vollist) {
+               eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+               warn $@ if $@;
+           }
+           die "create failed - $err";
+       }
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'vmdiridx',
+    path => '{vmid}', 
+    method => 'GET',
+    proxyto => 'node',
+    description => "Directory index",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {
+               subdir => { type => 'string' },
+           },
+       },
+       links => [ { rel => 'child', href => "{subdir}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $res = [
+           { subdir => 'config' },
+           { subdir => 'status' },
+           { subdir => 'unlink' },
+           { subdir => 'vncproxy' },
+           { subdir => 'rrd' },
+           { subdir => 'rrddata' },
+           ];
+       
+       return $res;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'rrd', 
+    path => '{vmid}/rrd', 
+    method => 'GET',
+    protected => 1, # fixme: can we avoid that?
+    permissions => {
+       path => '/vms/{vmid}',
+       privs => [ 'VM.Audit' ],
+    },
+    description => "Read VM RRD statistics (returns PNG)",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           timeframe => {
+               description => "Specify the time frame you are interested in.",
+               type => 'string',
+               enum => [ 'hour', 'day', 'week', 'month', 'year' ],
+           },
+           ds => {
+               description => "The list of datasources you want to display.",
+               type => 'string', format => 'pve-configid-list',
+           },
+           cf => {
+               description => "The RRD consolidation function",
+               type => 'string',
+               enum => [ 'AVERAGE', 'MAX' ],
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => "object",
+       properties => {
+           filename => { type => 'string' },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       return PVE::Cluster::create_rrd_graph(
+           "pve2-vm/$param->{vmid}", $param->{timeframe}, 
+           $param->{ds}, $param->{cf});
+                                             
+    }});
+
+__PACKAGE__->register_method({
+    name => 'rrddata', 
+    path => '{vmid}/rrddata', 
+    method => 'GET',
+    protected => 1, # fixme: can we avoid that?
+    permissions => {
+       path => '/vms/{vmid}',
+       privs => [ 'VM.Audit' ],
+    },
+    description => "Read VM RRD statistics",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           timeframe => {
+               description => "Specify the time frame you are interested in.",
+               type => 'string',
+               enum => [ 'hour', 'day', 'week', 'month', 'year' ],
+           },
+           cf => {
+               description => "The RRD consolidation function",
+               type => 'string',
+               enum => [ 'AVERAGE', 'MAX' ],
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => "array",
+       items => {
+           type => "object",
+           properties => {},
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       return PVE::Cluster::create_rrd_data(
+           "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
+    }});
+
+
+__PACKAGE__->register_method({
+    name => 'vm_config', 
+    path => '{vmid}/config', 
+    method => 'GET',
+    proxyto => 'node',
+    description => "Get virtual machine configuration.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { 
+       type => "object",
+       properties => {},
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $conf = PVE::QemuServer::load_config($param->{vmid});
+
+       return $conf;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'update_vm', 
+    path => '{vmid}/config', 
+    method => 'PUT',
+    protected => 1,
+    proxyto => 'node',
+    description => "Set virtual machine options.",
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::QemuServer::json_config_properties(
+           {
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
+               skiplock => { 
+                   description => "Ignore locks - only root is allowed to use this option.",
+                   type => 'boolean', 
+                   optional => 1,
+               },
+               delete => {
+                   type => 'string', format => 'pve-configid-list',
+                   description => "A list of settings you want to delete.",
+                   optional => 1,
+               },
+               force => {
+                   type => 'boolean',
+                   description => $opt_force_description,
+                   optional => 1,
+                   requires => 'delete',
+               },
+           }),
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $user = $rpcenv->get_user();
+
+       my $node = extract_param($param, 'node');
+
+       # fixme: fork worker?
+
+       my $vmid = extract_param($param, 'vmid');
+
+       my $skiplock = extract_param($param, 'skiplock');
+       raise_param_exc({ skiplock => "Only root may use this option." }) if $user ne 'root@pam';
+
+       my $delete = extract_param($param, 'delete');
+       my $force = extract_param($param, 'force');
+
+       die "no options specified\n" if !$delete && !scalar(keys %$param);
+
+       my $storecfg = PVE::Storage::config(); 
+
+       &$resolve_cdrom_alias($param);
+
+       my $eject = {};
+       my $cdchange = {};
+
+       foreach my $opt (keys %$param) {
+           if (PVE::QemuServer::valid_drivename($opt)) {
+               my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
+               raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
+               if ($drive->{file} eq 'eject') {
+                   $eject->{$opt} = 1;
+                   delete $param->{$opt};
+                   next;
+               }
+
+               PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
+               $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
+
+               if (PVE::QemuServer::drive_is_cdrom($drive)) {
+                   $cdchange->{$opt} = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
+               }
+           }
+       }
+
+       foreach my $opt (PVE::Tools::split_list($delete)) {
+           $opt = 'ide2' if $opt eq 'cdrom';
+           die "you can't use '-$opt' and '-delete $opt' at the same time\n"
+               if defined($param->{$opt});
+       }
+
+       PVE::QemuServer::add_random_macs($param);
+
+       my $vollist = [];
+
+       my $updatefn =  sub {
+
+           my $conf = PVE::QemuServer::load_config($vmid);
+
+           PVE::QemuServer::check_lock($conf) if !$skiplock;
+
+           foreach my $opt (keys %$eject) {
+               if ($conf->{$opt}) {
+                   my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
+                   $cdchange->{$opt} = undef if PVE::QemuServer::drive_is_cdrom($drive);
+               } else {
+                   raise_param_exc({ $opt => "eject failed - drive does not exist." });
+               }
+           }
+
+           foreach my $opt (keys %$param) {
+               next if !PVE::QemuServer::valid_drivename($opt);
+               next if !$conf->{$opt};
+               my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
+               next if PVE::QemuServer::drive_is_cdrom($old_drive);
+               my $new_drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
+               if ($new_drive->{file} ne $old_drive->{file}) {
+                   my ($path, $owner);
+                   eval { ($path, $owner) = PVE::Storage::path($storecfg, $old_drive->{file}); };
+                   if ($owner && ($owner == $vmid)) {
+                       PVE::QemuServer::add_unused_volume($conf, $param, $old_drive->{file});
+                   }
+               }
+           }
+
+           my $unset = {};
+
+           foreach my $opt (PVE::Tools::split_list($delete)) {
+               $opt = 'ide2' if $opt eq 'cdrom';
+               if (!PVE::QemuServer::option_exists($opt)) {
+                   raise_param_exc({ delete => "unknown option '$opt'" });
+               } 
+               next if !defined($conf->{$opt});
+               if (PVE::QemuServer::valid_drivename($opt)) {
+                   my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
+                   if (PVE::QemuServer::drive_is_cdrom($drive)) {
+                       $cdchange->{$opt} = undef;
+                   } else {
+                       my $volid = $drive->{file};
+
+                       if ($volid !~  m|^/|) {
+                           my ($path, $owner);
+                           eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
+                           if ($owner && ($owner == $vmid)) {
+                               if ($force) {
+                                   push @$vollist, $volid;
+                               } else {
+                                   PVE::QemuServer::add_unused_volume($conf, $param, $volid);
+                               }
+                           }
+                       }
+                   }
+               } elsif ($opt =~ m/^unused/) {
+                   push @$vollist, $conf->{$opt};
+               }
+
+               $unset->{$opt} = 1;
+           }
+
+           PVE::QemuServer::create_disks($storecfg, $vmid, $param);
+
+           PVE::QemuServer::change_config_nolock($vmid, $param, $unset, 1);
+
+           return if !PVE::QemuServer::check_running($vmid);
+
+           foreach my $opt (keys %$cdchange) {
+               my $qdn = PVE::QemuServer::qemu_drive_name($opt, 'cdrom');
+               my $path = $cdchange->{$opt};
+               PVE::QemuServer::vm_monitor_command($vmid, "eject $qdn", 0);
+               PVE::QemuServer::vm_monitor_command($vmid, "change $qdn \"$path\"", 0) if $path;
+           }
+       };
+
+       PVE::QemuServer::lock_config($vmid, $updatefn);
+
+       foreach my $volid (@$vollist) {
+           eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+           # fixme: log ?
+           warn $@ if $@;
+       }
+
+       return undef;
+    }});
+
+
+__PACKAGE__->register_method({
+    name => 'destroy_vm', 
+    path => '{vmid}', 
+    method => 'DELETE',
+    protected => 1,
+    proxyto => 'node',
+    description => "Destroy the vm (also delete all used/owned volumes).",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $user = $rpcenv->get_user();
+
+       my $vmid = $param->{vmid};
+
+       my $skiplock = $param->{skiplock};
+       raise_param_exc({ skiplock => "Only root may use this option." }) 
+           if $user ne 'root@pam';
+
+       my $storecfg = PVE::Storage::config(); 
+
+       PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'unlink', 
+    path => '{vmid}/unlink', 
+    method => 'PUT',
+    protected => 1,
+    proxyto => 'node',
+    description => "Unlink/delete disk images.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           idlist => {
+               type => 'string', format => 'pve-configid-list',
+               description => "A list of disk IDs you want to delete.",
+           },
+           force => {
+               type => 'boolean',
+               description => $opt_force_description,
+               optional => 1,
+           },
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       $param->{delete} = extract_param($param, 'idlist');
+
+       __PACKAGE__->update_vm($param);
+
+       return undef;
+    }});
+
+my $sslcert;
+
+__PACKAGE__->register_method({
+    name => 'vncproxy', 
+    path => '{vmid}/vncproxy', 
+    method => 'POST',
+    protected => 1,
+    permissions => {
+       path => '/vms/{vmid}',
+       privs => [ 'VM.Console' ],
+    },
+    description => "Creates a TCP VNC proxy connections.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { 
+       additionalProperties => 0,
+       properties => {
+           user => { type => 'string' },
+           ticket => { type => 'string' },
+           cert => { type => 'string' },
+           port => { type => 'integer' },
+           upid => { type => 'string' },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $user = $rpcenv->get_user();
+       my $ticket = PVE::AccessControl::assemble_ticket($user);
+
+       my $vmid = $param->{vmid};
+       my $node = $param->{node};
+
+       $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
+           if !$sslcert;
+
+       my $port = PVE::Tools::next_vnc_port();
+
+       my $remip;
+       
+       if ($node ne PVE::INotify::nodename()) {
+           $remip = PVE::Cluster::remote_node_ip($node);
+       }
+
+       # NOTE: kvm VNC traffic is already TLS encrypted,
+       # so we select the fastest chipher here (or 'none'?)
+       my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
+                              '-c', 'blowfish-cbc', $remip] : [];
+
+       my $timeout = 10; 
+
+       my $realcmd = sub {
+           my $upid = shift;
+
+           syslog('info', "starting vnc proxy $upid\n");
+
+           my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
+
+           my $qmstr = join(' ', @$qmcmd);
+
+           # also redirect stderr (else we get RFB protocol errors)
+           my @cmd = ('/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null");
+
+           my $cmdstr = join(' ', @cmd);
+           syslog('info', "CMD3: $cmdstr");
+
+           if (system(@cmd) != 0) {
+               my $msg = "VM $vmid vnc proxy failed - $?";
+               syslog('err', $msg);
+               return;
+           }
+
+           return;
+       };
+
+       my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $user, $realcmd);
+
+       return {
+           user => $user,
+           ticket => $ticket,
+           port => $port, 
+           upid => $upid, 
+           cert => $sslcert, 
+       };
+    }});
+
+__PACKAGE__->register_method({
+    name => 'vm_status', 
+    path => '{vmid}/status',
+    method => 'GET',
+    proxyto => 'node',
+    protected => 1, # qemu pid files are only readable by root
+    description => "Get virtual machine status.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { type => 'object' },
+    code => sub {
+       my ($param) = @_;
+
+       # test if VM exists
+       my $conf = PVE::QemuServer::load_config($param->{vmid});
+
+       my $vmstatus =  PVE::QemuServer::vmstatus($param->{vmid});
+
+       return $vmstatus->{$param->{vmid}};
+    }});
+
+__PACKAGE__->register_method({
+    name => 'vm_command', 
+    path => '{vmid}/status',
+    method => 'PUT',
+    protected => 1,
+    proxyto => 'node',
+    description => "Set virtual machine status.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           skiplock => { 
+               description => "Ignore locks - only root is allowed to use this option.",
+               type => 'boolean', 
+               optional => 1,
+           },
+           command => { 
+               type => 'string',
+               enum => [qw(start stop reset shutdown cad suspend resume) ],
+           },
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $user = $rpcenv->get_user();
+
+       my $node = extract_param($param, 'node');
+
+       # fixme: proxy to correct node
+       # fixme: fork worker?
+
+       my $vmid = extract_param($param, 'vmid');
+
+       my $skiplock = extract_param($param, 'skiplock');
+       raise_param_exc({ skiplock => "Only root may use this option." }) 
+           if $user ne 'root@pam';
+
+       my $command = $param->{command};
+
+       my $storecfg = PVE::Storage::config(); 
+       
+       if ($command eq 'start') {
+           my $statefile = undef; # fixme: --incoming parameter
+           PVE::QemuServer::vm_start($storecfg, $vmid, $statefile, $skiplock);
+       } elsif ($command eq 'stop') {
+           PVE::QemuServer::vm_stop($vmid, $skiplock);
+       } elsif ($command eq 'reset') {
+           PVE::QemuServer::vm_reset($vmid, $skiplock);
+       } elsif ($command eq 'shutdown') {
+           PVE::QemuServer::vm_shutdown($vmid, $skiplock);
+       } elsif ($command eq 'suspend') {
+           PVE::QemuServer::vm_suspend($vmid, $skiplock);
+       } elsif ($command eq 'resume') {
+           PVE::QemuServer::vm_resume($vmid, $skiplock);
+       } elsif ($command eq 'cad') {
+           PVE::QemuServer::vm_cad($vmid, $skiplock);
+       } else {
+           raise_param_exc({ command => "unknown command '$command'" }) 
+       }
+
+       return undef;
+    }});
+
+
+1;
diff --git a/PVE/Makefile b/PVE/Makefile
new file mode 100644 (file)
index 0000000..36753c0
--- /dev/null
@@ -0,0 +1,6 @@
+
+.PHONY: install
+install:
+       install -D -m 0644 QemuServer.pm ${DESTDIR}${PERLDIR}/PVE/QemuServer.pm
+       make -C VZDump install
+       make -C API2 install
\ No newline at end of file
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
new file mode 100644 (file)
index 0000000..8613ba3
--- /dev/null
@@ -0,0 +1,2666 @@
+package PVE::QemuServer;
+
+use strict;
+use POSIX;
+use IO::Handle;
+use IO::Select;
+use IO::File;
+use IO::Dir;
+use IO::Socket::UNIX;
+use File::Basename;
+use File::Path;
+use File::stat;
+use Getopt::Long;
+use Digest::SHA1;
+use Fcntl ':flock';
+use Cwd 'abs_path';
+use IPC::Open3;
+use Fcntl;
+use PVE::SafeSyslog;
+use Storable qw(dclone);
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::Storage;
+use PVE::Tools qw(run_command lock_file file_read_firstline);
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::INotify;
+use PVE::ProcFSTools;
+use Time::HiRes qw (gettimeofday);
+
+my $clock_ticks = POSIX::sysconf(&POSIX::_SC_CLK_TCK);
+
+# Note about locking: we use flock on the config file protect 
+# against concurent actions.
+# Aditionaly, we have a 'lock' setting in the config file. This
+# can be set to 'migrate' or 'backup'. Most actions are not
+# allowed when such lock is set. But you can ignore this kind of
+# lock with the --skiplock flag.
+
+cfs_register_file('/qemu-server/', \&parse_vm_config);  
+
+#no warnings 'redefine';
+
+unless(defined(&_VZSYSCALLS_H_)) {
+    eval 'sub _VZSYSCALLS_H_ () {1;}' unless defined(&_VZSYSCALLS_H_);
+    require 'sys/syscall.ph';
+    if(defined(&__x86_64__)) {
+       eval 'sub __NR_fairsched_vcpus () {499;}' unless defined(&__NR_fairsched_vcpus);
+       eval 'sub __NR_fairsched_mknod () {504;}' unless defined(&__NR_fairsched_mknod);
+       eval 'sub __NR_fairsched_rmnod () {505;}' unless defined(&__NR_fairsched_rmnod);
+       eval 'sub __NR_fairsched_chwt () {506;}' unless defined(&__NR_fairsched_chwt);
+       eval 'sub __NR_fairsched_mvpr () {507;}' unless defined(&__NR_fairsched_mvpr);
+       eval 'sub __NR_fairsched_rate () {508;}' unless defined(&__NR_fairsched_rate);
+       eval 'sub __NR_setluid () {501;}' unless defined(&__NR_setluid);
+       eval 'sub __NR_setublimit () {502;}' unless defined(&__NR_setublimit);
+    }
+    elsif(defined( &__i386__) ) {
+       eval 'sub __NR_fairsched_mknod () {500;}' unless defined(&__NR_fairsched_mknod);
+       eval 'sub __NR_fairsched_rmnod () {501;}' unless defined(&__NR_fairsched_rmnod);
+       eval 'sub __NR_fairsched_chwt () {502;}' unless defined(&__NR_fairsched_chwt);
+       eval 'sub __NR_fairsched_mvpr () {503;}' unless defined(&__NR_fairsched_mvpr);
+       eval 'sub __NR_fairsched_rate () {504;}' unless defined(&__NR_fairsched_rate);
+       eval 'sub __NR_fairsched_vcpus () {505;}' unless defined(&__NR_fairsched_vcpus);
+       eval 'sub __NR_setluid () {511;}' unless defined(&__NR_setluid);
+       eval 'sub __NR_setublimit () {512;}' unless defined(&__NR_setublimit);
+    } else {
+       die("no fairsched syscall for this arch");
+    }
+    require 'asm/ioctl.ph';
+    eval 'sub KVM_GET_API_VERSION () { &_IO(0xAE, 0x);}' unless defined(&KVM_GET_API_VERSION);
+}
+
+sub fairsched_mknod {
+    my ($parent, $weight, $desired) = @_;
+
+    return syscall(&__NR_fairsched_mknod, int ($parent), int ($weight), int ($desired));
+}
+
+sub fairsched_rmnod {
+    my ($id) = @_;
+
+    return syscall(&__NR_fairsched_rmnod, int ($id));
+}
+
+sub fairsched_mvpr {
+    my ($pid, $newid) = @_;
+
+    return syscall(&__NR_fairsched_mvpr, int ($pid), int ($newid));
+}
+
+sub fairsched_vcpus {
+    my ($id, $vcpus) = @_;
+
+    return syscall(&__NR_fairsched_vcpus, int ($id), int ($vcpus));
+}
+
+sub fairsched_rate {
+    my ($id, $op, $rate) = @_;
+
+    return syscall(&__NR_fairsched_rate, int ($id), int ($op), int ($rate));
+}
+
+use constant FAIRSCHED_SET_RATE  => 0;
+use constant FAIRSCHED_DROP_RATE => 1;
+use constant FAIRSCHED_GET_RATE  => 2;
+
+sub fairsched_cpulimit {
+    my ($id, $limit) = @_;
+
+    my $cpulim1024 = int ($limit * 1024 / 100);
+    my $op = $cpulim1024 ? FAIRSCHED_SET_RATE : FAIRSCHED_DROP_RATE;
+
+    return fairsched_rate ($id, $op, $cpulim1024);
+}
+
+my $nodename = PVE::INotify::nodename();
+
+mkdir "/etc/pve/nodes/$nodename";
+my $confdir = "/etc/pve/nodes/$nodename/qemu-server";
+mkdir $confdir;
+
+my $var_run_tmpdir = "/var/run/qemu-server";
+mkdir $var_run_tmpdir;
+
+my $lock_dir = "/var/lock/qemu-server";
+mkdir $lock_dir;
+
+my $pcisysfs = "/sys/bus/pci";
+
+my $keymaphash = PVE::Tools::kvmkeymaps();
+
+my $confdesc = {
+    onboot => {
+       optional => 1,
+       type => 'boolean',
+       description => "Specifies whether a VM will be started during system bootup.",
+       default => 0,
+    },
+    autostart => {
+       optional => 1,
+       type => 'boolean',
+       description => "Automatic restart after crash (currently ignored).",
+       default => 0,
+    },
+    reboot => {
+       optional => 1,
+       type => 'boolean',
+       description => "Allow reboot. If set to '0' the VM exit on reboot.",
+       default => 1,
+    },
+    lock => {
+       optional => 1,
+       type => 'string',
+       description => "Lock/unlock the VM.",
+       enum => [qw(migrate backup)],
+    },
+    cpulimit => {
+       optional => 1,
+       type => 'integer',
+       description => "Limit of CPU usage in per cent. Note if the computer has 2 CPUs, it has total of 200% CPU time. Value '0' indicates no CPU limit.\n\nNOTE: This option is currently ignored.",
+       minimum => 0,
+       default => 0,
+    },
+    cpuunits => {
+       optional => 1,
+       type => 'integer',
+       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
+       minimum => 0,
+       maximum => 500000,
+       default => 1000,
+    },
+    memory => {
+       optional => 1,
+       type => 'integer',
+       description => "Amount of RAM for the VM in MB.",
+       minimum => 16,
+       default => 512,
+    },
+    keyboard => {
+       optional => 1,
+       type => 'string',
+       description => "Keybord layout for vnc server. Default is read from the datacenter configuration file.",
+       enum => [ keys %$keymaphash ],
+       default => 'en-us',
+    },
+    name => {
+       optional => 1,
+       type => 'string',
+       description => "Set a name for the VM. Only used on the configuration web interface.",
+    },
+    description => {
+       optional => 1,
+       type => 'string',
+       description => "Description for the VM. Only used on the configuration web interface.",
+    },
+    ostype => {
+       optional => 1,
+       type => 'string',
+        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 l24 l26)],
+       description => <<EODESC,
+Used to enable special optimization/features for specific
+operating systems:
+
+other  => unspecified OS
+wxp    => Microsoft Windows XP
+w2k    => Microsoft Windows 2000
+w2k3   => Microsoft Windows 2003
+w2k8   => Microsoft Windows 2008
+wvista => Microsoft Windows Vista
+win7   => Microsoft Windows 7
+l24    => Linux 2.4 Kernel
+l26    => Linux 2.6/3.X Kernel
+
+other|l24|l26                  ... no special behaviour
+wxp|w2k|w2k3|w2k8|wvista|win7  ... use --localtime switch
+EODESC
+    },
+    boot => {
+       optional => 1,
+       type => 'string',
+       description => "Boot on floppy (a), hard disk (c), CD-ROM (d), or network (n).",
+       pattern => '[acdn]{1,4}',
+       default => 'cad',
+    },
+    bootdisk => {
+       optional => 1,
+       type => 'string', format => 'pve-qm-bootdisk',
+       description => "Enable booting from specified disk.",
+       pattern => '(ide|scsi|virtio)\d+',
+    },
+    smp => {
+       optional => 1,
+       type => 'integer',
+       description => "The number of CPUs. Please use option -sockets instead.",
+       minimum => 1,
+       default => 1,
+    },
+    sockets => {
+       optional => 1,
+       type => 'integer',
+       description => "The number of CPU sockets.",
+       minimum => 1,
+       default => 1,
+    },
+    cores => {
+       optional => 1,
+       type => 'integer',
+       description => "The number of cores per socket.",
+       minimum => 1,
+       default => 1,
+    },
+    acpi => {
+       optional => 1,
+       type => 'boolean',
+       description => "Enable/disable ACPI.",
+       default => 1,
+    },
+    kvm => {
+       optional => 1,
+       type => 'boolean',
+       description => "Enable/disable KVM hardware virtualization.",
+       default => 1,
+    },
+    tdf => {
+       optional => 1,
+       type => 'boolean',
+       description => "Enable/disable time drift fix.",
+       default => 1,
+    },
+    localtime => { 
+       optional => 1,
+       type => 'boolean',
+       description => "Set the real time clock to local time. This is enabled by default if ostype indicates a Microsoft OS.",
+    },
+    freeze => {
+       optional => 1,
+       type => 'boolean',
+       description => "Freeze CPU at startup (use 'c' monitor command to start execution).",
+    },
+    vga => {
+       optional => 1,
+       type => 'string',
+       description => "Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. Default is 'std' for win7/w2k8, and 'cirrur' for other OS types",
+       enum => [qw(std cirrus vmware)],
+    },
+    hostpci => {
+       optional => 1,
+        type => 'string', format => 'pve-qm-hostpci',
+       typetext => "HOSTPCIDEVICE { , HOSTPCIDEVICE }",
+       description => <<EODESCR,
+Map host pci devices. HOSTPCIDEVICE syntax is:
+
+'bus:dev.func' (hexadecimal numbers)
+
+You can us the 'lspci' command to list existing pci devices.
+
+Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+
+Experimental: user reported problems with this option.
+EODESCR
+    },
+    serial => {
+       optional => 1,
+       type => 'string', format => 'pve-qm-serial',
+       typetext => "SERIALDEVICE { , SERIALDEVICE }",
+       description =>  <<EODESCR,
+Map host serial devices. SERIALDEVICE syntax is /dev/ttyS* 
+
+Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+
+Experimental: user reported problems with this option.
+EODESCR
+    },
+    parallel => {
+       optional => 1,
+       type => 'string', format => 'pve-qm-parallel',
+       typetext => "PARALLELDEVICE { , PARALLELDEVICE }",
+       description =>  <<EODESCR,
+Map host parallel devices. PARALLELDEVICE syntax is /dev/parport* 
+
+Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+
+Experimental: user reported problems with this option.
+EODESCR
+    },
+    startdate => {
+       optional => 1,
+       type => 'string', 
+       typetext => "(now | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS)",
+       description => "Set the initial date of the real time clock. Valid format for date are: 'now' or '2006-06-17T16:01:21' or '2006-06-17'.",
+       pattern => '(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)',
+       default => 'now',
+    },
+    args => {
+       optional => 1,
+       type => 'string',
+       description => <<EODESCR,
+Note: this option is for experts only. It allows you to pass arbitrary arguments to kvm, for example:
+
+args: -no-reboot -no-hpet
+EODESCR
+    },
+    tablet => {
+       optional => 1,
+       type => 'boolean',
+       default => 1,
+       description => "Enable/disable the usb tablet device. This device is usually needed to allow absolute mouse positioning. Else the mouse runs out of sync with normal vnc clients. If you're running lots of console-only guests on one host, you may consider disabling this to save some context switches.",
+    },
+    migrate_speed => {
+       optional => 1,
+       type => 'integer',
+       description => "Set maximum speed (in MB/s) for migrations. Value 0 is no limit.",
+       minimum => 0,
+       default => 0,
+    },
+    migrate_downtime => {
+       optional => 1,
+       type => 'integer',
+       description => "Set maximum tolerated downtime (in seconds) for migrations.",
+       minimum => 0,
+       default => 1,
+    },
+    cdrom => {
+       optional => 1,
+       type => 'string', format => 'pve-qm-drive',
+       typetext => 'volume',
+       description => "This is an alias for option -ide2",
+    },
+    cpu => {
+       optional => 1,
+       description => "Emulated CPU type.",
+       type => 'string',
+       enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom host) ],
+       default => 'qemu64',
+    },
+};
+
+# what about other qemu settings ?
+#cpu => 'string',
+#machine => 'string',
+#fda => 'file',
+#fdb => 'file',
+#mtdblock => 'file',
+#sd => 'file',
+#pflash => 'file',
+#snapshot => 'bool',
+#bootp => 'file',
+##tftp => 'dir',
+##smb => 'dir',
+#kernel => 'file',
+#append => 'string',
+#initrd => 'file',
+##soundhw => 'string',
+
+while (my ($k, $v) = each %$confdesc) {
+    PVE::JSONSchema::register_standard_option("pve-qm-$k", $v);
+}
+
+my $MAX_IDE_DISKS = 4;
+my $MAX_SCSI_DISKS = 16;
+my $MAX_VIRTIO_DISKS = 16;
+my $MAX_USB_DEVICES = 5;
+my $MAX_NETS = 32;
+my $MAX_UNUSED_DISKS = 8;
+
+my $nic_model_list = ['rtl8139', 'ne2k_pci', 'e1000',  'pcnet',  'virtio',
+                     'ne2k_isa', 'i82551', 'i82557b', 'i82559er'];
+my $nic_model_list_txt = join (' ', sort @$nic_model_list);
+
+# fixme:
+my $netdesc = {
+    optional => 1,
+    type => 'string', format => 'pve-qm-net',
+    typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=<dev>][,rate=<mbps>]",
+    description => <<EODESCR,
+Specify network devices. 
+
+MODEL is one of: $nic_model_list_txt
+
+XX:XX:XX:XX:XX:XX should be an unique MAC address. This is 
+automatically generated if not specified.
+
+The bridge parameter can be used to automatically add the interface to a bridge device. The Proxmox VE standard bridge is called 'vmbr0'.
+
+Option 'rate' is used to limit traffic bandwidth from and to this interface. It is specified as floating point number, unit is 'Megabytes per second'.
+
+If you specify no bridge, we create a kvm 'user' (NATed) network device, which provides DHCP and DNS services. The following addresses are used:
+
+10.0.2.2   Gateway
+10.0.2.3   DNS Server
+10.0.2.4   SMB Server
+
+The DHCP server assign addresses to the guest starting from 10.0.2.15.
+
+EODESCR
+};
+PVE::JSONSchema::register_standard_option("pve-qm-net", $netdesc);
+
+for (my $i = 0; $i < $MAX_NETS; $i++)  {
+    $confdesc->{"net$i"} = $netdesc;
+}
+
+my $drivename_hash;
+    
+my $idedesc = {
+    optional => 1,
+    type => 'string', format => 'pve-qm-drive',
+    typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback] [,format=f] [,backup=yes|no] [,aio=native|threads]',
+    description => "Use volume as IDE hard disk or CD-ROM (n is 0 to 3).",
+};
+PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
+
+my $scsidesc = {
+    optional => 1,
+    type => 'string', format => 'pve-qm-drive',
+    typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback] [,format=f] [,backup=yes|no] [,aio=native|threads]',
+    description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to 15).",
+};
+PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
+
+my $virtiodesc = {
+    optional => 1,
+    type => 'string', format => 'pve-qm-drive',
+    typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback] [,format=f] [,backup=yes|no] [,aio=native|threads]',
+    description => "Use volume as VIRTIO hard disk (n is 0 to 15).",
+};
+PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
+
+my $usbdesc = {
+    optional => 1,
+    type => 'string', format => 'pve-qm-usb-device',
+    typetext => 'host=HOSTUSBDEVICE',
+    description => <<EODESCR,
+Configure an USB device (n is 0 to 5). This can be used to
+pass-through usb devices to the guest. HOSTUSBDEVICE syntax is:
+
+'bus-port(.port)*' (decimal numbers) or 
+'vendor_id:product_id' (hexadeciaml numbers)
+
+You can use the 'lsusb -t' command to list existing usb devices.  
+
+Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+
+EODESCR
+};
+PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
+
+
+for (my $i = 0; $i < $MAX_IDE_DISKS; $i++)  {
+    $drivename_hash->{"ide$i"} = 1;
+    $confdesc->{"ide$i"} = $idedesc;
+}
+
+for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++)  {
+    $drivename_hash->{"scsi$i"} = 1;
+    $confdesc->{"scsi$i"} = $scsidesc ;
+}
+
+for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++)  {
+    $drivename_hash->{"virtio$i"} = 1;
+    $confdesc->{"virtio$i"} = $virtiodesc;
+}
+
+for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
+    $confdesc->{"usb$i"} = $usbdesc;
+}
+
+my $unuseddesc = {
+    optional => 1,
+    type => 'string', format => 'pve-volume-id',
+    description => "Reference to unused volumes.",
+};
+
+for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++)  {
+    $confdesc->{"unused$i"} = $unuseddesc;
+}
+
+my $kvm_api_version = 0;
+
+sub kvm_version {
+
+    return $kvm_api_version if $kvm_api_version;
+
+    my $fh = IO::File->new ("</dev/kvm") ||
+       return 0;
+
+    if (my $v = $fh->ioctl (KVM_GET_API_VERSION(), 0)) {
+       $kvm_api_version = $v;
+    }
+
+    $fh->close();
+
+    return  $kvm_api_version;
+}
+
+my $kvm_user_version;
+
+sub kvm_user_version {
+
+    return $kvm_user_version if $kvm_user_version;
+
+    $kvm_user_version = 'unknown';
+
+    my $tmp = `kvm -help 2>/dev/null`;
+    
+    if ($tmp =~ m/^QEMU( PC)? emulator version (\d+\.\d+\.\d+) /) {
+       $kvm_user_version = $2;
+    }
+
+    return $kvm_user_version;
+
+}
+
+my $kernel_has_vhost_net = -c '/dev/vhost-net';
+
+sub disknames {
+    # order is important - used to autoselect boot disk
+    return ((map { "ide$_" } (0 .. ($MAX_IDE_DISKS - 1))),  
+            (map { "scsi$_" } (0 .. ($MAX_SCSI_DISKS - 1))),
+            (map { "virtio$_" } (0 .. ($MAX_VIRTIO_DISKS - 1))));
+}
+
+sub valid_drivename {
+    my $dev = shift;
+
+    return defined ($drivename_hash->{$dev});
+}
+
+sub option_exists {
+    my $key = shift;
+    return defined($confdesc->{$key});
+} 
+
+sub nic_models {
+    return $nic_model_list;
+}
+
+sub os_list_description {
+
+    return {
+       other => 'Other',
+       wxp => 'Windows XP',
+       w2k => 'Windows 2000',
+       w2k3 =>, 'Windows 2003',
+       w2k8 => 'Windows 2008',
+       wvista => 'Windows Vista',
+       win7 => 'Windows 7',
+       l24 => 'Linux 2.4',
+       l26 => 'Linux 2.6',
+    }; 
+}
+
+# a clumsy way to split an argument string into an array,
+# we simply pass it to the cli (exec call)
+# fixme: use Text::ParseWords::shellwords() ?
+sub split_args {
+    my ($str) = @_;
+
+    my $args = [];
+
+    return $args if !$str;
+
+    my $cmd = 'perl -e \'foreach my $a (@ARGV) { print "$a\n"; } \' -- ' . $str;
+
+    eval {
+       run_command ($cmd, outfunc => sub {
+           my $data = shift;
+           push @$args, $data;
+       });
+    };
+    
+    my $err = $@;
+
+    die "unable to parse args: $str\n" if $err;
+
+    return $args;
+}
+
+sub disk_devive_info {
+    my $dev = shift;
+
+    die "unknown disk device format '$dev'" if $dev !~ m/^(ide|scsi|virtio)(\d+)$/;
+
+    my $bus = $1;
+    my $index = $2;
+    my $maxdev = 1024;
+
+    if ($bus eq 'ide') {
+       $maxdev = 2;
+    } elsif ($bus eq 'scsi') {
+       $maxdev = 8;
+    }
+
+    my $controller = int ($index / $maxdev);
+    my $unit = $index % $maxdev;
+
+
+    return { bus => $bus, desc => uc($bus) . " $controller:$unit",
+            controller => $controller, unit => $unit, index => $index };
+
+}
+
+sub qemu_drive_name {
+    my ($dev, $media) = @_; 
+
+    my $info = disk_devive_info ($dev);
+    my $mediastr = '';
+
+    if (($info->{bus} eq 'ide') || ($info->{bus} eq 'scsi')) {
+       $mediastr = ($media eq 'cdrom') ? "-cd" : "-hd";
+       return sprintf("%s%i%s%i", $info->{bus}, $info->{controller}, 
+                      $mediastr, $info->{unit});
+    } else {
+       return sprintf("%s%i", $info->{bus}, $info->{index}); 
+    }
+}
+
+my $cdrom_path;
+
+sub get_cdrom_path {
+
+    return  $cdrom_path if $cdrom_path;
+
+    return $cdrom_path = "/dev/cdrom" if -l "/dev/cdrom";
+    return $cdrom_path = "/dev/cdrom1" if -l "/dev/cdrom1";
+    return $cdrom_path = "/dev/cdrom2" if -l "/dev/cdrom2";
+}
+
+sub get_iso_path {
+    my ($storecfg, $vmid, $cdrom) = @_;
+
+    if ($cdrom eq 'cdrom') {
+       return get_cdrom_path();
+    } elsif ($cdrom eq 'none') {
+       return '';
+    } elsif ($cdrom =~ m|^/|) {
+       return $cdrom;
+    } else {
+       return PVE::Storage::path ($storecfg, $cdrom);
+    }
+}
+
+# try to convert old style file names to volume IDs
+sub filename_to_volume_id {
+    my ($vmid, $file, $media) = @_;
+
+    if (!($file eq 'none' || $file eq 'cdrom' ||
+         $file =~ m|^/dev/.+| || $file =~ m/^([^:]+):(.+)$/)) {
+                           
+       return undef if $file =~ m|/|;
+       
+       if ($media && $media eq 'cdrom') {
+           $file = "local:iso/$file";
+       } else {
+           $file = "local:$vmid/$file";
+       }
+    }
+
+    return $file;
+}
+
+sub verify_media_type {
+    my ($opt, $vtype, $media) = @_;
+
+    return if !$media;
+
+    my $etype;
+    if ($media eq 'disk') {
+       $etype = 'image';
+    } elsif ($media eq 'cdrom') {
+       $etype = 'iso';
+    } else {
+       die "internal error";
+    }
+
+    return if ($vtype eq $etype);
+    
+    raise_param_exc({ $opt => "unexpected media type ($vtype != $etype)" });
+}
+
+sub cleanup_drive_path {
+    my ($opt, $storecfg, $drive) = @_;
+
+    # try to convert filesystem paths to volume IDs
+
+    if (($drive->{file} !~ m/^(cdrom|none)$/) &&
+       ($drive->{file} !~ m|^/dev/.+|) &&
+       ($drive->{file} !~ m/^([^:]+):(.+)$/) &&
+       ($drive->{file} !~ m/^\d+$/)) { 
+       my ($vtype, $volid) = PVE::Storage::path_to_volume_id($storecfg, $drive->{file});
+       raise_param_exc({ $opt => "unable to associate path '$drive->{file}' to any storage"}) if !$vtype;
+       $drive->{media} = 'cdrom' if !$drive->{media} && $vtype eq 'iso';
+       verify_media_type($opt, $vtype, $drive->{media});
+       $drive->{file} = $volid;
+    }
+
+    $drive->{media} = 'cdrom' if !$drive->{media} && $drive->{file} =~ m/^(cdrom|none)$/;
+}
+
+sub create_conf_nolock {
+    my ($vmid, $settings) = @_;
+
+    my $filename = config_file ($vmid);
+
+    die "configuration file '$filename' already exists\n" if -f $filename;
+    
+    my $defaults = load_defaults();
+
+    $settings->{name} = "vm$vmid" if !$settings->{name};
+    $settings->{memory} = $defaults->{memory} if !$settings->{memory};
+
+    my $data = '';
+    foreach my $opt (keys %$settings) {
+       next if !$confdesc->{$opt};
+
+       my $value = $settings->{$opt};
+       next if !$value;
+
+       $data .= "$opt: $value\n";
+    }
+
+    PVE::Tools::file_set_contents($filename, $data);
+}
+
+# ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
+#        [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
+#        [,aio=native|threads]
+
+sub parse_drive {
+    my ($key, $data) = @_;
+
+    my $res = {};
+    
+    # $key may be undefined - used to verify JSON parameters
+    if (!defined($key)) {
+       $res->{interface} = 'unknown'; # should not harm when used to verify parameters
+       $res->{index} = 0;
+    } elsif ($key =~ m/^([^\d]+)(\d+)$/) {
+       $res->{interface} = $1;
+       $res->{index} = $2;
+    } else {
+       return undef;
+    }
+
+    foreach my $p (split (/,/, $data)) {
+       next if $p =~ m/^\s*$/;
+
+       if ($p =~ m/^(file|volume|cyls|heads|secs|trans|media|snapshot|cache|format|rerror|werror|backup|aio)=(.+)$/) {
+           my ($k, $v) = ($1, $2);
+
+           $k = 'file' if $k eq 'volume';
+
+           return undef if defined $res->{$k};
+           
+           $res->{$k} = $v;
+       } else {
+           if (!$res->{file} && $p !~ m/=/) {
+               $res->{file} = $p;
+           } else {
+               return undef;
+           }
+       }
+    }
+
+    return undef if !$res->{file};
+
+    return undef if $res->{cache} && 
+       $res->{cache} !~ m/^(off|none|writethrough|writeback)$/;
+    return undef if $res->{snapshot} && $res->{snapshot} !~ m/^(on|off)$/;
+    return undef if $res->{cyls} && $res->{cyls} !~ m/^\d+$/;
+    return undef if $res->{heads} && $res->{heads} !~ m/^\d+$/;
+    return undef if $res->{secs} && $res->{secs} !~ m/^\d+$/;
+    return undef if $res->{media} && $res->{media} !~ m/^(disk|cdrom)$/;
+    return undef if $res->{trans} && $res->{trans} !~ m/^(none|lba|auto)$/;
+    return undef if $res->{format} && $res->{format} !~ m/^(raw|cow|qcow|qcow2|vmdk|cloop)$/;
+    return undef if $res->{rerror} && $res->{rerror} !~ m/^(ignore|report|stop)$/;
+    return undef if $res->{werror} && $res->{werror} !~ m/^(enospc|ignore|report|stop)$/;
+    return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
+    return undef if $res->{aio} && $res->{aio} !~ m/^(native|threads)$/;
+
+    if ($res->{media} && ($res->{media} eq 'cdrom')) {
+       return undef if $res->{snapshot} || $res->{trans} || $res->{format};
+       return undef if $res->{heads} || $res->{secs} || $res->{cyls}; 
+       return undef if $res->{interface} eq 'virtio';
+    }
+
+    # rerror does not work with scsi drives
+    if ($res->{rerror}) {
+       return undef if $res->{interface} eq 'scsi';
+    }
+
+    return $res;
+}
+
+my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio);
+
+sub print_drive {
+    my ($vmid, $drive) = @_;
+
+    my $opts = '';
+    foreach my $o (@qemu_drive_options, 'backup') {
+       $opts .= ",$o=$drive->{$o}" if $drive->{$o};
+    }
+
+    return "$drive->{file}$opts";
+}
+
+sub print_drive_full {
+    my ($storecfg, $vmid, $drive) = @_;
+
+    my $opts = '';
+    foreach my $o (@qemu_drive_options) {
+       $opts .= ",$o=$drive->{$o}" if $drive->{$o};
+    } 
+
+    # use linux-aio by default (qemu default is threads)
+    $opts .= ",aio=native" if !$drive->{aio}; 
+
+    my $path;
+    my $volid = $drive->{file};
+    if (drive_is_cdrom ($drive)) {
+       $path = get_iso_path ($storecfg, $vmid, $volid);
+    } else {
+       if ($volid =~ m|^/|) {
+           $path = $volid;
+       } else {
+           $path = PVE::Storage::path ($storecfg, $volid);
+       }
+    }
+
+    my $pathinfo = $path ? "file=$path," : '';
+
+    return "${pathinfo}if=$drive->{interface},index=$drive->{index}$opts";
+}
+
+
+sub drive_is_cdrom {
+    my ($drive) = @_;
+
+    return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
+
+}
+
+# netX: e1000=XX:XX:XX:XX:XX:XX,bridge=vmbr0,rate=<mbps>
+sub parse_net {
+    my ($data) = @_;
+
+    my $res = {};
+
+    foreach my $kvp (split (/,/, $data)) {
+
+       if ($kvp =~ m/^(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) {
+           my $model = lc ($1);
+           my $mac = uc($3) || random_ether_addr ();
+           $res->{model} = $model;
+           $res->{macaddr} = $mac;
+       } elsif ($kvp =~ m/^bridge=(\S+)$/) {
+           $res->{bridge} = $1;
+       } elsif ($kvp =~ m/^rate=(\d+(\.\d+)?)$/) {
+           $res->{rate} = $1;
+       } else {
+           return undef;
+       }
+       
+    }
+
+    return undef if !$res->{model};
+
+    return $res;
+}
+
+sub print_net {
+    my $net = shift;
+
+    my $res = "$net->{model}";
+    $res .= "=$net->{macaddr}" if $net->{macaddr};
+    $res .= ",bridge=$net->{bridge}" if $net->{bridge};
+    $res .= ",rate=$net->{rate}" if $net->{rate};
+
+    return $res;
+}
+
+sub add_random_macs {
+    my ($settings) = @_;
+
+    foreach my $opt (keys %$settings) {
+       next if $opt !~ m/^net(\d+)$/;
+       my $net = parse_net($settings->{$opt});
+       next if !$net;
+       $settings->{$opt} = print_net($net);
+    }
+}
+
+sub add_unused_volume {
+    my ($config, $res, $volid) = @_;
+
+    my $key;
+    for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
+       my $test = "unused$ind";
+       if (my $vid = $config->{$test}) {
+           return if $vid eq $volid; # do not add duplicates
+       } else {
+           $key = $test;
+       } 
+    }
+
+    die "To many unused volume - please delete them first.\n" if !$key;
+
+    $res->{$key} = $volid;
+}
+
+# fixme: remove all thos $noerr parameters?
+
+PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk);
+sub verify_bootdisk {
+    my ($value, $noerr) = @_;
+
+    return $value if valid_drivename($value); 
+
+    return undef if $noerr;
+
+    die "invalid boot disk '$value'\n";
+}
+
+PVE::JSONSchema::register_format('pve-qm-net', \&verify_net);
+sub verify_net {
+    my ($value, $noerr) = @_;
+
+    return $value if parse_net($value);
+
+    return undef if $noerr;
+    
+    die "unable to parse network options\n";
+}
+
+PVE::JSONSchema::register_format('pve-qm-drive', \&verify_drive);
+sub verify_drive {
+    my ($value, $noerr) = @_;
+
+    return $value if parse_drive (undef, $value);
+
+    return undef if $noerr;
+    
+    die "unable to parse drive options\n";
+}
+
+PVE::JSONSchema::register_format('pve-qm-hostpci', \&verify_hostpci);
+sub verify_hostpci {
+    my ($value, $noerr) = @_;
+
+    my @dl = split (/,/, $value);
+    foreach my $v (@dl) {
+       if ($v !~ m/^[a-f0-9]{2}:[a-f0-9]{2}\.[a-f0-9]$/i) {
+           return undef if $noerr;
+           die "unable to parse pci id\n";
+       }
+    }
+    return $value;
+}
+
+sub parse_usb_device {
+    my ($value) = @_;
+
+    return undef if !$value;
+
+    my @dl = split (/,/, $value);
+    my $found;
+
+    my $res = {};
+    foreach my $v (@dl) {
+       if ($v =~ m/^host=([0-9A-Fa-f]{4}):([0-9A-Fa-f]{4})$/) {
+           $found = 1;
+           $res->{vendorid} = $1;
+           $res->{productid} = $2;
+       } elsif ($v =~ m/^host=(\d+)\-(\d+(\.\d+)*)$/) {
+           $found = 1;
+           $res->{hostbus} = $1;
+           $res->{hostport} = $2;
+       } else {
+           return undef;
+       }
+    }
+    return undef if !$found;
+
+    return $res;
+}
+PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
+sub verify_usb_device {
+    my ($value, $noerr) = @_;
+
+    return $value if parse_usb_device($value);
+
+    return undef if $noerr;
+    
+    die "unable to parse usb device\n";
+}
+
+PVE::JSONSchema::register_format('pve-qm-parallel', \&verify_parallel);
+sub verify_parallel {
+    my ($value, $noerr) = @_;
+
+    my @dl = split (/,/, $value);
+    foreach my $v (@dl) {
+       if ($v !~ m|^/dev/parport\d+$|) {
+           return undef if $noerr;
+           die "invalid device name\n";
+       }
+    }
+    return $value;
+}
+
+PVE::JSONSchema::register_format('pve-qm-serial', \&verify_serial);
+sub verify_serial {
+    my ($value, $noerr) = @_;
+
+    my @dl = split (/,/, $value);
+    foreach my $v (@dl) {
+       if ($v !~ m|^/dev/ttyS\d+$|) {
+           return undef if $noerr;
+           die "invalid device name\n";
+       }
+    }
+    return $value;
+}
+
+# add JSON properties for create and set function
+sub json_config_properties {
+    my $prop = shift;
+
+    foreach my $opt (keys %$confdesc) {
+       $prop->{$opt} = $confdesc->{$opt};
+    }
+
+    return $prop;
+}
+
+sub check_type {
+    my ($key, $value) = @_;
+
+    die "unknown setting '$key'\n" if !$confdesc->{$key};
+
+    my $type = $confdesc->{$key}->{type};
+
+    if (!defined ($value)) {
+       die "got undefined value\n";
+    }
+
+    if ($value =~ m/[\n\r]/) {
+       die "property contains a line feed\n";
+    }
+
+    if ($type eq 'boolean') {
+       return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i); 
+       return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i); 
+       die "type check ('boolean') failed - got '$value'\n";   
+    } elsif ($type eq 'integer') {
+       return int($1) if $value =~ m/^(\d+)$/;
+       die "type check ('integer') failed - got '$value'\n";
+    } elsif ($type eq 'string') {
+       if (my $fmt = $confdesc->{$key}->{format}) {
+           if ($fmt eq 'pve-qm-drive') {
+               # special case - we need to pass $key to parse_drive()
+               my $drive = parse_drive ($key, $value);
+               return $value if $drive;
+               die "unable to parse drive options\n";
+           }
+           PVE::JSONSchema::check_format($fmt, $value);
+           return $value;      
+       } 
+       $value =~ s/^\"(.*)\"$/$1/;
+       return $value;  
+    } else {
+       die "internal error"
+    }
+}
+
+sub lock_config {
+    my ($vmid, $code, @param) = @_;
+
+    my $filename = config_file_lock ($vmid);
+
+    lock_file($filename, 10, $code, @param);
+
+    die $@ if $@;
+}
+
+sub cfs_config_path {
+    my ($vmid) = @_;
+
+    return "nodes/$nodename/qemu-server/$vmid.conf";
+}
+
+sub config_file {
+    my ($vmid) = @_;
+
+    my $cfspath = cfs_config_path($vmid);
+    return "/etc/pve/$cfspath";
+}
+
+sub config_file_lock {
+    my ($vmid) = @_;
+
+    return "$lock_dir/lock-$vmid.conf";
+}
+
+sub touch_config {
+    my ($vmid) = @_;
+
+    my $conf = config_file ($vmid);
+    utime undef, undef, $conf;
+}
+
+sub create_disks {
+    my ($storecfg, $vmid, $settings) = @_;
+
+    my $vollist = [];
+
+    eval {
+       foreach_drive($settings, sub {
+           my ($ds, $disk) = @_;
+
+           return if drive_is_cdrom ($disk);
+
+           my $file = $disk->{file};
+
+           if ($file =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
+               my $storeid = $2 || 'local';
+               my $size = $3;
+               my $defformat = PVE::Storage::storage_default_format ($storecfg, $storeid);
+               my $fmt = $disk->{format} || $defformat;
+               syslog ('info', "VM $vmid creating new disk - size is $size GB");
+
+               my $volid = PVE::Storage::vdisk_alloc ($storecfg, $storeid, $vmid, 
+                                                      $fmt, undef, $size*1024*1024);
+
+               $disk->{file} = $volid;
+               delete ($disk->{format}); # no longer needed
+               push @$vollist, $volid;
+               $settings->{$ds} = PVE::QemuServer::print_drive ($vmid, $disk);
+           } else {
+               my $path;
+               if ($disk->{file} =~ m|^/dev/.+|) {
+                   $path = $disk->{file};
+               } else {
+                   $path = PVE::Storage::path ($storecfg, $disk->{file});
+               }
+               if (!(-f $path || -b $path)) {
+                   die "image '$path' does not exists\n";
+               }
+           }
+       });
+    };
+
+    my $err = $@;
+
+    if ($err) {
+       syslog ('err', "VM $vmid creating disks failed");
+       foreach my $volid (@$vollist) {
+           eval { PVE::Storage::vdisk_free ($storecfg, $volid); };
+           warn $@ if $@;
+       }
+       die $err;
+    }
+
+    return $vollist;
+}
+
+sub unlink_image {
+    my ($storecfg, $vmid, $volid) = @_;
+
+    die "reject to unlink absolute path '$volid'"
+       if $volid =~ m|^/|;
+    
+    my ($path, $owner) = PVE::Storage::path ($storecfg, $volid);
+
+    die "reject to unlink '$volid' - not owned by this VM"
+       if !$owner || ($owner != $vmid);
+
+    syslog ('info', "VM $vmid deleting volume '$volid'");
+
+    PVE::Storage::vdisk_free ($storecfg, $volid);
+
+    touch_config ($vmid);
+}
+
+sub destroy_vm {
+    my ($storecfg, $vmid) = @_;
+
+    my $conffile = config_file ($vmid);
+
+    my $conf = load_config ($vmid);
+
+    check_lock ($conf);
+
+    # only remove disks owned by this VM 
+    foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+
+       return if drive_is_cdrom ($drive);
+
+       my $volid = $drive->{file};
+       next if !$volid || $volid =~ m|^/|;
+
+       my ($path, $owner) = PVE::Storage::path ($storecfg, $volid);
+       next if !$path || !$owner || ($owner != $vmid);
+
+       PVE::Storage::vdisk_free ($storecfg, $volid);
+    });
+    
+    unlink $conffile;
+
+    # also remove unused disk
+    eval {
+       my $dl = PVE::Storage::vdisk_list ($storecfg, undef, $vmid);
+
+       eval {
+           PVE::Storage::foreach_volid ($dl, sub {
+               my ($volid, $sid, $volname, $d) = @_;
+               PVE::Storage::vdisk_free ($storecfg, $volid);       
+           });
+       };
+       warn $@ if $@;
+
+    };
+    warn $@ if $@;
+}
+
+# fixme: remove?
+sub load_diskinfo_old {
+    my ($storecfg, $vmid, $conf) = @_;
+
+    my $info = {};
+    my $res = {};
+    my $vollist;
+
+    foreach_drive($conf, sub {
+       my ($ds, $di) = @_;
+
+       $res->{$ds} = $di;
+
+       return if drive_is_cdrom ($di);
+
+       if ($di->{file} =~ m|^/dev/.+|) {
+           $info->{$di->{file}}->{size} = PVE::Storage::file_size_info ($di->{file});
+       } else {
+           push @$vollist, $di->{file};
+       }
+    });
+
+    eval {
+       my $dl = PVE::Storage::vdisk_list ($storecfg, undef, $vmid, $vollist);
+
+       PVE::Storage::foreach_volid ($dl, sub {
+           my ($volid, $sid, $volname, $d) = @_;
+           $info->{$volid} = $d;
+       });
+    };
+    warn $@ if $@;
+
+    foreach my $ds (keys %$res) {
+       my $di = $res->{$ds};
+
+       $res->{$ds}->{disksize} = $info->{$di->{file}} ? 
+           $info->{$di->{file}}->{size} / (1024*1024) : 0;
+    }
+
+    return $res;
+}
+
+sub load_config {
+    my ($vmid) = @_;
+
+    my $cfspath = cfs_config_path($vmid);
+
+    my $conf = PVE::Cluster::cfs_read_file($cfspath);
+
+    die "no such VM ('$vmid')\n" if !defined($conf);
+
+    return $conf;
+}  
+
+sub parse_vm_config {
+    my ($filename, $raw) = @_;
+
+    return undef if !defined($raw);
+
+    my $res = {};
+
+    $filename =~ m|/qemu-server/(\d+)\.conf$| 
+       || die "got strange filename '$filename'";
+
+    my $vmid = $1;
+
+    while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
+       my $line = $1;
+       
+       next if $line =~ m/^\#/;
+
+       next if $line =~ m/^\s*$/;
+
+       if ($line =~ m/^(description):\s*(.*\S)\s*$/) {
+           my $key = $1;
+           my $value = PVE::Tools::decode_text($2);
+           $res->{$key} = $value;
+       } elsif ($line =~ m/^(args):\s*(.*\S)\s*$/) {
+           my $key = $1;
+           my $value = $2;
+           $res->{$key} = $value;
+       } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
+           my $key = $1;
+           my $value = $2;
+           eval { $value = check_type($key, $value); };
+           if ($@) {
+               warn "vm $vmid - unable to parse value of '$key' - $@";
+           } else {
+               my $fmt = $confdesc->{$key}->{format};
+               if ($fmt && $fmt eq 'pve-qm-drive') {
+                   my $v = parse_drive($key, $value);
+                   if (my $volid = filename_to_volume_id($vmid, $v->{file}, $v->{media})) {
+                       $v->{file} = $volid;
+                       $value = print_drive ($vmid, $v);
+                   } else {
+                       warn "vm $vmid - unable to parse value of '$key'\n";
+                       next;
+                   }
+               }
+
+               if ($key eq 'cdrom') {
+                   $res->{ide2} = $value;
+               } else {
+                   $res->{$key} = $value;
+               }
+           }
+       }
+    }
+
+    # convert old smp to sockets
+    if ($res->{smp} && !$res->{sockets}) {
+       $res->{sockets} = $res->{smp};
+    } 
+    delete $res->{smp};
+
+    return $res;
+}
+
+sub change_config {
+    my ($vmid, $settings, $unset, $skiplock) = @_;
+
+    lock_config ($vmid, &change_config_nolock, $settings, $unset, $skiplock);
+}
+
+sub change_config_nolock {
+    my ($vmid, $settings, $unset, $skiplock) = @_;
+
+    my $res = {};
+
+    $unset->{ide2} = $unset->{cdrom} if $unset->{cdrom};
+
+    check_lock($settings) if !$skiplock;
+
+    # we do not use 'smp' any longer
+    if ($settings->{sockets}) {
+       $unset->{smp} = 1; 
+    } elsif ($settings->{smp}) {
+       $settings->{sockets} = $settings->{smp};
+       $unset->{smp} = 1;
+    }
+
+    my $new_volids = {};
+
+    foreach my $key (keys %$settings) {
+       my $value = $settings->{$key};
+       if ($key eq 'description') {
+           $value = PVE::Tools::encode_text($value);
+       }
+       eval { $value = check_type($key, $value); };
+       die "unable to parse value of '$key' - $@" if $@;
+       if ($key eq 'cdrom') {
+           $res->{ide2} = $value;
+       } else {
+           $res->{$key} = $value;
+       }
+       if (valid_drivename($key)) {
+           my $drive = PVE::QemuServer::parse_drive($key, $value);
+           $new_volids->{$drive->{file}} = 1 if $drive && $drive->{file};
+       }
+    }
+
+    my $filename = config_file($vmid);
+    my $tmpfn = "$filename.$$.tmp";
+
+    my $fh = new IO::File ($filename, "r") ||
+       die "unable to read config for VM $vmid\n";
+
+    my $werror = "unable to write config for VM $vmid\n";
+
+    my $out = new IO::File ($tmpfn, "w") || die $werror;
+
+    eval {
+
+       my $done;
+
+       while (my $line = <$fh>) {
+       
+           if (($line =~ m/^\#/) || ($line =~ m/^\s*$/)) {
+               die $werror unless print $out $line;
+               next;
+           }
+
+           if ($line =~ m/^([a-z][a-z_]*\d*):\s*(.*\S)\s*$/) {
+               my $key = $1;
+               my $value = $2;
+
+               # remove 'unusedX' settings if we re-add a volume
+               next if $key =~ m/^unused/ && $new_volids->{$value};
+
+               # convert 'smp' to 'sockets'
+               $key = 'sockets' if $key eq 'smp';
+
+               next if $done->{$key};
+               $done->{$key} = 1;
+
+               if (defined ($res->{$key})) {
+                   $value = $res->{$key};
+                   delete $res->{$key};
+               }
+               if (!defined ($unset->{$key})) {
+                   die $werror unless print $out "$key: $value\n";
+               } 
+
+               next;
+           }
+
+           die "unable to parse config file: $line\n";
+       }
+
+       foreach my $key (keys %$res) {
+
+           if (!defined ($unset->{$key})) {
+               die $werror unless print $out "$key: $res->{$key}\n";
+           }
+       }
+    };
+
+    my $err = $@;
+
+    $fh->close();
+
+    if ($err) {
+       $out->close();
+       unlink $tmpfn;
+       die $err;
+    }
+
+    if (!$out->close()) {
+       $err = "close failed - $!\n";
+       unlink $tmpfn;
+       die $err;       
+    }
+
+    if (!rename($tmpfn, $filename)) {
+       $err = "rename failed - $!\n";
+       unlink $tmpfn;
+       die $err;
+    }
+}
+
+sub load_defaults { 
+
+    my $res = {};
+
+    # we use static defaults from our JSON schema configuration
+    foreach my $key (keys %$confdesc) {
+       if (defined(my $default = $confdesc->{$key}->{default})) {
+           $res->{$key} = $default;
+       }
+    }
+   
+    my $conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
+    $res->{keyboard} = $conf->{keyboard} if $conf->{keyboard};
+
+    return $res;
+}
+
+sub config_list {
+    my $vmlist = PVE::Cluster::get_vmlist();
+    my $res = {};
+    return $res if !$vmlist || !$vmlist->{ids};
+    my $ids = $vmlist->{ids};
+
+    my $nodename = PVE::INotify::nodename();
+    foreach my $vmid (keys %$ids) {
+       my $d = $ids->{$vmid};
+       next if !$d->{node} || $d->{node} ne $nodename;
+       $res->{$vmid}->{exists} = 1;
+    }
+    return $res;
+}
+
+sub check_lock {
+    my ($conf) = @_;
+
+    die "VM is locked ($conf->{lock})\n" if $conf->{lock};
+}
+
+sub check_cmdline {
+    my ($pidfile, $pid) = @_;
+
+    my $fh = IO::File->new ("/proc/$pid/cmdline", "r");
+    if (defined ($fh)) {
+       my $line = <$fh>;
+       $fh->close;
+       return undef if !$line;
+       my @param = split (/\0/, $line);
+
+       my $cmd = $param[0];
+       return if !$cmd || ($cmd !~ m|kvm$|);
+
+       for (my $i = 0; $i < scalar (@param); $i++) {
+           my $p = $param[$i];
+           next if !$p;
+           if (($p eq '-pidfile') || ($p eq '--pidfile')) {
+               my $p = $param[$i+1];
+               return 1 if $p && ($p eq $pidfile);
+               return undef;
+           }
+       }
+    }
+    return undef;
+}
+
+sub check_running {
+    my ($vmid) = @_;
+
+    my $filename = config_file ($vmid);
+
+    die "unable to find configuration file for VM $vmid - no such machine\n"
+       if ! -f $filename;
+
+    my $pidfile = pidfile_name ($vmid);
+
+    if (my $fd = IO::File->new ("<$pidfile")) {
+       my $st = stat ($fd);
+       my $line = <$fd>;
+       close ($fd);
+
+       my $mtime = $st->mtime;
+       if ($mtime > time()) {
+           warn "file '$filename' modified in future\n";
+       }
+
+       if ($line =~ m/^(\d+)$/) {
+           my $pid = $1;
+
+           return $pid if ((-d "/proc/$pid") && check_cmdline ($pidfile, $pid));
+       }
+    }
+
+    return undef;
+}
+
+sub vzlist {
+    
+    my $vzlist = config_list();
+
+    my $fd = IO::Dir->new ($var_run_tmpdir) || return $vzlist;
+
+    while (defined(my $de = $fd->read)) { 
+       next if $de !~ m/^(\d+)\.pid$/;
+       my $vmid = $1;
+       next if !defined ($vzlist->{$vmid});
+       if (my $pid = check_running ($vmid)) {
+           $vzlist->{$vmid}->{pid} = $pid;
+       }
+    }
+
+    return $vzlist;
+}
+
+my $storage_timeout_hash = {};
+
+sub disksize {
+    my ($storecfg, $conf) = @_;
+
+    my $bootdisk = $conf->{bootdisk};
+    return undef if !$bootdisk;
+    return undef if !valid_drivename($bootdisk);
+
+    return undef if !$conf->{$bootdisk};
+
+    my $drive = parse_drive($bootdisk, $conf->{$bootdisk});
+    return undef if !defined($drive);
+
+    return undef if drive_is_cdrom($drive);
+
+    my $volid = $drive->{file};
+    return undef if !$volid;
+
+    my $path;
+    my $storeid;
+    my $timeoutid;
+
+    if ($volid =~ m|^/|) {
+       $path = $timeoutid = $volid;
+    } else {
+       $storeid = $timeoutid = PVE::Storage::parse_volume_id ($volid);
+       $path = PVE::Storage::path($storecfg, $volid);
+    }
+
+    my $last_timeout = $storage_timeout_hash->{$timeoutid};
+    if ($last_timeout) {
+       if ((time() - $last_timeout) < 30) {
+           # skip storage with errors
+           return undef ;
+       }
+       delete $storage_timeout_hash->{$timeoutid};
+    }
+
+    my ($size, $format, $used);
+
+    ($size, $format, $used) = PVE::Storage::file_size_info($path, 1);
+
+    if (!defined($format)) {
+       # got timeout
+       $storage_timeout_hash->{$timeoutid} = time();
+       return undef;
+    }
+
+    return wantarray ? ($size, $used) : $size;
+}
+
+my $last_proc_pid_stat;
+
+sub vmstatus {
+    my ($opt_vmid) = @_;
+
+    my $res = {};
+
+    my $storecfg = PVE::Storage::config(); 
+
+    my $list = vzlist();
+    my ($uptime) = PVE::ProcFSTools::read_proc_uptime();
+
+    foreach my $vmid (keys %$list) {
+       next if $opt_vmid && ($vmid ne $opt_vmid);
+
+       my $cfspath = cfs_config_path($vmid);
+       my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
+
+       my $d = {};
+       $d->{pid} = $list->{$vmid}->{pid};
+
+       # fixme: better status?
+       $d->{status} = $list->{$vmid}->{pid} ? 'running' : 'stopped';
+
+       my ($size, $used) = disksize($storecfg, $conf);
+       if (defined($size) && defined($used)) {
+           $d->{disk} = $used;
+           $d->{maxdisk} = $size;
+       } else {
+           $d->{disk} = 0;
+           $d->{maxdisk} = 0;
+       }
+
+       $d->{cpus} = ($conf->{sockets} || 1) * ($conf->{cores} || 1);
+       $d->{name} = $conf->{name} || "VM $vmid";
+       $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024) : 0; 
+
+
+       $d->{uptime} = 0;
+       $d->{cpu} = 0;
+       $d->{relcpu} = 0;
+       $d->{mem} = 0;
+
+       $d->{netout} = 0;
+       $d->{netin} = 0;
+
+       $d->{diskread} = 0;
+       $d->{diskwrite} = 0;
+
+       $res->{$vmid} = $d;
+    }
+
+    my $netdev = PVE::ProcFSTools::read_proc_net_dev();
+    foreach my $dev (keys %$netdev) {
+       next if $dev !~ m/^tap([1-9]\d*)i/;
+       my $vmid = $1;
+       my $d = $res->{$vmid};
+       next if !$d;
+       
+       $d->{netout} += $netdev->{$dev}->{receive};
+       $d->{netin} += $netdev->{$dev}->{transmit};
+    }
+
+    my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
+    my $cpucount = $cpuinfo->{cpus} || 1;
+    my $ctime = gettimeofday;
+
+    foreach my $vmid (keys %$list) {
+
+       my $d = $res->{$vmid};
+       my $pid = $d->{pid};
+       next if !$pid;
+
+       if (my $fh = IO::File->new("/proc/$pid/io", "r")) {
+           my $data = {};
+           while (defined (my $line = <$fh>)) {
+               if ($line =~ m/^([rw]char):\s+(\d+)$/) {
+                   $data->{$1} = $2;
+               }
+           }
+           close($fh);
+           $d->{diskread} = $data->{rchar} || 0;
+           $d->{diskwrite} = $data->{wchar} || 0;
+       }
+
+       my $statstr = file_read_firstline("/proc/$pid/stat");
+       next if !$statstr;
+
+       my ($utime, $stime, $vsize, $rss, $starttime);
+       if ($statstr =~ m/^$pid \(.*\) \S (-?\d+) -?\d+ -?\d+ -?\d+ -?\d+ \d+ \d+ \d+ \d+ \d+ (\d+) (\d+) (-?\d+) (-?\d+) -?\d+ -?\d+ -?\d+ 0 (\d+) (\d+) (-?\d+) \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ -?\d+ -?\d+ \d+ \d+ \d+/) {
+           ($utime, $stime, $vsize, $rss, $starttime) = ($2, $3, $7, $8 * 4096, $6);
+       } else {
+           next;
+       }
+       my $used = $utime + $stime;
+
+       my $vcpus = $d->{cpus} > $cpucount ? $cpucount : $d->{cpus};
+
+       $d->{uptime} = int ($uptime - ($starttime/100));
+
+       if ($vsize) {
+           $d->{mem} = int (($rss/$vsize)*$d->{maxmem});
+       }
+
+       my $old = $last_proc_pid_stat->{$pid};
+       if (!$old) {
+           $last_proc_pid_stat->{$pid} = { 
+               time => $ctime, 
+               used => $used,
+               cpu => 0,
+               relcpu => 0,
+           };
+           next;
+       }
+
+       my $dtime = ($ctime -  $old->{time}) * $cpucount * $clock_ticks;
+
+       if ($dtime > 1000) {
+           my $dutime = $used -  $old->{used};
+
+           $d->{cpu} = $dutime/$dtime;
+           $d->{relcpu} = ($d->{cpu} * $cpucount) / $vcpus;
+           $last_proc_pid_stat->{$pid} = {
+               time => $ctime, 
+               used => $used,
+               cpu => $d->{cpu},
+               relcpu => $d->{relcpu},
+           };
+       } else {
+           $d->{cpu} = $old->{cpu};
+           $d->{relcpu} = $old->{relcpu};
+       }
+    }
+
+    return $res;
+}
+
+sub foreach_drive {
+    my ($conf, $func) = @_;
+
+    foreach my $ds (keys %$conf) {
+       next if !valid_drivename($ds);
+
+       my $drive = parse_drive ($ds, $conf->{$ds});
+       next if !$drive;
+
+       &$func($ds, $drive);
+    }
+}
+
+sub config_to_command {
+    my ($storecfg, $vmid, $conf, $defaults, $migrate_uri) = @_;
+
+    my $cmd = [];
+
+    my $kvmver = kvm_user_version();
+    my $vernum = 0; # unknown
+    if ($kvmver =~ m/^(\d+)\.(\d+)\.(\d+)$/) {
+       $vernum = $1*1000000+$2*1000+$3;
+    }
+
+    die "detected old qemu-kvm binary ($kvmver)\n" if $vernum < 14000;
+
+    my $have_ovz = -f '/proc/vz/vestat';
+
+    push @$cmd, '/usr/bin/kvm';
+
+    push @$cmd, '-id', $vmid;
+
+    my $use_virtio = 0;
+
+    my $socket = monitor_socket ($vmid);
+    push @$cmd, '-monitor', "unix:$socket,server,nowait";
+
+    $socket = vnc_socket ($vmid);
+    push @$cmd,  '-vnc', "unix:$socket,x509,password";
+
+    push @$cmd, '-pidfile' , pidfile_name ($vmid);
+    push @$cmd, '-daemonize';
+
+    push @$cmd, '-incoming', $migrate_uri if $migrate_uri;
+
+    # include usb device config
+    push @$cmd, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg';
+    
+    # enable absolute mouse coordinates (needed by vnc)
+    my $tablet = defined ($conf->{tablet}) ? $conf->{tablet} : $defaults->{tablet};
+    push @$cmd, '-device', 'usb-tablet,bus=ehci.0,port=6' if $tablet;
+
+    # host pci devices
+    if (my $pcidl = $conf->{hostpci}) {
+       my @dl = split (/,/, $pcidl);
+       foreach my $dev (@dl) {
+           push @$cmd, '-device', "pci-assign,host=$dev" if $dev;
+       }
+    }
+
+    # usb devices
+    for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
+       my $d = parse_usb_device($conf->{"usb$i"});
+       next if !$d;
+       if ($d->{vendorid} && $d->{productid}) {
+           push @$cmd, '-device', "usb-host,vendorid=$d->{vendorid},productid=$d->{productid}";
+       } elsif (defined($d->{hostbus}) && defined($d->{hostport})) {
+           push @$cmd, '-device', "usb-host,hostbus=$d->{hostbus},hostport=$d->{hostport}";
+       }
+    }
+
+    if (my $usbdl = $conf->{hostusb}) {
+       my @dl = split (/,/, $usbdl);
+       foreach my $dev (@dl) {
+           push @$cmd, '-usbdevice', "host:$dev" if $dev;
+       }
+    }
+
+    # serial devices
+    if (my $serdl = $conf->{serial}) {
+       my @dl = split (/,/, $serdl);
+       foreach my $dev (@dl) {
+           next if !$dev;
+           if (-c $dev) {
+               push @$cmd, '-serial', "$dev";
+           }
+       }
+    }
+
+    # parallel devices
+    if (my $pardl = $conf->{parallel}) {
+       my @dl = split (/,/, $pardl);
+       foreach my $dev (@dl) {
+           next if !$dev;
+           if (-c $dev) {
+               push @$cmd, '-parallel', "$dev";
+           }
+       }
+    }
+
+    my $vmname = $conf->{name} || "vm$vmid";
+
+    push @$cmd, '-name', $vmname;
+    
+    my $sockets = 1;
+    $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
+    $sockets = $conf->{sockets} if  $conf->{sockets};
+
+    my $cores = $conf->{cores} || 1;
+
+    my $boot_opt;
+
+    push @$cmd, '-smp', "sockets=$sockets,cores=$cores";
+
+    push @$cmd, '-cpu', $conf->{cpu} if $conf->{cpu};
+
+    $boot_opt = "menu=on";
+    if ($conf->{boot}) {
+       $boot_opt .= ",order=$conf->{boot}";
+    }
+
+    push @$cmd, '-nodefaults';
+
+    push @$cmd, '-boot', $boot_opt if $boot_opt;
+
+    push @$cmd, '-no-acpi' if defined ($conf->{acpi}) && $conf->{acpi} == 0;
+
+    push @$cmd, '-no-reboot' if  defined ($conf->{reboot}) && $conf->{reboot} == 0;
+
+    my $vga = $conf->{vga};
+    if (!$vga) {
+       if ($conf->{ostype} && ($conf->{ostype} eq 'win7' || $conf->{ostype} eq 'w2k8')) {
+           $vga = 'std';
+       } else {
+           $vga = 'cirrus';
+       }
+    }
+  
+    push @$cmd, '-vga', $vga if $vga; # for kvm 77 and later
+
+    # time drift fix
+    my $tdf = defined ($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf};
+    push @$cmd, '-tdf' if $tdf;
+
+    my $nokvm = defined ($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
+
+    if (my $ost = $conf->{ostype}) {
+       # other, wxp, w2k, w2k3, w2k8, wvista, win7, l24, l26
+
+       if ($ost =~ m/^w/) { # windows
+           push @$cmd, '-localtime' if !defined ($conf->{localtime});
+
+           # use rtc-td-hack when acpi is enabled
+           if (!(defined ($conf->{acpi}) && $conf->{acpi} == 0)) {
+               push @$cmd, '-rtc-td-hack';
+           }
+       }
+
+       # -tdf ?
+       # -no-acpi 
+       # -no-kvm 
+       # -win2k-hack ?
+    }
+
+    push @$cmd, '-no-kvm' if $nokvm;
+
+    push @$cmd, '-localtime' if $conf->{localtime};
+
+    push @$cmd, '-startdate', $conf->{startdate} if $conf->{startdate};
+
+    push @$cmd, '-S' if $conf->{freeze};
+
+    # set keyboard layout
+    my $kb = $conf->{keyboard} || $defaults->{keyboard};
+    push @$cmd, '-k', $kb if $kb;
+
+    # enable sound
+    #my $soundhw = $conf->{soundhw} || $defaults->{soundhw};
+    #push @$cmd, '-soundhw', 'es1370';
+    #push @$cmd, '-soundhw', $soundhw if $soundhw;
+
+    my $vollist = [];
+
+    foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+
+       eval { 
+           PVE::Storage::parse_volume_id ($drive->{file});
+           push @$vollist, $drive->{file};
+       }; # ignore errors
+
+       $use_virtio = 1 if $ds =~ m/^virtio/;
+       my $tmp = print_drive_full ($storecfg, $vmid, $drive);
+       $tmp .= ",boot=on" if $conf->{bootdisk} && ($conf->{bootdisk} eq $ds);
+       push @$cmd, '-drive', $tmp;
+    });
+
+    push @$cmd, '-m', $conf->{memory} || $defaults->{memory};
+    my $foundnet = 0;
+
+    foreach my $k (sort keys %$conf) {
+       next if $k !~ m/^net(\d+)$/;
+       my $i = int ($1);
+
+       die "got strange net id '$i'\n" if $i >= ${MAX_NETS};
+
+       if ($conf->{"net$i"} && (my $net = parse_net($conf->{"net$i"}))) {
+
+           $foundnet = 1;
+
+           my $ifname = "tap${vmid}i$i";
+
+           # kvm uses TUNSETIFF ioctl, and that limits ifname length
+           die "interface name '$ifname' is too long (max 15 character)\n" 
+               if length($ifname) >= 16;
+
+           my $device = $net->{model};
+           my $vhostparam = '';
+           if ($net->{model} eq 'virtio') {
+               $use_virtio = 1;
+               $device = 'virtio-net-pci';
+               $vhostparam = ',vhost=on' if $kernel_has_vhost_net;
+           };
+
+           if ($net->{bridge}) {
+               push @$cmd, '-netdev', "type=tap,id=${k},ifname=${ifname},script=/var/lib/qemu-server/pve-bridge$vhostparam";
+           } else {
+               push @$cmd, '-netdev', "type=user,id=${k},hostname=$vmname";
+           }
+
+           # qemu > 0.15 always try to boot from network - we disable that by
+           # not loading the pxe rom file
+           my $extra = (!$conf->{boot} || ($conf->{boot} !~ m/n/)) ?
+               "romfile=," : '';
+           push @$cmd, '-device', "$device,${extra}mac=$net->{macaddr},netdev=${k}";
+       }
+    } 
+       
+    push @$cmd, '-net', 'none' if !$foundnet;
+
+    # hack: virtio with fairsched is unreliable, so we do not use fairsched
+    # when the VM uses virtio devices.
+    if (!$use_virtio && $have_ovz) { 
+     
+       my $cpuunits = defined ($conf->{cpuunits}) ? 
+           $conf->{cpuunits} : $defaults->{cpuunits};
+
+       push @$cmd, '-cpuunits', $cpuunits if $cpuunits;
+
+       # fixme: cpulimit is currently ignored
+       #push @$cmd, '-cpulimit', $conf->{cpulimit} if $conf->{cpulimit};
+    }
+
+    # add custom args
+    if ($conf->{args}) {
+       my $aa = split_args ($conf->{args});
+       push @$cmd, @$aa;
+    }
+
+    return wantarray ? ($cmd, $vollist) : $cmd;
+}
+sub vnc_socket {
+    my ($vmid) = @_;
+    return "${var_run_tmpdir}/$vmid.vnc";
+}
+
+sub monitor_socket {
+    my ($vmid) = @_;
+    return "${var_run_tmpdir}/$vmid.mon";
+}
+
+sub pidfile_name {
+    my ($vmid) = @_;
+    return "${var_run_tmpdir}/$vmid.pid";
+}
+
+sub random_ether_addr {
+
+    my $rand = Digest::SHA1::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 next_migrate_port {
+
+    for (my $p = 60000; $p < 60010; $p++) {
+
+       my $sock = IO::Socket::INET->new (Listen => 5,
+                                         LocalAddr => 'localhost',
+                                         LocalPort => $p,
+                                         ReuseAddr => 1,
+                                         Proto     => 0);
+
+       if ($sock) {
+           close ($sock);
+           return $p;
+       }
+    }
+
+    die "unable to find free migration port";
+}
+
+sub vm_start {
+    my ($storecfg, $vmid, $statefile, $skiplock) = @_;
+
+    lock_config ($vmid, sub {
+       my $conf = load_config ($vmid);
+
+       check_lock ($conf) if !$skiplock;
+
+       if (check_running ($vmid)) {
+           my $msg = "VM $vmid already running - start failed\n" ;
+           syslog ('err', $msg);
+           die $msg;
+       } else {
+           syslog ('info', "VM $vmid start");
+       }
+
+       my $migrate_uri;
+       my $migrate_port = 0;
+
+       if ($statefile) {
+           if ($statefile eq 'tcp') {
+               $migrate_port = next_migrate_port();
+               $migrate_uri = "tcp:localhost:${migrate_port}";
+           } else {
+               if (-f $statefile) {
+                   $migrate_uri = "exec:cat $statefile";
+               } else {
+                   warn "state file '$statefile' does not exist - doing normal startup\n";
+               }
+           }
+       }
+
+       my $defaults = load_defaults();
+
+       my ($cmd, $vollist) = config_to_command ($storecfg, $vmid, $conf, $defaults, $migrate_uri);
+       # host pci devices
+       if (my $pcidl = $conf->{hostpci}) {
+           my @dl = split (/,/, $pcidl);
+           foreach my $dev (@dl) {
+               $dev = lc($dev);
+               my $info = pci_device_info("0000:$dev");
+               die "no pci device info for device '$dev'\n" if !$info;
+               die "can't unbind pci device '$dev'\n" if !pci_dev_bind_to_stub($info);
+               die "can't reset pci device '$dev'\n" if !pci_dev_reset($info);
+           }
+       }
+
+       PVE::Storage::activate_volumes($storecfg, $vollist);
+
+       eval  { run_command ($cmd, timeout => $migrate_uri ? undef : 30); };
+
+       my $err = $@;
+
+       if ($err) {
+           my $msg = "start failed: $err";
+           syslog ('err', "VM $vmid $msg");
+           die $msg;
+       }
+
+       if ($statefile) {
+
+           if ($statefile eq 'tcp') {
+               print "migration listens on port $migrate_port\n";
+           } else {
+               unlink $statefile;
+               # fixme: send resume - is that necessary ?
+               eval  { vm_monitor_command ($vmid, "cont", 1) };
+           }
+       }
+
+       if (my $migrate_speed = 
+           $conf->{migrate_speed} || $defaults->{migrate_speed}) {
+           my $cmd = "migrate_set_speed ${migrate_speed}m";
+           eval { vm_monitor_command ($vmid, $cmd, 1); };
+       }
+
+       if (my $migrate_downtime = 
+           $conf->{migrate_downtime} || $defaults->{migrate_downtime}) {
+           my $cmd = "migrate_set_downtime ${migrate_downtime}";
+           eval { vm_monitor_command ($vmid, $cmd, 1); };
+       }
+    });
+}
+
+sub __read_avail {
+    my ($fh, $timeout) = @_;
+
+    my $sel = new IO::Select;
+    $sel->add ($fh);
+
+    my $res = '';
+    my $buf;
+
+    my @ready;
+    while (scalar (@ready = $sel->can_read ($timeout))) {
+       my $count;
+       if ($count = $fh->sysread ($buf, 8192)) {
+           if ($buf =~ /^(.*)\(qemu\) $/s) {
+               $res .= $1;
+               last;
+           } else {
+               $res .= $buf;
+           }
+       } else {
+           if (!defined ($count)) {
+               die "$!\n";
+           }
+           last;
+       }
+    }
+
+    die "monitor read timeout\n" if !scalar (@ready);
+
+    return $res;
+}
+
+sub vm_monitor_command {
+    my ($vmid, $cmdstr, $nolog) = @_;
+
+    my $res;
+
+    syslog ("info", "VM $vmid monitor command '$cmdstr'") if !$nolog;
+
+    eval {
+       die "VM not running\n"  if !check_running ($vmid);
+
+       my $sname = monitor_socket ($vmid);
+
+       my $sock = IO::Socket::UNIX->new ( Peer => $sname ) ||
+           die "unable to connect to VM $vmid socket - $!\n";
+
+       my $timeout = 3;
+
+       # hack: migrate sometime blocks the monitor (when migrate_downtime 
+       # is set)
+       if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) {
+           $timeout = 60*60; # 1 hour
+       }
+
+       # read banner;
+       my $data = __read_avail ($sock, $timeout);
+       
+       if ($data !~ m/^QEMU\s+(\S+)\s+monitor\s/) {
+           die "got unexpected qemu monitor banner\n";
+       }
+
+       my $sel = new IO::Select;
+       $sel->add ($sock);
+
+       if (!scalar (my @ready = $sel->can_write ($timeout))) {
+           die "monitor write error - timeout";
+       }
+
+       my $fullcmd = "$cmdstr\r";
+
+       my $b;
+       if (!($b = $sock->syswrite ($fullcmd)) || ($b != length ($fullcmd))) {
+           die "monitor write error - $!";
+       }
+
+       return if ($cmdstr eq 'q') || ($cmdstr eq 'quit');
+
+       $timeout = 20; 
+
+       if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) {
+           $timeout = 60*60; # 1 hour
+       } elsif ($cmdstr =~ m/^(eject|change)/) {
+           $timeout = 60; # note: cdrom mount command is slow
+       }
+       if ($res = __read_avail ($sock, $timeout)) {
+  
+           my @lines = split ("\r?\n", $res);
+
+           shift @lines if $lines[0] !~ m/^unknown command/; # skip echo
+           
+           $res = join ("\n", @lines);
+           $res .= "\n";
+       }
+    };
+
+    my $err = $@;
+
+    if ($err) {
+       syslog ("err", "VM $vmid monitor command failed - $err");
+       die $err;
+    }
+
+    return $res;
+}
+
+sub vm_commandline {
+    my ($storecfg, $vmid) = @_;
+
+    my $conf = load_config ($vmid);
+
+    my $defaults = load_defaults();
+
+    my $cmd = config_to_command ($storecfg, $vmid, $conf, $defaults);
+
+    return join (' ', @$cmd);
+}
+
+sub vm_reset {
+    my ($vmid, $skiplock) = @_;
+
+    lock_config ($vmid, sub {
+
+       my $conf = load_config ($vmid);
+
+       check_lock ($conf) if !$skiplock;
+
+       syslog ("info", "VM $vmid sending 'reset'");
+
+       vm_monitor_command ($vmid, "system_reset", 1);
+    });
+}
+
+sub vm_shutdown {
+    my ($vmid, $skiplock) = @_;
+
+    lock_config ($vmid, sub {
+
+       my $conf = load_config ($vmid);
+
+       check_lock ($conf) if !$skiplock;
+
+       syslog ("info", "VM $vmid sending 'shutdown'");
+
+       vm_monitor_command ($vmid, "system_powerdown", 1);
+    });
+}
+
+sub vm_stop {
+    my ($vmid, $skiplock) = @_;
+
+    lock_config ($vmid, sub {
+
+       my $pid = check_running ($vmid);
+
+       if (!$pid) {
+           syslog ('info', "VM $vmid already stopped");
+           return;
+       }
+
+       my $conf = load_config ($vmid);
+
+       check_lock ($conf) if !$skiplock;
+    
+       syslog ("info", "VM $vmid stopping");
+
+       eval { vm_monitor_command ($vmid, "quit", 1); };
+
+       my $err = $@;
+
+       if (!$err) {
+           # wait some time
+           my $timeout = 50; # fixme: how long?
+
+           my $count = 0;
+           while (($count < $timeout) && check_running ($vmid)) {
+               $count++;
+               sleep 1;
+           }
+
+           if ($count >= $timeout) {
+               syslog ('info', "VM $vmid still running - terminating now with SIGTERM");
+               kill 15, $pid;
+           }
+       } else {
+           syslog ('info', "VM $vmid quit failed - terminating now with SIGTERM");
+           kill 15, $pid;
+       }
+
+       # wait again
+       my $timeout = 10;
+
+       my $count = 0;
+       while (($count < $timeout) && check_running ($vmid)) {
+           $count++;
+           sleep 1;
+       }
+
+       if ($count >= $timeout) {
+           syslog ('info', "VM $vmid still running - terminating now with SIGKILL\n");
+           kill 9, $pid;
+       }
+
+       fairsched_rmnod ($vmid); # try to destroy group
+    });
+}
+
+sub vm_suspend {
+    my ($vmid, $skiplock) = @_;
+
+    lock_config ($vmid, sub {
+
+       my $conf = load_config ($vmid);
+
+       check_lock ($conf) if !$skiplock;
+
+       syslog ("info", "VM $vmid suspend");
+
+       vm_monitor_command ($vmid, "stop", 1);
+    });
+}
+
+sub vm_resume {
+    my ($vmid, $skiplock) = @_;
+
+    lock_config ($vmid, sub {
+
+       my $conf = load_config ($vmid);
+
+       check_lock ($conf) if !$skiplock;
+
+       syslog ("info", "VM $vmid resume");
+
+       vm_monitor_command ($vmid, "cont", 1);
+    });
+}
+
+sub vm_cad {
+    my ($vmid, $skiplock) = @_;
+
+    lock_config ($vmid, sub {
+
+       my $conf = load_config ($vmid);
+
+       check_lock ($conf) if !$skiplock;
+
+       syslog ("info", "VM $vmid sending cntl-alt-delete");
+    
+       vm_monitor_command ($vmid, "sendkey ctrl-alt-delete", 1);
+    });
+}
+
+sub vm_destroy {
+    my ($storecfg, $vmid, $skiplock) = @_;
+
+    lock_config ($vmid, sub {
+
+       my $conf = load_config ($vmid);
+
+       check_lock ($conf) if !$skiplock;
+
+       syslog ("info", "VM $vmid destroy called (removing all data)");
+
+       eval {
+           if (!check_running($vmid)) {
+               fairsched_rmnod($vmid); # try to destroy group
+               destroy_vm($storecfg, $vmid);
+           } else {
+               die "VM is running\n";
+           }
+       };
+
+       my $err = $@;
+
+       if ($err) {
+           syslog ("err", "VM $vmid destroy failed - $err");
+           die $err;
+       }
+    });
+}
+
+sub vm_stopall {
+    my ($timeout) = @_;
+
+    $timeout = 3*60 if !$timeout;
+
+    my $vzlist = vzlist();
+    my $count = 0;
+    foreach my $vmid (keys %$vzlist) {
+       next if !$vzlist->{$vmid}->{pid};
+       $count++;
+    }
+
+    if ($count) {
+
+       my $msg = "Stopping Qemu Server - sending shutdown requests to all VMs\n";
+       syslog ('info', $msg);
+       print STDERR $msg;
+
+       foreach my $vmid (keys %$vzlist) {
+           next if !$vzlist->{$vmid}->{pid};
+           eval { vm_shutdown ($vmid, 1); };
+           print STDERR $@ if $@;
+       }
+
+       my $wt = 5;
+       my $maxtries = int (($timeout + $wt -1)/$wt);
+       my $try = 0;
+       while (($try < $maxtries) && $count) {
+           $try++;
+           sleep $wt;
+
+           $vzlist = vzlist();
+           $count = 0;
+           foreach my $vmid (keys %$vzlist) {
+               next if !$vzlist->{$vmid}->{pid};
+               $count++;
+           }
+           last if !$count;
+       }
+
+       return if !$count;
+
+       foreach my $vmid (keys %$vzlist) {
+           next if !$vzlist->{$vmid}->{pid};
+       
+           $msg = "VM $vmid still running - sending stop now\n";
+           syslog ('info', $msg);
+           print $msg;
+
+           eval { vm_monitor_command ($vmid, "quit", 1); };
+           print STDERR $@ if $@;
+
+       }
+
+       $timeout = 30;
+       $maxtries = int (($timeout + $wt -1)/$wt);
+       $try = 0;
+       while (($try < $maxtries) && $count) {
+           $try++;
+           sleep $wt;
+
+           $vzlist = vzlist();
+           $count = 0;
+           foreach my $vmid (keys %$vzlist) {
+               next if !$vzlist->{$vmid}->{pid};
+               $count++;
+           }
+           last if !$count;
+       }
+
+       return if !$count;
+
+       foreach my $vmid (keys %$vzlist) {
+           next if !$vzlist->{$vmid}->{pid};
+       
+           $msg = "VM $vmid still running - terminating now with SIGTERM\n";
+           syslog ('info', $msg);
+           print $msg;
+           kill 15, $vzlist->{$vmid}->{pid};
+       }
+
+       # this is called by system shotdown scripts, so remaining
+       # processes gets killed anyways (no need to send kill -9 here)
+
+       $msg = "Qemu Server stopped\n";
+       syslog ('info', $msg);
+       print STDERR $msg;
+    }
+}
+
+# pci helpers
+
+sub file_write {
+    my ($filename, $buf) = @_;
+
+    my $fh = IO::File->new ($filename, "w");
+    return undef if !$fh;
+
+    my $res = print $fh $buf;
+
+    $fh->close();
+
+    return $res;
+}
+
+sub pci_device_info {
+    my ($name) = @_;
+
+    my $res;
+
+    return undef if $name !~ m/^([a-f0-9]{4}):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])$/;
+    my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4);
+
+    my $irq = file_read_firstline("$pcisysfs/devices/$name/irq");
+    return undef if !defined($irq) || $irq !~ m/^\d+$/;
+
+    my $vendor = file_read_firstline("$pcisysfs/devices/$name/vendor");
+    return undef if !defined($vendor) || $vendor !~ s/^0x//;
+
+    my $product = file_read_firstline("$pcisysfs/devices/$name/device");
+    return undef if !defined($product) || $product !~ s/^0x//;
+
+    $res = {
+       name => $name,
+       vendor => $vendor,
+       product => $product,
+       domain => $domain,
+       bus => $bus,
+       slot => $slot,
+       func => $func,
+       irq => $irq,
+       has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0,
+    };
+
+    return $res;
+}
+
+sub pci_dev_reset {
+    my ($dev) = @_;
+
+    my $name = $dev->{name};
+
+    my $fn = "$pcisysfs/devices/$name/reset";
+
+    return file_write ($fn, "1");
+}
+
+sub pci_dev_bind_to_stub {
+    my ($dev) = @_;
+
+    my $name = $dev->{name};
+
+    my $testdir = "$pcisysfs/drivers/pci-stub/$name";
+    return 1 if -d $testdir;
+
+    my $data = "$dev->{vendor} $dev->{product}";
+    return undef if !file_write ("$pcisysfs/drivers/pci-stub/new_id", $data);
+
+    my $fn = "$pcisysfs/devices/$name/driver/unbind";
+    if (!file_write ($fn, $name)) {
+       return undef if -f $fn;
+    }
+
+    $fn = "$pcisysfs/drivers/pci-stub/bind";
+    if (! -d $testdir) {
+       return undef if !file_write ($fn, $name);
+    }
+
+    return -d $testdir;
+}
+
+1;
diff --git a/PVE/VZDump/Makefile b/PVE/VZDump/Makefile
new file mode 100644 (file)
index 0000000..8599a9d
--- /dev/null
@@ -0,0 +1,4 @@
+
+.PHONY: install
+install:
+       install -D -m 0644 QemuServer.pm ${DESTDIR}${PERLDIR}/PVE/VZDump/QemuServer.pm
diff --git a/PVE/VZDump/QemuServer.pm b/PVE/VZDump/QemuServer.pm
new file mode 100644 (file)
index 0000000..11e2ec0
--- /dev/null
@@ -0,0 +1,460 @@
+package PVE::VZDump::QemuServer;
+
+#    Copyright (C) 2007-2009 Proxmox Server Solutions GmbH
+#
+#    Copyright: vzdump is under GNU GPL, the GNU General Public License.
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; version 2 dated June, 1991.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the
+#    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+#    MA 02110-1301, USA.
+#
+#    Author: Dietmar Maurer <dietmar@proxmox.com>
+
+use strict;
+use warnings;
+use File::Path;
+use File::Basename;
+use PVE::VZDump;
+use PVE::Cluster;
+use PVE::Storage;
+use PVE::QemuServer;
+use Sys::Hostname;
+use IO::File;
+
+use base qw (PVE::VZDump::Plugin);
+
+sub new {
+    my ($class, $vzdump) = @_;
+    
+    PVE::VZDump::check_bin ('qm');
+
+    my $self = bless { vzdump => $vzdump };
+
+    $self->{vmlist} = PVE::QemuServer::vzlist();
+    $self->{storecfg} = PVE::Storage::config();
+
+    return $self;
+};
+
+
+sub type {
+    return 'qemu';
+}
+
+sub vmlist {
+    my ($self) = @_;
+
+    return [ keys %{$self->{vmlist}} ];
+}
+
+sub prepare {
+    my ($self, $task, $vmid, $mode) = @_;
+
+    $task->{disks} = [];
+
+    my $conf = $self->{vmlist}->{$vmid} = PVE::QemuServer::load_config ($vmid);
+
+    $task->{hostname} = $conf->{name};
+
+    my $lvmmap = PVE::VZDump::get_lvm_mapping();
+
+    my $hostname = hostname(); 
+
+    my $ind = {};
+    my $mountinfo = {};
+    my $mountind = 0;
+
+    my $snapshot_count = 0;
+
+    PVE::QemuServer::foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+
+       return if PVE::QemuServer::drive_is_cdrom ($drive);
+
+       if (defined($drive->{backup}) && $drive->{backup} eq "no") {
+           $self->loginfo("exclude disk '$ds' (backup=no)");
+           return;
+       }          
+       my $volid = $drive->{file};
+
+       my $path;
+
+       my ($storeid, $volname) = PVE::Storage::parse_volume_id ($volid, 1);
+       if ($storeid) {
+           PVE::Storage::activate_storage ($self->{storecfg}, $storeid);
+           $path = PVE::Storage::path ($self->{storecfg}, $volid);
+       } else {
+           $path = $volid;
+       }
+
+       return if !$path;
+
+       die "no such volume '$volid'\n" if ! -e $path;
+
+       my $diskinfo = { path => $path , volid => $volid, storeid => $storeid, 
+                        snappath => $path, virtdev => $ds };
+
+       if (-b $path) {
+
+           $diskinfo->{type} = 'block';
+
+           $diskinfo->{filename} = "vm-disk-$ds.raw";
+
+           if ($mode eq 'snapshot') {
+               my ($lvmvg, $lvmlv) = @{$lvmmap->{$path}} if defined ($lvmmap->{$path});
+               die ("mode failure - unable to detect lvm volume group\n") if !$lvmvg;
+
+               $ind->{$lvmvg} = 0 if !defined $ind->{$lvmvg};
+               $diskinfo->{snapname} = "vzsnap-$hostname-$ind->{$lvmvg}";
+               $diskinfo->{snapdev} = "/dev/$lvmvg/$diskinfo->{snapname}";
+               $diskinfo->{lvmvg} = $lvmvg;
+               $diskinfo->{lvmlv} = $lvmlv;
+               $diskinfo->{snappath} = $diskinfo->{snapdev};
+               $ind->{$lvmvg}++;
+
+               $snapshot_count++;
+           }
+
+       } else {
+
+           $diskinfo->{type} = 'file';
+
+           my (undef, $dir, $ext) = fileparse ($path, qr/\.[^.]*/);
+
+           $diskinfo->{filename} = "vm-disk-$ds$ext";
+
+           if ($mode eq 'snapshot') {
+           
+               my ($srcdev, $lvmpath, $lvmvg, $lvmlv, $fstype) =
+                   PVE::VZDump::get_lvm_device ($dir, $lvmmap);
+
+               my $targetdev = PVE::VZDump::get_lvm_device ($task->{dumpdir}, $lvmmap);
+
+               die ("mode failure - unable to detect lvm volume group\n") if !$lvmvg;
+               die ("mode failure - wrong lvm mount point '$lvmpath'\n") if $dir !~ m|/?$lvmpath/?|;
+               die ("mode failure - unable to dump into snapshot (use option --dumpdir)\n") 
+                   if $targetdev eq $srcdev;
+               
+               $ind->{$lvmvg} = 0 if !defined $ind->{$lvmvg};
+                   
+               my $info = $mountinfo->{$lvmpath};
+               if (!$info) {
+                   my $snapname = "vzsnap-$hostname-$ind->{$lvmvg}";
+                   my $snapdev = "/dev/$lvmvg/$snapname";
+                   $mountinfo->{$lvmpath} = $info = {
+                       snapdev => $snapdev,
+                       snapname => $snapname,
+                       mountpoint => "/mnt/vzsnap$mountind",
+                   };
+                   $ind->{$lvmvg}++;
+                   $mountind++;
+
+                   $snapshot_count++;
+               } 
+
+               $diskinfo->{snapdev} = $info->{snapdev};
+               $diskinfo->{snapname} = $info->{snapname};
+               $diskinfo->{mountpoint} = $info->{mountpoint};
+               
+               $diskinfo->{lvmvg} = $lvmvg;
+               $diskinfo->{lvmlv} = $lvmlv;
+               
+               $diskinfo->{fstype}  = $fstype;
+               $diskinfo->{lvmpath} = $lvmpath;
+
+               $diskinfo->{snappath} = $path;
+               $diskinfo->{snappath} =~ s|/?$lvmpath/?|$diskinfo->{mountpoint}/|;
+           }
+       }
+
+       push @{$task->{disks}}, $diskinfo;
+
+    });
+
+    $task->{snapshot_count} = $snapshot_count;
+}
+
+sub vm_status {
+    my ($self, $vmid) = @_;
+
+    my $status_text = $self->cmd ("qm status $vmid");
+    chomp $status_text;
+
+    my $running = $status_text =~ m/running/ ? 1 : 0;
+   
+    return wantarray ? ($running, $status_text) : $running; 
+}
+
+sub lock_vm {
+    my ($self, $vmid) = @_;
+
+    $self->cmd ("qm set $vmid --lock backup");
+}
+
+sub unlock_vm {
+    my ($self, $vmid) = @_;
+
+    $self->cmd ("qm --skiplock set $vmid --lock ''");
+}
+
+sub stop_vm {
+    my ($self, $task, $vmid) = @_;
+
+    my $opts = $self->{vzdump}->{opts};
+
+    my $wait = $opts->{stopwait} * 60;
+    # send shutdown and wait
+    $self->cmd ("qm --skiplock shutdown $vmid && qm wait $vmid $wait");
+}
+
+sub start_vm {
+    my ($self, $task, $vmid) = @_;
+
+    $self->cmd ("qm --skiplock start $vmid");
+}
+
+sub suspend_vm {
+    my ($self, $task, $vmid) = @_;
+
+    $self->cmd ("qm --skiplock suspend $vmid");
+}
+
+sub resume_vm {
+    my ($self, $task, $vmid) = @_;
+
+    $self->cmd ("qm --skiplock resume $vmid");
+}
+
+sub snapshot_alloc {
+    my ($self, $volid, $name, $size, $srcdev) = @_;
+
+    my $cmd = "lvcreate --size ${size}M --snapshot --name '$name' '$srcdev'";
+
+    my ($storeid, $volname) = PVE::Storage::parse_volume_id ($volid, 1);
+    if ($storeid) {
+
+       my $scfg = PVE::Storage::storage_config ($self->{storecfg}, $storeid);
+
+       # lock shared storage
+       return PVE::Storage::cluster_lock_storage ($storeid, $scfg->{shared}, undef, sub {
+
+           if ($scfg->{type} eq 'lvm') {
+               my $vg = $scfg->{vgname};
+
+               $self->cmd ($cmd);
+
+           } else {
+               die "can't allocate snapshot on storage type '$scfg->{type}'\n";
+           }
+       });
+    } else {
+       $self->cmd ($cmd);
+    }
+}
+
+sub snapshot_free {
+    my ($self, $volid, $name, $snapdev, $noerr) = @_;
+
+    my $cmd = "lvremove -f '$snapdev'";
+
+    eval {
+       my ($storeid, $volname) = PVE::Storage::parse_volume_id ($volid, 1);
+       if ($storeid) {
+
+           my $scfg = PVE::Storage::storage_config ($self->{storecfg}, $storeid);
+
+           # lock shared storage
+           return PVE::Storage::cluster_lock_storage ($storeid, $scfg->{shared}, undef, sub {
+
+               if ($scfg->{type} eq 'lvm') {
+                   my $vg = $scfg->{vgname};
+
+                   $self->cmd ($cmd);
+
+               } else {
+                   die "can't allocate snapshot on storage type '$scfg->{type}'\n";
+               }
+           });
+       } else {
+           $self->cmd ($cmd);
+       }
+    };
+    die $@ if !$noerr;
+    $self->logerr ($@) if $@;
+}
+
+sub snapshot {
+    my ($self, $task, $vmid) = @_;
+
+    my $opts = $self->{vzdump}->{opts};
+
+    my $mounts = {};
+
+    foreach my $di (@{$task->{disks}}) {
+       if ($di->{type} eq 'block') {
+
+           if (-b $di->{snapdev}) {
+               $self->loginfo ("trying to remove stale snapshot '$di->{snapdev}'");
+               $self->snapshot_free ($di->{volid}, $di->{snapname}, $di->{snapdev}, 1); 
+           }
+
+           $di->{cleanup_lvm} = 1;
+           $self->snapshot_alloc ($di->{volid}, $di->{snapname}, $opts->{size},
+                                  "/dev/$di->{lvmvg}/$di->{lvmlv}"); 
+
+       } elsif ($di->{type} eq 'file') {
+
+           next if defined ($mounts->{$di->{mountpoint}}); # already mounted
+
+           # note: files are never on shared storage, so we use $di->{path} instead
+           # of $di->{volid} (avoid PVE:Storage calls because path start with /)
+
+           if (-b $di->{snapdev}) {
+               $self->loginfo ("trying to remove stale snapshot '$di->{snapdev}'");        
+           
+               $self->cmd_noerr ("umount $di->{mountpoint}");
+
+               $self->snapshot_free ($di->{path}, $di->{snapname}, $di->{snapdev}, 1); 
+           }
+
+           mkpath $di->{mountpoint}; # create mount point for lvm snapshot
+
+           $di->{cleanup_lvm} = 1;
+
+           $self->snapshot_alloc ($di->{path}, $di->{snapname}, $opts->{size},
+                                  "/dev/$di->{lvmvg}/$di->{lvmlv}"); 
+           
+           my $mopts = $di->{fstype} eq 'xfs' ? "-o nouuid" : '';
+
+           $di->{snapshot_mount} = 1;
+
+           $self->cmd ("mount -t $di->{fstype} $mopts $di->{snapdev} $di->{mountpoint}");
+
+           $mounts->{$di->{mountpoint}} = 1;
+
+       } else {
+           die "implement me";
+       }
+    }
+}
+
+sub get_size {
+    my $path = shift;
+
+    if (-f $path) {
+       return -s $path;
+    } elsif (-b $path) {
+       my $fh = IO::File->new ($path, "r");
+       die "unable to open '$path' to detect device size\n" if !$fh;
+       my $size = sysseek $fh, 0, 2;
+       $fh->close();
+       die "unable to detect device size for '$path'\n" if !$size;
+       return $size;
+    }
+}
+
+sub assemble {
+    my ($self, $task, $vmid) = @_;
+
+    my $conffile = PVE::QemuServer::config_file ($vmid);
+
+    my $outfile = "$task->{tmpdir}/qemu-server.conf";
+
+    my $outfd;
+    my $conffd;
+
+    eval {
+
+       $outfd = IO::File->new (">$outfile") ||
+           die "unable to open '$outfile'";
+       $conffd = IO::File->new ($conffile, 'r') ||
+           die "unable open '$conffile'";
+
+       while (defined (my $line = <$conffd>)) {
+           next if $line =~ m/^\#vzdump\#/; # just to be sure
+           print $outfd $line;
+       }
+
+       foreach my $di (@{$task->{disks}}) {
+           if ($di->{type} eq 'block' || $di->{type} eq 'file') {
+               my $size = get_size ($di->{snappath});
+               my $storeid = $di->{storeid} || '';
+               print $outfd "#vzdump#map:$di->{virtdev}:$di->{filename}:$size:$storeid:\n";
+           } else {
+               die "internal error";
+           }
+       }
+    };
+    my $err = $@;
+
+    close ($outfd) if $outfd;
+    close ($conffd) if $conffd;
+    
+    die $err if $err;
+}
+
+sub archive {
+    my ($self, $task, $vmid, $filename) = @_;
+
+    my $conffile = "$task->{tmpdir}/qemu-server.conf";
+
+    my $opts = $self->{vzdump}->{opts};
+
+    my $starttime = time ();
+
+    my $fh;
+
+    my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream
+
+    my @filea = ($conffile, 'qemu-server.conf'); # always first file in tar
+    foreach my $di (@{$task->{disks}}) {
+       if ($di->{type} eq 'block' || $di->{type} eq 'file') {
+           push @filea, $di->{snappath}, $di->{filename};
+       } else {
+           die "implement me";
+       }
+    }
+
+    my $out = ">$filename";
+    $out = "|cstream -t $bwl $out" if $opts->{bwlimit};
+    $out = "|gzip $out" if $opts->{compress};
+
+    my $files = join (' ', map { "'$_'" } @filea);
+    
+    $self->cmd("/usr/lib/qemu-server/vmtar $files $out");
+}
+
+sub cleanup {
+    my ($self, $task, $vmid) = @_;
+
+   foreach my $di (@{$task->{disks}}) {
+       
+       if ($di->{snapshot_mount}) {
+          $self->cmd_noerr ("umount $di->{mountpoint}");
+       }
+
+       if ($di->{cleanup_lvm}) {
+          if (-b $di->{snapdev}) {
+              if ($di->{type} eq 'block') {
+                  $self->snapshot_free ($di->{volid}, $di->{snapname}, $di->{snapdev}, 1);
+              } elsif ($di->{type} eq 'file') {
+                  $self->snapshot_free ($di->{path}, $di->{snapname}, $di->{snapdev}, 1);
+              }
+          }
+       }
+   }
+
+}
+
+1;
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..54bb332
--- /dev/null
+++ b/TODO
@@ -0,0 +1,10 @@
+TODOs
+=====
+
+* Add GuestAgent support (after qemu-0.16.0):
+  http://wiki.qemu.org/Features/QAPI/GuestAgent
+
+* Use QMP protocol (after qemu-0.16.0)
+
+* usb2 support
\ No newline at end of file
diff --git a/changelog.Debian b/changelog.Debian
new file mode 100644 (file)
index 0000000..916e686
--- /dev/null
@@ -0,0 +1,218 @@
+qemu-server (2.0-1) unstable; urgency=low
+
+  * see Changelog for details
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Aug 2010 13:48:12 +0200
+
+qemu-server (1.1-18) unstable; urgency=low
+
+  * small bug fix im qmigrate
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 20 Aug 2010 08:05:21 +0200
+
+qemu-server (1.1-17) unstable; urgency=low
+
+  * carefully catch write errors
+
+ -- Proxmox Support Team <support@proxmox.com>  Mon, 19 Jul 2010 09:00:48 +0200
+
+qemu-server (1.1-16) unstable; urgency=low
+
+  * add rerror/werror options (patch from l.mierzwa)
+
+ -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Jun 2010 08:49:00 +0200
+
+qemu-server (1.1-15) unstable; urgency=low
+
+  * fix vmtar bug (endless growing archive)
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 25 Jun 2010 12:22:17 +0200
+
+qemu-server (1.1-14) unstable; urgency=low
+
+  *  correct order of config option (prevent virtio reordering)
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 28 Apr 2010 09:05:15 +0200
+
+qemu-server (1.1-13) unstable; urgency=low
+
+  * allow vlan1-to vlan4094 (Since 802.1q allows VLAN identifiers up to 4094).
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 21 Apr 2010 10:19:37 +0200
+
+qemu-server (1.1-12) unstable; urgency=low
+
+  * minor fixes for new qemu-kvm 0.12.3
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 16 Apr 2010 12:01:17 +0200
+
+qemu-server (1.1-11) unstable; urgency=low
+
+  * experimental support for pci pass-through (option 'hostpci')
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 08 Jan 2010 13:03:44 +0100
+
+qemu-server (1.1-10) unstable; urgency=low
+
+  * add compatibility code for older kvm versions (0.9)
+  
+  * only use fairsched when the kernel has openvz support
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 04 Dec 2009 15:17:18 +0100
+
+qemu-server (1.1-9) unstable; urgency=low
+
+  * always display boot menu (Press F12...)
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 28 Oct 2009 10:28:23 +0100
+
+qemu-server (1.1-8) unstable; urgency=low
+
+  * fix 'stopall' timeout
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 23 Oct 2009 12:55:23 +0200
+
+qemu-server (1.1-7) unstable; urgency=low
+
+  * do not set fairsched --id when using virtio
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 22 Oct 2009 11:57:57 +0200
+
+qemu-server (1.1-6) unstable; urgency=low
+
+  * disable fairsched when option 'cpuunits' is set to 0 (zero)
+
+  * disable fairsched when the VM uses virtio devices.
+  
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 15 Oct 2009 15:06:48 +0200
+
+qemu-server (1.1-5) unstable; urgency=low
+
+  * suppress syslog when setting migrate downtime/speed
+
+ -- Proxmox Support Team <support@proxmox.com>  Tue, 06 Oct 2009 10:10:55 +0200
+
+qemu-server (1.1-4) unstable; urgency=low
+
+  * depend on stable pve-qemu-kvm
+
+  * new migrate_speed and migrate_downtime settings
+  
+ -- Proxmox Support Team <support@proxmox.com>  Mon, 28 Sep 2009 11:18:08 +0200
+
+qemu-server (1.1-3) unstable; urgency=low
+
+  * support up to 1000 vlans
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 18 Sep 2009 09:54:35 +0200
+
+qemu-server (1.1-2) unstable; urgency=low
+
+  * introduce new 'sockets' and 'cores' settings
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 18 Sep 2009 09:54:18 +0200
+
+qemu-server (1.1-1) unstable; urgency=low
+
+  * use new pve-storage framework
+  
+  * delete unused disk on destroy
+
+  * fix cache= option (none|writethrough|writeback)
+
+  * use rtc-td-hack for windows when acpi is enabled
+  
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 25 Jun 2009 08:49:53 +0200
+
+qemu-server (1.0-14) unstable; urgency=low
+
+  * add new tablet option (to disable --usbdevice tablet, which generate
+    many interrupts, which is bad when you run many VMs) (Patch was
+    provided by Tomasz Chmielewski)
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 27 May 2009 12:50:45 +0200
+
+qemu-server (1.0-13) unstable; urgency=low
+
+  * Conflict with netcat-openbsd
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 13 May 2009 10:20:54 +0200
+
+qemu-server (1.0-12) unstable; urgency=low
+
+  * fixes for debian lenny
+
+ -- Proxmox Support Team <support@proxmox.com>  Tue, 21 Apr 2009 14:28:42 +0200
+
+qemu-server (1.0-11) unstable; urgency=low
+
+  *  allow white spaces inside args - use normal shell quoting
+  
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Feb 2009 11:31:36 +0100
+
+qemu-server (1.0-10) unstable; urgency=low
+
+  * add 'args' option
+
+  * bug fix for 'lost description'
+  
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 11 Feb 2009 08:18:29 +0100
+
+qemu-server (1.0-9) unstable; urgency=low
+
+  * add 'parallel' option
+  
+  * add 'startdate' option
+  
+  * fix manual page
+
+ -- Proxmox Support Team <support@proxmox.com>  Mon,  2 Feb 2009 08:53:26 +0100
+
+qemu-server (1.0-8) unstable; urgency=low
+
+  * add 'serial' option
+
+ -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Jan 2009 08:52:24 +0100
+
+qemu-server (1.0-7) unstable; urgency=low
+
+  * use new systax for kvm vga option (needed for kvm newer than kvm77)
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed,  7 Jan 2009 14:46:09 +0100
+
+qemu-server (1.0-6) unstable; urgency=low
+
+  * use predefined names for tap devices
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 19 Dec 2008 13:00:44 +0100
+
+qemu-server (1.0-5) unstable; urgency=low
+
+  * added host usb device support
+
+ -- Proxmox Support Team <support@proxmox.com>  Mon, 17 Nov 2008 11:26:04 +0100
+
+qemu-server (1.0-4) unstable; urgency=low
+
+  * fix status problem
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 13 Nov 2008 13:13:43 +0100
+
+qemu-server (1.0-3) unstable; urgency=low
+
+  * small bug fixes
+
+ -- Proxmox Support Team <support@proxmox.com>  Tue, 11 Nov 2008 08:29:23 +0100
+
+qemu-server (1.0-1) unstable; urgency=low
+
+  * update for kvm-75, support vm migration
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Oct 2008 11:04:03 +0200
+
+qemu-server (0.9) unstable; urgency=low
+
+  * initial release
+
+ -- Proxmox Support Team <support@proxmox.com>  Mon,  4 Feb 2008 09:10:13 +0100
+
diff --git a/control.in b/control.in
new file mode 100644 (file)
index 0000000..0d08d35
--- /dev/null
@@ -0,0 +1,10 @@
+Package: qemu-server
+Version: @@VERSION@@-@@PKGRELEASE@@
+Section: admin
+Priority: optional
+Architecture: @@ARCH@@
+Depends: libc6 (>= 2.7-18), perl (>= 5.10.0-19), libterm-readline-gnu-perl, libdigest-sha1-perl, pve-qemu-kvm (>= 0.11.1) | pve-qemu-kvm-2.6.18, netcat-traditional, libpve-storage-perl
+Conflicts: netcat-openbsd
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Description: Qemu Server Tools
+ This package contains the Qemu Server tools used by Proxmox VE
diff --git a/copyright b/copyright
new file mode 100644 (file)
index 0000000..22d2e93
--- /dev/null
+++ b/copyright
@@ -0,0 +1,21 @@
+Copyright (C) 2009 Proxmox Server Solutions GmbH
+
+This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
+
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 dated June, 1991.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+MA 02110-1301, USA.
+
+The complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL'.
diff --git a/gen-vmconf-pod.pl b/gen-vmconf-pod.pl
new file mode 100755 (executable)
index 0000000..a01b787
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/perl -w
+
+package main;
+
+use strict;
+use PVE::Tools;
+use PVE::Cluster;
+use PVE::PodParser;
+use PVE::QemuServer;
+
+my $prop = PVE::QemuServer::json_config_properties();
+my $format = PVE::PodParser::dump_properties($prop);
+
+my $parser = PVE::PodParser->new();
+$parser->{include}->{format} = $format;
+$parser->parse_from_file($0);
+
+exit 0;
+
+__END__
+
+=head1 NAME
+
+vm.conf - Proxmox VE virtual machine (qemu/kvm) configuration files.
+
+=head1 SYNOPSYS
+
+The F</etc/pve/qemu-server/C<VMID>.conf> files stores VM
+configuration, where C<VMID> is the numeric ID of the given VM. Note
+that C<VMID <= 100> are reserved for internal purposes.
+
+=head1 FILE FORMAT
+
+Configuration file use a simple colon separated key/value format. Each
+line has the following format:
+
+ OPTION: value
+
+Blank lines in the file are ignored, and lines starting with a C<#>
+character are treated as comments and are also ignored.
+
+One can use the F<qm> command to generate and modify those files.
+
+=head1 OPTIONS
+
+=include format
+
+=include pve_copyright
diff --git a/pcitest.pl b/pcitest.pl
new file mode 100755 (executable)
index 0000000..a354c68
--- /dev/null
@@ -0,0 +1,291 @@
+#!/usr/bin/perl -w
+
+# this is some experimental code to test pci pass through
+
+use strict;
+use IO::Dir;
+use IO::File;
+use Time::HiRes qw(usleep);
+use Data::Dumper;
+
+# linux/Documentation/filesystems/sysfs-pci.txt
+# linux/DocumentationABI/testing/sysfs-bus-pci
+
+use constant {
+    PCI_STATUS => 0x06,
+    PCI_CONF_HEADER_LEN => 0x40,
+    PCI_STATUS_CAP_LIST => 0x10,
+    PCI_CAPABILITY_LIST => 0x34, 
+    PCI_CAP_ID_PM => 0x01,
+    PCI_PM_CTRL => 0x04,
+    PCI_PM_CTRL_STATE_MASK => 0x03,
+    PCI_PM_CTRL_STATE_D0 => 0x00,
+    PCI_PM_CTRL_STATE_D3hot => 0x03,
+    PCI_PM_CTRL_NO_SOFT_RESET => 0x08,
+};
+
+my $pcisysfs = "/sys/bus/pci";
+
+sub file_read_firstline {
+    my ($filename) = @_;
+
+    my $fh = IO::File->new ($filename, "r");
+    return undef if !$fh;
+    my $res = <$fh>;
+    chomp $res;
+    $fh->close;
+    return $res;
+}
+
+sub file_read {
+    my ($filename) = @_;
+
+    my $fh = IO::File->new ($filename, "r");
+    return undef if !$fh;
+
+    local $/ = undef; # enable slurp mode
+    my $content = <$fh>;
+    $fh->close();
+
+    return $content;
+}
+
+sub file_write {
+    my ($filename, $buf) = @_;
+
+    my $fh = IO::File->new ($filename, "w");
+    return undef if !$fh;
+
+    my $res = print $fh $buf;
+
+    $fh->close();
+
+    return $res;
+}
+
+sub read_pci_config {
+    my $name = shift;
+
+    return file_read ("$pcisysfs/devices/$name/config");
+}
+
+sub pci_config_write {
+    my ($name, $pos, $buf) = @_;
+
+    my $filename = "$pcisysfs/devices/$name/config";
+
+    my $fh = IO::File->new ($filename, "w");
+    return undef if !$fh;
+
+    if (sysseek($fh, $pos, 0) != $pos) {
+       print "PCI WRITE seek failed\n";
+       return undef;
+    }
+
+    my $res = syswrite ($fh, $buf);
+    print "PCI WRITE $res\n";
+
+    $fh->close();
+
+    return $res;
+}
+
+sub pci_config_read {
+    my ($conf, $pos, $fmt) = @_;
+
+    my $len;
+    if ($fmt eq 'C') {
+       $len = 1;
+    } elsif ($fmt eq 'S') {
+       $len = 2;
+    } elsif ($fmt eq 'L') {
+       $len = 4;
+    } else {
+       return undef;
+    }
+    return undef if (($pos < 0) || (($pos + $len) > length($conf)));
+
+    return unpack($fmt, substr($conf, $pos, $len));
+}
+
+
+sub pci_device_list {
+
+    my $res = {};
+
+    my $dh = IO::Dir->new ("$pcisysfs/devices") || return $res;
+
+    my $used_irqs;
+
+    if ($dh) {
+       while (defined(my $name = $dh->read)) {
+           if ($name =~ m/^([a-f0-9]{4}):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])$/i) {
+               my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4);
+
+               my $irq = file_read_firstline("$pcisysfs/devices/$name/irq");
+               next if $irq !~ m/^\d+$/;
+
+               my $irq_is_shared = defined($used_irqs->{$irq}) || 0;
+               $used_irqs->{$irq} = 1;
+
+               my $vendor = file_read_firstline("$pcisysfs/devices/$name/vendor");
+               next if $vendor !~ s/^0x//;
+               my $product = file_read_firstline("$pcisysfs/devices/$name/device");
+               next if $product !~ s/^0x//;
+
+               my $conf = read_pci_config ($name);
+               next if !$conf;
+
+               $res->{$name} = {
+                   vendor => $vendor,
+                   product => $product,
+                   domain => $domain,
+                   bus => $bus,
+                   slot => $slot,
+                   func => $func,
+                   irq => $irq,
+                   irq_is_shared => $irq_is_shared,
+                   has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0,
+               };
+
+
+               my $status = pci_config_read ($conf, PCI_STATUS, 'S');
+               next if !defined ($status) || (!($status & PCI_STATUS_CAP_LIST));
+
+               my $pos = pci_config_read ($conf, PCI_CAPABILITY_LIST, 'C');
+               while ($pos && $pos > PCI_CONF_HEADER_LEN && $pos != 0xff) {
+                   my $capid = pci_config_read ($conf, $pos, 'C');
+                   last if !defined ($capid);
+                   $res->{$name}->{cap}->{$capid} = $pos;
+                   $pos = pci_config_read ($conf, $pos + 1, 'C');
+               }
+
+               #print Dumper($res->{$name});
+               my $capid = PCI_CAP_ID_PM;
+               if (my $pm_cap_off = $res->{$name}->{cap}->{$capid}) {
+                   # require the NO_SOFT_RESET bit is clear
+                   my $ctl = pci_config_read ($conf, $pm_cap_off + PCI_PM_CTRL, 'L');
+                   if (defined ($ctl) && !($ctl & PCI_PM_CTRL_NO_SOFT_RESET)) {
+                       $res->{$name}->{has_pm_reset} = 1;
+                   } 
+               }
+           }
+       }
+    }
+
+    return $res;
+}
+
+sub pci_pm_reset {
+    my ($list, $name) = @_;
+
+    print "trying to reset $name\n";
+
+    my $dev = $list->{$name} || die "no such pci device '$name";
+    
+    my $capid = PCI_CAP_ID_PM;
+    my $pm_cap_off = $list->{$name}->{cap}->{$capid};
+
+    return undef if !defined ($pm_cap_off);
+    return undef if !$dev->{has_pm_reset};
+
+    my $conf = read_pci_config ($name) || die "cant read pci config";
+
+    my $ctl = pci_config_read ($conf, $pm_cap_off + PCI_PM_CTRL, 'L');
+    return undef if !defined ($ctl);
+
+    $ctl = $ctl & ~PCI_PM_CTRL_STATE_MASK;
+
+    pci_config_write($name, $pm_cap_off + PCI_PM_CTRL, 
+                    pack ('L', $ctl|PCI_PM_CTRL_STATE_D3hot));
+    usleep(10000); # 10ms
+
+    pci_config_write($name, $pm_cap_off + PCI_PM_CTRL, 
+                    pack ('L', $ctl|PCI_PM_CTRL_STATE_D0));
+
+    usleep(10000); # 10ms
+
+    return pci_config_write($name, 0, $conf);
+}
+
+sub pci_dev_reset {
+    my ($list, $name) = @_;
+
+    print "trying to reset $name\n";
+
+    my $dev = $list->{$name} || die "no such pci device '$name";
+
+    my $fn = "$pcisysfs/devices/$name/reset";
+
+    return file_write ($fn, "1");
+}
+
+
+sub pci_dev_bind_to_stub {
+    my ($list, $name) = @_;
+
+    my $dev = $list->{$name} || die "no such pci device '$name";
+
+    #return undef if $dev->{irq_is_shared};
+
+    my $testdir = "$pcisysfs/drivers/pci-stub/$name";
+    return 1 if -d $testdir;
+
+    my $data = "$dev->{vendor} $dev->{product}";
+    return undef if !file_write ("$pcisysfs/drivers/pci-stub/new_id", $data);
+
+    my $fn = "$pcisysfs/devices/$name/driver/unbind";
+    if (!file_write ($fn, $name)) {
+       return undef if -f $fn;
+    }
+
+    $fn = "$pcisysfs/drivers/pci-stub/bind";
+    if (! -d $testdir) {
+       return undef if !file_write ($fn, $name);
+    }
+
+    return -d $testdir;
+}
+
+sub pci_dev_unbind_from_stub {
+    my ($list, $name) = @_;
+
+    my $dev = $list->{$name} || die "no such pci device '$name";
+
+    #return undef if $dev->{irq_is_shared};
+
+    my $testdir = "$pcisysfs/drivers/pci-stub/$name";
+    return 1 if ! -d $testdir;
+
+    my $data = "$dev->{vendor} $dev->{product}";
+    file_write ("$pcisysfs/drivers/pci-stub/remove_id", $data);
+
+    return undef if !file_write ("$pcisysfs/drivers/pci-stub/unbind", $name);
+
+    return ! -d $testdir;
+}
+
+my $devlist = pci_device_list();
+print Dumper($devlist);
+
+my $name = $ARGV[0] || exit 0;
+
+if (!pci_dev_bind_to_stub($devlist, $name)) {
+    print "failed\n";
+    exit (-1);
+}
+if (!pci_dev_unbind_from_stub($devlist, $name)) {
+    print "failed\n";
+    exit (-1);
+}
+
+#pci_pm_reset ($devlist, $name);
+
+if (!pci_dev_reset ($devlist, $name)) {
+    print "reset failed\n";
+    exit (-1);
+}
+
+
+exit 0;
diff --git a/postinst b/postinst
new file mode 100644 (file)
index 0000000..14f7165
--- /dev/null
+++ b/postinst
@@ -0,0 +1,48 @@
+#!/bin/sh
+# postinst script for qemu-server
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <postinst> `abort-remove'
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    configure)
+
+       update-rc.d qemu-server defaults 25 >/dev/null 2>&1
+
+       # update old config files
+
+       /var/lib/qemu-server/qmupdate
+
+    ;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/postrm b/postrm
new file mode 100755 (executable)
index 0000000..3590d53
--- /dev/null
+++ b/postrm
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+# Abort if any command returns an error value
+set -e
+
+if [ "$1" = purge ]; then
+    update-rc.d qemu-server remove >/dev/null 2>&1
+fi
diff --git a/pve-bridge b/pve-bridge
new file mode 100755 (executable)
index 0000000..2d826cf
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/perl -w
+
+use strict;
+use PVE::QemuServer;
+use PVE::Tools qw(run_command);
+
+my $iface = shift;
+
+die "no interface specified\n" if !$iface;
+
+die "got strange interface name '$iface'\n" 
+    if $iface !~ m/^tap(\d+)i(\d+)$/;
+
+my $vmid = $1;
+my $netid = "net$2";
+
+my $conf = PVE::QemuServer::load_config ($vmid);
+
+die "unable to get network config '$netid'\n"
+    if !$conf->{$netid};
+
+my $net = PVE::QemuServer::parse_net($conf->{$netid});
+die "unable to parse network config '$netid'\n" if !$net;
+
+my $bridge = $net->{bridge};
+die "unable to get bridge setting\n" if !$bridge;
+
+system ("/sbin/ifconfig $iface 0.0.0.0 promisc up") == 0 ||
+    die "interface activation failed\n";
+
+if ($net->{rate}) {
+
+    my $rate = int($net->{rate}*1024*1024);
+    my $burst = 1024*1024;
+
+    system("/sbin/tc qdisc del dev $iface ingres >/dev/null 2>&1");
+    system("/sbin/tc qdisc del dev $iface root >/dev/null 2>&1");
+
+    run_command("/sbin/tc qdisc add dev $iface handle ffff: ingress");
+
+    # this does not work wit virtio - don't know why
+    #run_command("/sbin/tc filter add dev $iface parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ${rate}bps burst ${burst}b drop flowid :1");
+    # so we use avrate instead
+    run_command("/sbin/tc filter add dev $iface parent ffff: " .
+               "protocol ip prio 50 estimator 1sec 8sec " .
+               "u32 match ip src 0.0.0.0/0 police avrate ${rate}bps drop flowid :1");
+
+    # tbf does not work for unknown reason
+    #$TC qdisc add dev $DEV root tbf rate $RATE latency 100ms burst $BURST
+    # so we use htb instead
+    run_command("/sbin/tc qdisc add dev $iface root handle 1: htb default 1");
+    run_command("/sbin/tc class add dev $iface parent 1: classid 1:1 " .
+               "htb rate ${rate}bps burst ${burst}b");
+
+    # enable this to debug tc
+    if (0) {
+       print "DEBUG tc settings\n";
+       system("/sbin/tc qdisc ls dev $iface");
+       system("/sbin/tc class ls dev $iface");
+       system("/sbin/tc filter ls dev $iface parent ffff:");
+    }
+
+}
+
+system ("/usr/sbin/brctl addif $bridge $iface") == 0 ||
+    die "can't add interface to bridge\n";
+
+exit 0;
diff --git a/pve-usb.cfg b/pve-usb.cfg
new file mode 100644 (file)
index 0000000..c40dd67
--- /dev/null
@@ -0,0 +1,25 @@
+[device "ehci"]
+  driver = "ich9-usb-ehci1"
+  addr = "1d.7"
+  multifunction = "on"
+
+[device "uhci-1"]
+  driver = "ich9-usb-uhci1"
+  addr = "1d.0"
+  multifunction = "on"
+  masterbus = "ehci.0"
+  firstport = "0"
+
+[device "uhci-2"]
+  driver = "ich9-usb-uhci2"
+  addr = "1d.1"
+  multifunction = "on"
+  masterbus = "ehci.0"
+  firstport = "2"
+
+[device "uhci-3"]
+  driver = "ich9-usb-uhci3"
+  addr = "1d.2"
+  multifunction = "on"
+  masterbus = "ehci.0"
+  firstport = "4"
diff --git a/qemu.init.d b/qemu.init.d
new file mode 100644 (file)
index 0000000..156596a
--- /dev/null
@@ -0,0 +1,45 @@
+#! /bin/sh
+
+### BEGIN INIT INFO
+# Provides:          qemu-server
+# Required-Start:    $network $local_fs $remote_fs
+# Required-Stop:     $network $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: start all qemu/kvm virtual machines
+### END INIT INFO
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+PROG=/usr/sbin/qm
+DESC="Qemu Server"
+
+test -x $PROG || exit 0
+
+set -e
+
+case "$1" in
+  start)
+       (egrep '^flags.*svm' /proc/cpuinfo >/dev/null && modprobe -q kvm-amd) || 
+       (egrep '^flags.*vmx' /proc/cpuinfo >/dev/null && modprobe -q kvm-intel) || 
+       echo "unable to load kvm module"
+
+       # recent distributions use tmpfs for /var/run
+       # and /var/lock to avoid to clean it up on every boot.
+       # they also assume that init scripts will create
+       # required subdirectories for proper operations
+       mkdir -p /var/run/qemu-server
+       mkdir -p /var/lock/qemu-server
+
+       $PROG startall
+       ;;
+  stop)
+       $PROG stopall
+       ;;
+  force-reload)
+       ;;
+  restart)
+        # nothing to do, because we are no real daemon
+       ;;
+esac
+
+exit 0
diff --git a/qm b/qm
new file mode 100755 (executable)
index 0000000..06339b6
--- /dev/null
+++ b/qm
@@ -0,0 +1,479 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Long;
+use Fcntl ':flock';
+use File::Path;
+use IO::Socket::UNIX;
+use IO::Select;
+
+use PVE::Tools qw(extract_param);
+use PVE::Cluster;
+use PVE::SafeSyslog;
+use PVE::INotify;
+use PVE::RPCEnvironment;
+use PVE::QemuServer;
+use PVE::API2::Qemu;
+use PVE::JSONSchema qw(get_standard_option);
+use Term::ReadLine;
+
+use PVE::CLIHandler;
+
+use base qw(PVE::CLIHandler);
+
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+
+initlog('qm');
+
+die "please run as root\n" if $> != 0;
+
+PVE::INotify::inotify_init();
+
+my $rpcenv = PVE::RPCEnvironment->init('cli');
+
+$rpcenv->init_request();
+$rpcenv->set_language($ENV{LANG});
+$rpcenv->set_user('root@pam'); 
+
+my $nodename = PVE::INotify::nodename();
+
+PVE::JSONSchema::register_standard_option('skiplock', {
+    description => "Ignore locks - only root is allowed to use this option.",
+    type => 'boolean', 
+    optional => 1,
+});
+
+sub run_vnc_proxy {
+    my ($vmid) = @_;
+
+    my $path = PVE::QemuServer::vnc_socket($vmid);
+
+    my $s = IO::Socket::UNIX->new(Peer => $path, Timeout => 120);
+
+    die "unable to connect to socket '$path' - $!" if !$s;
+
+    my $select = new IO::Select;
+
+    $select->add(\*STDIN);
+    $select->add($s);
+
+    my $timeout = 60*15; # 15 minutes
+
+    my @handles;
+    while ($select->count && 
+          scalar(@handles = $select->can_read ($timeout))) {
+       foreach my $h (@handles) {
+           my $buf;
+           my $n = $h->sysread($buf, 4096);
+
+           if ($h == \*STDIN) {
+               if ($n) {
+                   syswrite($s, $buf);
+               } else {
+                   exit(0);
+               }
+           } elsif ($h == $s) {
+               if ($n) {
+                   syswrite(\*STDOUT, $buf);
+               } else {
+                   exit(0);
+               }
+           }
+       }
+    }
+    exit(0);
+}
+
+__PACKAGE__->register_method ({
+    name => 'showcmd', 
+    path => 'showcmd', 
+    method => 'GET',
+    description => "Show command line which is used to start the VM (debug info).",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $storecfg = PVE::Storage::config();
+       print PVE::QemuServer::vm_commandline($storecfg, $param->{vmid}) . "\n";
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'status', 
+    path => 'status', 
+    method => 'GET',
+    description => "Show VM status.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           vmid => get_standard_option('pve-vmid'),
+           verbose => {
+               description => "Verbose output format",
+               type => 'boolean',
+               optional => 1,
+           }
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       # test if VM exists
+       my $conf = PVE::QemuServer::load_config ($param->{vmid});
+
+       my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
+       my $stat = $vmstatus->{$param->{vmid}};
+       if ($param->{verbose}) {
+           foreach my $k (sort (keys %$stat)) {
+               next if $k eq 'cpu' || $k eq 'relcpu'; # always 0
+               my $v = $stat->{$k};
+               next if !defined($v);
+               print "$k: $v\n";
+           }
+       } else {
+           my $status = $stat->{status} || 'unknown';
+           print "status: $status\n";
+       }
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'vncproxy', 
+    path => 'vncproxy', 
+    method => 'PUT',
+    description => "Proxy VM VNC traffic to stdin/stdout",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $vmid = $param->{vmid};
+
+       run_vnc_proxy ($vmid);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'unlock', 
+    path => 'unlock', 
+    method => 'PUT',
+    description => "Unlock the VM.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $vmid = $param->{vmid};
+
+       PVE::QemuServer::lock_config ($vmid, sub {
+           PVE::QemuServer::change_config_nolock  ($vmid, {}, { lock => 1 }, 1);
+       });
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'mtunnel', 
+    path => 'mtunnel', 
+    method => 'POST',
+    description => "Used by vzmigrate - do not use manually.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       print "tunnel online\n";
+       *STDOUT->flush();
+
+       while (my $line = <>) {
+           chomp $line;
+           last if $line =~ m/^quit$/;
+       }
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'startall', 
+    path => 'startall', 
+    method => 'POST',
+    description => "Start all virtual machines (when onboot=1).",
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $vzlist = PVE::QemuServer::vzlist();
+       my $storecfg = PVE::Storage::config();
+
+       foreach my $vmid (keys %$vzlist) {
+           next if $vzlist->{$vmid}->{pid}; # already running
+
+           eval {
+               my $conf = PVE::QemuServer::load_config($vmid);
+               if ($conf->{onboot}) {
+                   print STDERR "Starting Qemu VM $vmid\n";
+                   PVE::QemuServer::vm_start($storecfg, $vmid);
+               }
+           };
+           print STDERR $@ if $@;
+       }
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'stopall', 
+    path => 'stopall', 
+    method => 'POST',
+    description => "Stop all virtual machines.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           timeout => {
+               description => "Timeout in seconds. Default is to wait 3 minutes.",
+               type => 'integer',
+               minimum => 1,
+               optional => 1,
+           }
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $timeout = $param->{timeout};
+
+       PVE::QemuServer::vm_stopall($timeout);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'wait', 
+    path => 'wait', 
+    method => 'GET',
+    description => "Wait until the VM is stopped.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           vmid => get_standard_option('pve-vmid'),
+           timeout => {
+               description => "Timeout in seconds. Default is to wait forever.",
+               type => 'integer',
+               minimum => 1,
+               optional => 1,
+           }
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $vmid = $param->{vmid};
+       my $timeout = $param->{timeout};
+
+       my $pid = PVE::QemuServer::check_running ($vmid);
+       return if !$pid;
+
+       print "waiting until VM $vmid stopps (PID $pid)\n";
+
+       my $count = 0;
+       while ((!$timeout || ($count < $timeout)) && PVE::QemuServer::check_running ($vmid)) {
+           $count++;
+           sleep 1;
+       }
+
+       die "wait failed - got timeout\n" if PVE::QemuServer::check_running ($vmid);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'monitor', 
+    path => 'monitor', 
+    method => 'POST',
+    description => "Enter Qemu Monitor interface.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $vmid = $param->{vmid};
+
+       my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
+
+       print "Entering Qemu Monitor for VM $vmid - type 'help' for help\n";
+
+       my $term = new Term::ReadLine ('qm');
+
+       my $input;
+       while (defined ($input = $term->readline('qm> '))) {
+           chomp $input;
+
+           next if $input =~ m/^\s*$/;
+
+           last if $input =~ m/^\s*q(uit)?\s*$/;
+
+           eval {
+               print PVE::QemuServer::vm_monitor_command ($vmid, $input);
+           };
+           print "ERROR: $@" if $@;
+       }
+
+       return undef;
+
+    }});
+
+my $cmddef = {
+    list => [ "PVE::API2::Qemu", 'vmlist', [],
+            { node => $nodename }, sub {
+                my $vmlist = shift;
+
+                exit 0 if (!scalar(@$vmlist));
+
+                printf "%10s %-20s %-10s %-10s %12s %-10s\n", 
+                qw(VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID);
+
+                foreach my $rec (sort { $a->{vmid} <=> $b->{vmid} } @$vmlist) {
+                    printf "%10s %-20s %-10s %-10s %12.2f %-10s\n", $rec->{vmid}, $rec->{name}, 
+                    $rec->{status}, 
+                    ($rec->{maxmem} || 0)/(1024*1024), 
+                    ($rec->{maxdisk} || 0)/(1024*1024*1024), 
+                    $rec->{pid}||0;
+                }
+
+                
+             } ],
+
+    create => [ "PVE::API2::Qemu", 'create_vm', ['vmid'], { node => $nodename } ],
+
+    destroy => [ "PVE::API2::Qemu", 'destroy_vm', ['vmid'], { node => $nodename } ],
+
+    set => [ "PVE::API2::Qemu", 'update_vm', ['vmid'], { node => $nodename } ],
+
+    unlink => [ "PVE::API2::Qemu", 'unlink', ['vmid', 'idlist'], { node => $nodename } ],
+
+    config => [ "PVE::API2::Qemu", 'vm_config', ['vmid'], 
+               { node => $nodename }, sub {
+                   my $config = shift;
+                   foreach my $k (sort (keys %$config)) {
+                       my $v = $config->{$k};
+                       if ($k eq 'description') {
+                           $v = PVE::Tools::encode_text($v);
+                       }
+                       print "$k: $v\n";
+                   }
+               }],
+       
+    showcmd => [ __PACKAGE__, 'showcmd', ['vmid']],
+
+    status => [ __PACKAGE__, 'status', ['vmid']],
+
+    vncproxy => [ __PACKAGE__, 'vncproxy', ['vmid']],
+
+    wait => [ __PACKAGE__, 'wait', ['vmid']],
+
+    unlock => [ __PACKAGE__, 'unlock', ['vmid']],
+
+    monitor  => [ __PACKAGE__, 'monitor', ['vmid']],
+
+    startall => [ __PACKAGE__, 'startall', []],
+
+    stopall => [ __PACKAGE__, 'stopall', []],
+
+    mtunnel => [ __PACKAGE__, 'mtunnel', []],  
+};
+
+sub register_vm_command {
+    my ($cmd, $descr) = @_;
+
+    # we create a wrapper, because we want one 'description' per command
+    __PACKAGE__->register_method ({
+       name => $cmd, 
+       path => $cmd, 
+       method => 'PUT',
+       description => $descr,
+       parameters => {
+           additionalProperties => 0,
+           properties => {
+               vmid => get_standard_option('pve-vmid'),
+               skiplock => get_standard_option('skiplock'),
+           },
+       },
+       returns => { type => 'null'},
+       code => sub {
+           my ($param) = @_;
+
+           $param->{command} = $cmd;
+           $param->{node} = $nodename;
+
+           return PVE::API2::Qemu->vm_command($param);
+       }});
+
+    $cmddef->{$cmd} = [ __PACKAGE__, $cmd, ['vmid']];
+}
+
+register_vm_command('start', "Start virtual machine.");
+register_vm_command('stop', "Stop virtual machine.");
+register_vm_command('reset', "Reset virtual machine.");
+register_vm_command('shutdown', "Shutdown virtual machine (send ACPI showdown request)");
+register_vm_command('suspend', "Suspend virtual machine.");
+register_vm_command('resume', "Resume virtual machine.");
+register_vm_command('cad', "Send CTRL-ALT-DELETE keys.");
+
+my $cmd = shift;
+
+PVE::CLIHandler::handle_cmd($cmddef, "qm", $cmd, \@ARGV, undef, $0);
+
+exit 0;
+
+__END__
+
+=head1 NAME
+
+qm - qemu/kvm virtual machine manager
+
+=head1 SYNOPSIS
+
+=include synopsis
+
+=head1 DESCRIPTION
+
+qm is a script to manage virtual machines with qemu/kvm. You can
+create and destroy virtual machines, and control execution
+(start/stop/suspend/resume). Besides that, you can use qm to set
+parameters in the associated config file. It is also possible to
+create and delete virtual disks.
+
+=include pve_copyright
diff --git a/qm.old b/qm.old
new file mode 100755 (executable)
index 0000000..82c2e79
--- /dev/null
+++ b/qm.old
@@ -0,0 +1,1073 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use Term::ReadLine;
+use PVE::QemuServer;
+use IO::Socket::INET;
+use File::Path;
+use Sys::Syslog;
+use PVE::Storage;
+
+# VNC proxy example:
+# nc -l -p 5900 -w 20 -c "/usr/sbin/qm vncproxy 200 <ticket>"
+# where ticket(password) is an arbitrary string containing 
+# [A-Za-z0-9\+\/] (base64 charset)
+# simple vncproxy example: nc -l -p 5900 -c "qm vncproxy 200 test"
+
+
+# command used by the vnc option
+my $vnccmd = "echo -e '__TICKET__\\n__TICKET__' |vncpasswd __TMPFILE__;vncviewer localhost:__DISPLAY__ -passwd __TMPFILE__";
+
+openlog ('qm', 'cons,pid', 'daemon');
+
+sub print_usage {
+    my ($msg) = @_;
+
+    if ($msg) {
+       print STDERR "ERROR: $msg\n";
+    }
+    print STDERR "qm <command> <vmid> [OPTIONS]\n";
+    print STDERR "qm [create|set] <vmid>\n";
+    print STDERR "\t--memory  <MBYTES>    memory in MB (64 - 8192)\n";
+    print STDERR "\t--sockets <N>         set number of CPU sockets <N>\n";
+    print STDERR "\t--cores <N>           set cores per socket to <N>\n";
+    print STDERR "\t--ostype NAME         specify OS type\n";
+    print STDERR "\t--onboot [yes|no]     start at boot\n";
+    print STDERR "\t--keyboard XX         set vnc keyboard layout\n";
+    print STDERR "\t--cpuunits <num>      CPU weight for a VM\n";
+    print STDERR "\t--name <text>         set a name for the VM\n";
+    print STDERR "\t--description <text>  set VM description\n";
+    print STDERR "\t--boot [a|c|d|n]      specify boot order\n";
+    print STDERR "\t--bootdisk <disk>     enable booting from <disk>\n";
+    print STDERR "\t--acpi (yes|no)       enable/disable ACPI\n";
+    print STDERR "\t--kvm (yes|no)        enable/disable KVM\n";
+    print STDERR "\t--tdf (yes|no)        enable/disable time drift fix\n";
+    print STDERR "\t--localtime (yes|no)  set the RTC to local time\n";
+    print STDERR "\t--vga (gd5446|vesa)   specify VGA type\n";
+
+    print STDERR "\n";
+    print STDERR "\t--vlan[0-9u]          MODEL=XX:XX:XX:XX:XX:XX[,MODEL=YY:YY:YY:YY:YY:YY]\n";
+
+    print STDERR "\n";
+    print STDERR "\t--ide<N>              [volume=]volume,[,media=cdrom|disk]\n";
+    print STDERR "\t                      [,cyls=c,heads=h,secs=s[,trans=t]]\n";
+    print STDERR "\t                      [,cache=none|writethrough|writeback]\n";
+    print STDERR "\t                      [,snapshot=on|off][,cache=on|off][,format=f]\n";
+    print STDERR "\t                      [,werror=enospc|ignore|report|stop]\n";
+    print STDERR "\t                      [,rerror=ignore|report|stop]\n";
+    print STDERR "\t--ide<N> <GBYTES>     create new disk\n";
+    print STDERR "\t--ide<N> delete       remove drive - destroy image\n";
+    print STDERR "\t--ide<N> undef        remove drive - keep image\n";
+
+    print STDERR "\t--cdrom <file>        is an alias for --ide2 <file>,media=cdrom\n";
+
+    print STDERR "\n";
+    print STDERR "\t--scsi<N>             [volume=]volume,[,media=cdrom|disk]\n";
+    print STDERR "\t                      [,cyls=c,heads=h,secs=s[,trans=t]]\n";
+    print STDERR "\t                      [,snapshot=on|off][,format=f]\n";
+    print STDERR "\t                      [,cache=none|writethrough|writeback]\n";
+    print STDERR "\t                      [,werror=enospc|ignore|report|stop]\n";
+    print STDERR "\t--scsi<N> <GBYTES>    create new disk\n";
+    print STDERR "\t--scsi<N> delete      remove drive - destroy image\n";
+    print STDERR "\t--scsi<N> undef       remove drive - keep image\n";
+
+    print STDERR "\n";
+    print STDERR "\t--virtio<N>           [volume=]volume,[,media=cdrom|disk]\n";
+    print STDERR "\t                      [,cyls=c,heads=h,secs=s[,trans=t]]\n";
+    print STDERR "\t                      [,snapshot=on|off][,format=f]\n";
+    print STDERR "\t                      [,cache=none|writethrough|writeback]\n";
+    print STDERR "\t                      [,werror=enospc|ignore|report|stop]\n";
+    print STDERR "\t                      [,rerror=ignore|report|stop]\n";
+    print STDERR "\t--virtio<N> <GBYTES>  create new disk\n";
+    print STDERR "\t--virtio<N> delete    remove drive - destroy image\n";
+    print STDERR "\t--virtio<N> undef     remove drive - keep image\n";
+
+    print STDERR "\n";
+
+    print STDERR "qm monitor <vmid>       connect to vm control monitor\n";
+    print STDERR "qm start <vmid>         start vm\n";
+    print STDERR "qm shutdown <vmid>      gracefully stop vm (send poweroff)\n";
+    print STDERR "qm wait <vmid> [time]   wait until vm is stopped\n";
+    print STDERR "qm stop <vmid>          kill vm (immediate stop)\n";
+    print STDERR "qm reset <vmid>         reset vm (stop, start)\n";
+    print STDERR "qm suspend <vmid>       suspend vm\n";
+    print STDERR "qm resume <vmid>        resume vm\n";
+    print STDERR "qm cad <vmid>           sendkey ctrl-alt-delete\n";
+    print STDERR "qm destroy <vmid>       destroy vm (delete all used/owned volumes)\n";
+    print STDERR "qm unlock <vmid>        clear migrate/backup lock\n";
+    print STDERR "qm status <vmid>        shows the container status\n";
+
+    print STDERR "\n";
+    print STDERR "qm cdrom <vmid> [<device>] <path>  set cdrom path. <device is ide2 by default>\n";
+    print STDERR "qm cdrom <vmid> [<device>] eject   eject cdrom\n";
+
+    print STDERR "\n";
+    print STDERR "qm unlink <vmid> <volume>  delete unused disk images\n";
+
+    print STDERR "qm vncproxy <vmid> <ticket>  open vnc proxy\n";
+    print STDERR "qm vnc <vmid>           start (X11) vncviewer (experimental)\n";
+    print STDERR "qm showcmd <vmid>       show command line (debug info)\n";
+    print STDERR "qm list                 list all virtual machines\n";
+
+    print STDERR "\n";
+    print STDERR "qm startall             start all virtual machines (when onboot=1)\n";
+    print STDERR "qm stopall [timeout]    stop all virtual machines (default timeout is 3 minutes)
+\n";
+
+}
+
+sub __next_vnc_port {
+
+    for (my $p = 5900; $p < 6000; $p++) {
+
+       my $sock = IO::Socket::INET->new (Listen => 5,
+                                         LocalAddr => 'localhost',
+                                         LocalPort => $p,
+                                         ReuseAddr => 1,
+                                         Proto     => 0);
+
+       if ($sock) {
+           close ($sock);
+           return $p;
+       }
+    }
+
+    die "unable to find free vnc port";
+}
+
+sub run_vnc_proxy {
+    my ($vmid) = @_;
+
+    my $path = PVE::QemuServer::vnc_socket ($vmid);
+
+    my $s = IO::Socket::UNIX->new (Peer => $path, Timeout => 120);
+
+    die "unable to connect to socket '$path' - $!" if !$s;
+
+    my $select = new IO::Select;
+
+    $select->add (\*STDIN);
+    $select->add ($s);
+
+    my $timeout = 60*15; # 15 minutes
+
+    my @handles;
+    while ($select->count && 
+          scalar (@handles = $select->can_read ($timeout))) {
+       foreach my $h (@handles) {
+           my $buf;
+           my $n = $h->sysread ($buf, 4096);
+
+           if ($h == \*STDIN) {
+               if ($n) {
+                   syswrite ($s, $buf);
+               } else {
+                   exit (0);
+               }
+           } elsif ($h == $s) {
+               if ($n) {
+                   syswrite (\*STDOUT, $buf);
+               } else {
+                   exit (0);
+               }
+           }
+       }
+    }
+    exit (0);
+}
+
+
+if (scalar (@ARGV) == 0) {
+    print_usage ();
+    exit (-1);
+}
+
+my $cmd = shift @ARGV;
+
+my $skiplock;
+if ($cmd =~ m/^--?skiplock$/) {
+    $skiplock = 1;
+    $cmd = shift @ARGV;
+}
+
+my $shortcmds = {
+    list => 1,
+    startall => 1,
+    stopall => 1, 
+    mtunnel => 1,
+};
+
+my $vmid;
+
+my @disks = PVE::QemuServer::disknames();
+
+my $qm = PVE::QemuServer->new();
+
+if (!defined ($shortcmds->{$cmd})) {
+    if (scalar (@ARGV) == 0) {
+       print_usage ("no <vmid>");
+       exit (-1);
+    }
+    $vmid = shift @ARGV;
+
+    if ($vmid !~ m/^\d+$/) {
+       print_usage ("unable to parse <vmid>");
+       exit (-1);
+    }
+}
+
+sub add_random_macs {
+    my ($settings) = @_;
+
+    foreach my $opt (keys %$settings) {
+       next if $opt !~ m/^vlan(\d+|u)$/;
+       my $vlan = PVE::QemuServer::parse_vlan ($settings->{$opt});
+       next if !$vlan;
+       $settings->{$opt} = PVE::QemuServer::print_vlan ($vlan);
+    }
+}
+    
+sub create_disks {
+    my ($storecfg, $vmid, $settings) = @_;
+
+    my $vollist = [];
+
+    eval {
+       foreach my $ds (@disks) {
+           next if !$settings->{$ds};
+
+           my $disk = PVE::QemuServer::parse_drive ($ds, $settings->{$ds});
+
+           next if PVE::QemuServer::drive_is_cdrom ($disk);
+
+           my $file = $disk->{file};
+
+           if ($file =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
+               my $storeid = $2 || 'local';
+               my $size = $3;
+               my $defformat = PVE::Storage::storage_default_format ($storecfg, $storeid);
+               my $fmt = $disk->{format} || $defformat;
+               syslog ('info', "VM $vmid creating new disk - size is $size GB");
+
+               my $volid = PVE::Storage::vdisk_alloc ($storecfg, $storeid, $vmid, 
+                                                      $fmt, undef, $size*1024*1024);
+
+               $disk->{file} = $volid;
+               delete ($disk->{format}); # no longer needed
+               push @$vollist, $volid;
+               $settings->{$ds} = PVE::QemuServer::print_drive ($vmid, $disk);
+           } else {
+               my $path;
+               if ($disk->{file} =~ m|^/dev/.+|) {
+                   $path = $disk->{file};
+               } else {
+                   $path = PVE::Storage::path ($storecfg, $disk->{file});
+               }
+               if (!(-f $path || -b $path)) {
+                   die "image '$path' does not exists\n";
+               }
+           }
+       }
+    };
+
+    my $err = $@;
+
+    if ($err) {
+       syslog ('err', "VM $vmid create disk failed - $err");
+       foreach my $volid (@$vollist) {
+           PVE::Storage::vdisk_free ($storecfg, $volid);
+       }
+       die $err;
+    }
+
+    return $vollist;
+}
+
+if ($cmd eq 'set') {
+
+    my $settings;
+    if (!($settings = $qm->parse_options ($vmid, 0))) {
+       exit (-1);
+    }
+
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    PVE::QemuServer::lock_config ($vmid, sub {
+
+       my $conf = PVE::QemuServer::load_config ($vmid);
+
+       PVE::QemuServer::check_lock ($conf) if !$skiplock;
+
+       my $unset = {};
+       foreach my $opt (keys %$settings) {
+           my $value = $settings->{$opt};
+           next if !defined ($value);
+           if ($value eq 'delete') {
+               foreach my $ds (@disks) {
+                   next if $ds ne $opt;
+                   my $disk = $conf->{diskinfo}->{$opt};
+                   next if !$disk;
+                   if (!PVE::QemuServer::drive_is_cdrom ($disk)) {
+                       PVE::QemuServer::unlink_image ($qm->{storecfg}, $vmid, $disk->{file});
+                   }
+                   last;
+               }
+               $unset->{$opt} = 1;
+               delete $settings->{$opt};
+           } elsif ($value eq '' || $value eq 'undef') {
+               $unset->{$opt} = 1;
+               delete $settings->{$opt};
+           }
+       }
+
+       add_random_macs ($settings);
+
+       create_disks ($qm->{storecfg}, $vmid, $settings);
+
+       PVE::QemuServer::change_config_nolock  ($vmid, $settings, $unset, 1);
+
+    });
+
+    my $err = $@;
+
+    die "setting parameters failed - $err" if $err;
+
+    exit (0);
+
+} elsif ($cmd eq 'create') {
+
+    my $vollist = [];
+    
+    my $filename = PVE::QemuServer::config_file ($vmid);
+
+    my $settings;
+
+    # first test (befor locking)
+    die "unable to create vm $vmid: config file already exists\n" 
+       if -f $filename;
+
+    if (!($settings = $qm->parse_options ($vmid, 1))) {
+       exit (-1);
+    }
+
+    add_random_macs ($settings);
+
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    syslog ('info', "VM $vmid creating new virtual machine");
+
+    PVE::QemuServer::lock_config ($vmid, sub {
+
+       # second test (after locking test is accurate)
+       die "unable to create vm $vmid: config file already exists\n" 
+           if -f $filename;
+
+       $vollist = create_disks ($qm->{storecfg}, $vmid, $settings);
+
+       # try to be smart about bootdisk
+       my @disks = PVE::QemuServer::disknames();
+       my $firstdisk;
+       foreach my $ds (reverse @disks) {
+           next if !$settings->{$ds};
+           my $disk = PVE::QemuServer::parse_drive ($ds, $settings->{$ds});
+           next if PVE::QemuServer::drive_is_cdrom ($disk);
+           $firstdisk = $ds;
+       }
+
+       if (!$settings->{bootdisk} && $firstdisk) {
+           $settings->{bootdisk} = $firstdisk; 
+       }
+
+       PVE::QemuServer::create_conf_nolock ($vmid, $settings);
+    });
+
+    my $err = $@;
+
+    if ($err) {
+       syslog ('err', "VM $vmid create failed - $err");
+       foreach my $volid (@$vollist) {
+           eval { PVE::Storage::vdisk_free ($qm->{storecfg}, $volid); };
+           warn $@ if $@;
+       }
+       die "create failed - $err";
+    }
+
+    exit (0);
+
+} elsif ($cmd eq 'unlink') {
+
+    if (scalar (@ARGV) == 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    PVE::QemuServer::lock_config ($vmid, sub {
+
+       my $conf = PVE::QemuServer::load_config ($vmid);
+
+       PVE::QemuServer::check_lock ($conf) if !$skiplock;
+
+       my $di = $conf->{diskinfo} || {};
+
+       foreach my $file (@ARGV) {
+           my $found;
+           foreach my $ds (keys %$di) {
+               if ($di->{$ds}->{file} eq $file) {
+                   $found = 1;
+                   last;
+               }
+           }
+           die "disk image '$file' is used - unable to unlink\n" if $found;
+
+           PVE::QemuServer::unlink_image ($qm->{storecfg}, $vmid, $file);
+       }
+    });
+
+    die $@ if $@;
+
+    exit 0;
+}
+
+if ($cmd eq 'monitor') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    print "Entering Qemu Monitor for VM $vmid - type 'help' for help\n";
+
+    my $term = new Term::ReadLine ('qm');
+
+    my $input;
+    while (defined ($input = $term->readline('qm> '))) {
+       chomp $input;
+
+       next if $input =~ m/^\s*$/;
+
+       if ($input =~ m/^\s*q(uit)?\s*$/) {
+           exit (0);
+       }
+       eval {
+           print PVE::QemuServer::vm_monitor_command ($vmid, $input);
+       };
+       print "ERROR: $@" if $@;
+    }
+
+} elsif ($cmd eq 'showcmd') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }    
+
+    print vm_commandline ($qm->{storecfg}, $vmid) . "\n";
+
+} elsif ($cmd eq 'start') {
+    my $statefile;
+    if (scalar (@ARGV) == 2 && ($ARGV[0] =~ m/^\-\-?i(ncoming)?$/)) {
+       $statefile = $ARGV[1];
+    } elsif (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }    
+
+    PVE::QemuServer::vm_start ($qm->{storecfg}, $vmid, $statefile, $skiplock);
+
+} elsif ($cmd eq 'startall') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }    
+
+    $qm->vm_startall_old ();
+
+} elsif ($cmd eq 'reset') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+    
+    PVE::QemuServer::vm_reset ($vmid, $skiplock);
+
+} elsif ($cmd eq 'shutdown') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    vm_shutdown ($vmid, $skiplock);
+
+} elsif ($cmd eq 'wait') {
+    my $timeout = shift || 0;
+
+    if ($timeout !~ m/^\d+$/) {
+       print_usage ("timeout must be numeric");
+       exit (-1);
+    }
+
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    $qm->vm_wait_old ($vmid, $timeout);
+
+} elsif ($cmd eq 'stopall') {
+    my $timeout = shift || 0;
+
+    if ($timeout !~ m/^\d+$/) {
+       print_usage ("timeout must be numeric");
+       exit (-1);
+    }
+
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    PVE::QemuServer::vm_stopall($timeout);
+
+} elsif ($cmd eq 'stop') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    vm_stop ($vmid, $skiplock);
+
+} elsif ($cmd eq 'destroy') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    PVE::QemuServer::vm_destroy ($qm->{storecfg}, $vmid, $skiplock);
+
+} elsif ($cmd eq 'suspend') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    PVE::QemuServer::vm_suspend ($vmid, $skiplock);
+
+} elsif ($cmd eq 'resume') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    PVE::QemuServer::vm_resume ($vmid, $skiplock);
+
+} elsif ($cmd eq 'cad') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    PVE::QemuServer::vm_cad ($vmid, $skiplock);
+
+} elsif ($cmd eq 'vncticket') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    my $ticket = PVE::QemuServer::vm_vncticket ($vmid);
+    print "TICKET:$ticket\n";
+
+} elsif ($cmd eq 'cdrom') {
+
+    my ($device, $path);
+
+    if (scalar (@ARGV) == 1) {
+       $path = shift @ARGV;
+       $device = 'ide2';
+    } elsif (scalar (@ARGV) == 2) {
+       my $dev;
+       ($dev, $path) = @ARGV;
+       foreach my $ds (@disks) {
+           if (($ds eq $dev) || ("-$ds" eq $dev) || ("--$ds" eq $dev)) {
+               $device = $ds;
+               last;
+           }
+       }
+    } else {
+       print_usage ();
+       exit (-1);
+    }
+
+    if (!$path || !$device) {
+       print_usage ();
+       exit (-1);
+    }
+
+    $path = 'none' if $path eq 'eject';
+
+    if (!($path eq 'none' || $path eq 'cdrom')) {
+
+       my ($vtype, $volid) = PVE::Storage::path_to_volume_id ($qm->{storecfg}, $path);
+
+       if (!($vtype eq 'iso')) {
+           die "path '$path' does not point to a valid cdrom image\n";
+       }
+
+       $path = $volid;
+    }
+
+    $qm->vm_cdrom ($vmid, $device, $path);
+} elsif ($cmd eq 'vncproxy') {
+    my $ticket = shift @ARGV;
+
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    if ($ticket) {
+       die "illegal characters in ticket\n" if $ticket =~ m/[^A-Za-z0-9\+\/]/;
+       $qm->vm_vncticket ($vmid, $ticket);
+    }
+
+    run_vnc_proxy ($vmid, $ticket);
+
+} elsif ($cmd eq 'vnc') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    my $port = __next_vnc_port();
+    my $display =  $port - 5900;
+
+    my $ticket = $qm->vm_vncticket ($vmid);
+    $ticket = substr ($ticket, 0, 8);
+    my $mpf = "/tmp/.qmvncpw$$";
+
+    $vnccmd =~ s/__TICKET__/$ticket/g;
+    $vnccmd =~ s/__TMPFILE__/$mpf/g;
+    $vnccmd =~ s/__DISPLAY__/$display/g;
+    $vnccmd =~ s/__PORT__/$port/g;
+
+    system ("$vnccmd &");
+    system ("nc -l -p $port -c 'qm vncproxy $vmid'");
+    unlink $mpf;
+
+} elsif ($cmd eq 'list') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    my $vzlist =  PVE::QemuServer::vzlist();
+
+    exit 0 if (!scalar(keys %$vzlist));
+
+    printf "%10s %-20s %-10s %-10s %12s %-10s\n", 
+    qw(VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID);
+
+    foreach my $vmid (sort keys %$vzlist) {
+       my $conf = PVE::QemuServer::load_config ($vmid);
+       my $name = $conf->{name} || '-';
+
+       my $status = $vzlist->{$vmid}->{pid} ? 'running' : 'stopped';
+       my $pid = $vzlist->{$vmid}->{pid} || 0;
+       printf "%10s %-20s %-10s %-10s %12.2f %-10s\n", $vmid, $name, $status, 
+       $conf->{memory}||0, $conf->{disksize}||0, $pid;
+    }
+
+} elsif ($cmd eq 'unlock') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+    PVE::QemuServer::lock_config ($vmid, sub {
+       PVE::QemuServer::change_config_nolock  ($vmid, {}, { lock => 1 }, 1);
+    });
+
+    die $@ if $@;
+
+} elsif ($cmd eq 'status') {
+    if (scalar (@ARGV) != 0) {
+       print_usage ();
+       exit (-1);
+    }
+
+    my $status = 'unknown';
+
+    eval { 
+       if (PVE::QemuServer::check_running($vmid)) {
+           $status = 'running';
+       } else {
+           $status = 'stopped';
+       }
+    };
+
+    print "$status\n";
+
+} elsif ($cmd eq 'mtunnel') {
+
+    print "tunnel online\n";
+    *STDOUT->flush();
+
+    while (my $line = <>) {
+       chomp $line;
+       last if $line =~ m/^quit$/;
+    }
+
+    exit (0);
+
+} else {
+    print_usage ();
+    exit (-1);
+}
+
+exit (0);
+
+__END__
+
+=head1 NAME
+
+qm - qemu/kvm manager
+
+=head1 SYNOPSIS
+
+qm [--skiplock] <command> <vmid> [OPTIONS]
+
+type qm to see a list of valid commands
+
+=head1 DESCRIPTION
+
+qm is a script to manage virtual machines with qemu/kvm. You can
+create and destroy virtual machines, and control execution
+(start/stop/suspend/resume). Besides that, you can use qm to set
+parameters in the associated config file. It is also possible to
+create and delete virtual disks.
+
+=head1 CONFIGURATION
+
+Global server configuration is stored in /etc/pve/qemu-server.cfg.
+
+All configuration files consists of lines in the form
+
+       PARAMETER: value
+
+The following defaults can be specified:
+
+=over 1
+
+=item keyboard: XX 
+
+Keybord layout used for vnc server  (for example C<fr> for French)
+
+=item onboot: (yes|no)
+
+Specifies whether a VM will be started during system bootup.
+Default is no, meaning the VM will not be started if ONBOOT 
+parameter is omitted.
+
+=item autostart: (yes|no)
+
+Automatic restart after crash. Default is no. This value is
+currently ignored (because we have no monitor).
+
+=item memory: num
+
+Amount of RAM for the VM in MB. Default value is 512.
+
+=item cpuunits: num
+
+CPU weight for a VM. Argument is positive non-zero number, passed to
+and used in the kernel fair scheduler.  The larger the number is,
+the more CPU time this VM gets. Maximum value is 500000, minimal
+is 8.  Number is relative to weights of all the other running VMs.
+If cpuunits are not specified, default value of 1000 is used.
+
+NOTE: You can disable fair-scheduler configuration by setting this to 0.
+
+=item vga: (std|cirrus)
+
+Select VGA type. Default is a Cirrus Logic GD5446 PCI VGA card (cirrus). 
+If you want to use high resolution modes (>= 1280x1024x16) then you should 
+use option C<std>.
+
+=item tdf: (yes|no)
+
+enable/disable time drift fix [default=yes]
+
+=item tablet: (yes|no)
+
+enable/disable the usb tablet device [default=yes]. This device is usually
+needed to allow absolute mouse positioning. Else the mouse runs out of
+sync with normal vnc clients. If you're running lots of console-only guests 
+on one host, you may consider setting "tablet: no" to save some context switches.
+
+=item migrate_downtime: num
+
+set maximum tolerated downtime (in seconds) for migrations. [default=1]
+
+=item migrate_speed: num
+
+set maximum speed (in MB/s) for migrations. [default=0 (no limit)]
+
+=back
+
+=head1 VM CONFIGURATION
+
+Each VM is identified by an unique ID (integer). Configuration for
+a VM is stored at C</etc/qemu-server/ID.conf>
+
+Currently, the following parameters are supported:
+
+=over 1
+
+=item keyboard: XX 
+
+Default is read from global configuration file.
+
+=item onboot: (yes|no)
+
+Default is read from global configuration file.
+
+=item autostart: (yes|no)
+
+Default is read from global configuration file.
+
+=item memory: num
+
+Default is read from global configuration file.
+
+=item cpuunits: num
+
+Default is read from global configuration file.
+
+=item migrate_downtime: num
+
+Default is read from global configuration file.
+
+=item migrate_speed: num
+
+Default is read from global configuration file.
+
+=item reboot: (yes|no)
+
+Exit instead of rebooting.
+
+=item name: text
+
+Set a name for the VM. Only used on the configuration
+web interface.
+
+=item description: text
+
+Description for the VM. Only used on the configuration
+web interface.
+
+=item boot: [a|c|d|n] 
+
+boot on floppy (a), hard disk (c), CD-ROM (d), or network (n)
+
+default is C<cad> (disk, floppy, cdrom)
+
+=item bootdisk: <disk>
+
+enable booting from <disk>
+
+<disk> = <ide0|ide1|ide2|ide3|scsi0|scsi1|scsi2|...
+
+=item smp: num (please use C<sockets> instead)
+
+set the number of CPUs to C<num> [default=1]
+
+=item sockets: num
+
+set the number of CPU sockets to C<num> [default=1]
+
+=item cores: num
+
+set the number of cores per socket to C<num> [default=1]
+
+=item acpi: (yes|no)
+
+enable/disable ACPI [default=yes]
+
+=item kvm: (yes|no)
+
+enable/disable KVM hardware virtualization [default=yes]
+
+=item tdf: (yes|no)
+
+Default is read from global configuration file.
+
+=item tablet: (yes|no)
+
+Default is read from global configuration file.
+
+=item localtime: (yes|no)
+
+set the real time clock to local time
+
+=item startdate: (now|YYYY-MM-DD|YYYY-MM-DDTHH-MM-SS)
+
+Set the initial date of the real time clock. Valid format 
+for date are: C<now> or C<2006-06-17T16:01:21> or C<2006-06-17>.
+The default value is C<now>. 
+
+=item freeze: (yes|no)
+
+freeze CPU at startup (use C<c> monitor command to start execution)
+
+=item vlan0: MODEL=XX:XX:XX:XX:XX:XX[,MODEL=YY:YY:YY:YY:YY:YY]
+
+Specify network devices connected to vlan0. Currently, vlan0 is
+connected to vmbr0.
+
+MODEL is one of: ne2k_pci e1000 rtl8139 pcnet virtio
+ne2k_isa i82551 i82557b i82559er
+
+XX:XX:XX:XX:XX:XX should be an unique MAC address
+
+=item vlan[1-4094]: MODEL=XX:XX:XX:XX:XX:XX[,MODEL=YY:YY:YY:YY:YY:YY]
+
+Same as vlan0, but vlanX is bridged to vmbrX
+
+=item vlanu: MODEL=XX:XX:XX:XX:XX:XX[,MODEL=YY:YY:YY:YY:YY:YY]
+
+Same as vlan0, but vlanu uses user mode network stack (NAT). 
+Provides DHCP and DNS services. The following addresses are used:
+
+    10.0.2.2   Gateway
+    10.0.2.3   DNS Server
+    10.0.2.4   SMB Server
+
+The DHCP server assign addresses to the hosts starting from 10.0.2.15. 
+
+=item ostype: (other|wxp|w2k|w2k3|w2k8|wvista|l24|l26)
+
+Used to enable special optimization/features for specific
+operating systems:
+
+    other  => unspecified OS
+    wxp    => Microsoft Windows XP
+    w2k    => Microsoft Windows 2000
+    w2k3   => Microsoft Windows 2003
+    w2k8   => Microsoft Windows 2008
+    wvista => Microsoft Windows Vista
+    l24    => Linux 2.4 Kernel
+    l26    => Linux 2.6 Kernel
+
+    other|l24|l26             ... no special behaviour
+    wxp|w2k|w2k3|w2k8|wvista  ... use --localtime switch
+
+=item vga: (std|cirrus)
+
+Default is read from global configuration file.
+
+=item ide0 - ide3: [volume=]volume,][,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough"|writeback] [,format=f]
+
+Use volume as IDE hard disk or CD-ROM.
+
+=item scsi0 - scsi15: [volume=]volume,][,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough"|writeback] [,format=f]
+
+Use volume as SCSI hard disk or CD-ROM.
+
+=item virtio0 - virtio15: [volume=]volume,][,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough"|writeback] [,format=f]
+
+Use file as VIRTIO hard disk.
+
+=item hostpci: [HOSTPCIDEVICE][,HOSTPCIDEVICE]*
+
+Map host pci devices. HOSTPCIDEVICE syntax is:
+
+C<bus:dev.func> (hexadecimal numbers)
+
+You can us the C<lspci> command to list existing pci devices.
+
+Note: This option allows direct access to host hardware. So it is no
+longer possible to migrate such machines - use with special care.
+
+Experimental: user reported problems with this option
+
+=item hostusb: [HOSTUSBDEVICE][,HOSTUSBDEVICE]*
+
+Map host usb devices. HOSTUSBDEVICE syntax is:
+
+C<bus.addr> (decimal numbers) or C<vendor_id:product_id> (hexadeciaml numbers)
+
+You can us the C<lsusb> command to list existing usb devices (or take a
+look at C</proc/bus/usb/devices>).
+
+Note: This option allows direct access to host hardware. So it is no
+longer possible to migrate such machines - use with special care.
+
+=item serial: [SERIALDEVICE][,SERIALDEVICE]*
+
+Experimental: user reported problems with this option
+
+Map host serial devices. SERIALDEVICE syntax is /dev/ttyS* 
+
+Note: This option allows direct access to host hardware. So it is no
+longer possible to migrate such machines - use with special care.
+
+=item parallel: [PARALLELDEVICE][,PARALLELDEVICE]*
+
+Experimental: user reported problems with this option
+
+Map host parallel devices. PARALLELDEVICE syntax is /dev/parport* 
+
+Note: This option allows direct access to host hardware. So it is no
+longer possible to migrate such machines - use with special care.
+
+=item args: ...
+
+Note: this option is for experts only. It allows you to pass arbitrary
+arguments to kvm, for example:
+
+  args: -no-reboot -no-hpet
+
+=item lock: (migrate|backup)
+
+This value is set during online migration (migrate) and vzdump
+(backup). 
+
+=back
+
+=head1 Locks
+
+Online migration and backups (vzdump) set a lock to prevent
+unintentional action on such VMs. Sometimes you need remove such lock
+manually (power failure).
+
+ qm unlock <vmid>
+
+=head1 EXAMPLES
+
+# create a new VM with 4 GB ide disk
+
+qm create 300 -ide0 4 -vlan0 e1000 -cdrom proxmox-mailgateway_2.1.iso
+
+# start the new VM
+
+qm start 300
+
+# send shutdown, then wait until VM is stopped
+
+qm shutdown 300 && qm wait 300
+
+# same as above, but only wait for 40 seconds
+
+qm shutdown 300 && qm wait 300 40
+
+
+
+
+
+
+
diff --git a/qmigrate b/qmigrate
new file mode 100755 (executable)
index 0000000..e6cb429
--- /dev/null
+++ b/qmigrate
@@ -0,0 +1,504 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Long;
+use PVE::SafeSyslog;
+use IO::Select;
+use IPC::Open3;
+use IPC::Open2;
+use PVE::Cluster;
+use PVE::QemuServer;
+use PVE::Storage;
+use POSIX qw(strftime);
+
+# fimxe: adopt for new cluster filestem
+
+die "not implemented - fixme!";
+
+# fixme: kvm > 88 has more migration options and verbose status
+
+initlog('qmigrate');
+PVE::Cluster::cfs_update();
+
+sub print_usage {
+    my $msg = shift;
+
+    print STDERR "ERROR: $msg\n" if $msg;
+    print STDERR "USAGE: qmigrate [--online] [--verbose]\n";
+    print STDERR "                destination_address VMID\n";
+    exit (-1);
+}
+
+
+# fixme: bwlimit ?
+
+my $opt_online;
+my $opt_verbose;
+
+sub logmsg {
+    my ($level, $msg) = @_;
+
+    chomp $msg;
+
+    return if !$msg;
+
+    my $tstr = strftime ("%b %d %H:%M:%S", localtime);
+
+    syslog ($level, $msg);
+
+    foreach my $line (split (/\n/, $msg)) {
+       print STDOUT "$tstr $line\n";
+    }
+    \*STDOUT->flush();
+}
+
+if (!GetOptions ('online' => \$opt_online, 
+                'verbose' => \$opt_verbose)) {
+    print_usage ();
+} 
+
+if (scalar (@ARGV) != 2) {
+    print_usage ();
+}
+
+my $host = shift;
+my $vmid = shift;
+
+# blowfish is a fast block cipher, much faster then 3des
+my @ssh_opts = ('-c', 'blowfish', '-o', 'BatchMode=yes');
+my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
+my @rem_ssh = (@ssh_cmd, "root\@$host");
+my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
+my $qm_cmd = '/usr/sbin/qm';
+
+$ENV{RSYNC_RSH} = join (' ', @ssh_cmd);
+
+logmsg ('err', "illegal VMID") if $vmid !~ m/^\d+$/;
+$vmid = int ($vmid); # remove leading zeros
+
+my $storecfg = PVE::Storage::config(); 
+
+my $conffile = PVE::QemuServer::config_file ($vmid);
+
+my $delayed_interrupt = 0;
+
+$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
+    logmsg ('err', "received interrupt - delayed");
+    $delayed_interrupt = 1;
+};
+
+sub eval_int {
+    my ($func) = @_;
+
+    eval {
+       local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
+           $delayed_interrupt = 0;
+           logmsg ('err', "received interrupt");
+           die "interrupted by signal\n";
+       };
+       local $SIG{PIPE} = sub {
+           $delayed_interrupt = 0;
+           logmsg ('err', "received broken pipe interrupt");
+           die "interrupted by signal\n";
+       };
+
+       my $di = $delayed_interrupt;
+       $delayed_interrupt = 0;
+
+       die "interrupted by signal\n" if $di;
+
+       &$func();
+    };
+}
+
+sub prepare {
+
+    die "VM $vmid does not exist\n" if ! -f $conffile;
+
+    # test ssh connection
+    my $cmd = [ @rem_ssh, '/bin/true' ];
+    eval { PVE::Storage::run_command ($cmd); };
+    die "Can't connect to destination address using public key\n" if $@;
+  
+    # test if VM already exists
+    $cmd = [ @rem_ssh, $qm_cmd, 'status', $vmid ];
+    my $stat = '';
+    eval { 
+       PVE::Storage::run_command ($cmd, outfunc => sub { $stat .= shift; });
+    };
+    die "can't query VM status on host '$host'\n" if $@;
+
+    die "VM $vmid already exists on destination host\n" if $stat !~ m/^unknown$/;
+}
+
+sub sync_disks {
+    my ($conf, $rhash, $running) = @_;
+
+    logmsg ('info', "copying disk images");
+
+    my $res = [];
+
+    eval {
+
+       my $volhash = {};
+
+       # get list from PVE::Storage (for unused volumes)
+       my $dl = PVE::Storage::vdisk_list ($storecfg, undef, $vmid);
+       PVE::Storage::foreach_volid ($dl, sub {
+           my ($volid, $sid, $volname) = @_;
+
+           my $scfg =  PVE::Storage::storage_config ($storecfg, $sid);
+
+           return if $scfg->{shared};
+
+           $volhash->{$volid} = 1;
+       });
+
+       # and add used,owned/non-shared disks (just to be sure we have all)  
+
+       my $sharedvm = 1;
+       PVE::QemuServer::foreach_drive($conf, sub {
+           my ($ds, $drive) = @_;
+
+           return if PVE::QemuServer::drive_is_cdrom ($drive);
+
+           my $volid = $drive->{file};
+
+           return if !$volid;
+           die "cant migrate local file/device '$volid'\n" if $volid =~ m|^/|;
+
+           my ($sid, $volname) = PVE::Storage::parse_volume_id ($volid);
+           
+           my $scfg =  PVE::Storage::storage_config ($storecfg, $sid);
+
+           return if $scfg->{shared};
+
+           $sharedvm = 0;
+
+           my ($path, $owner) = PVE::Storage::path ($storecfg, $volid);
+
+           die "can't migrate volume '$volid' - owned by other VM (owner = VM $owner)\n" 
+               if !$owner || ($owner != $vmid);
+
+           $volhash->{$volid} = 1;
+       });
+
+       if ($running && !$sharedvm) {
+           die "can't do online migration - VM uses local disks\n";
+       }
+
+       # do some checks first
+       foreach my $volid (keys %$volhash) {
+           my ($sid, $volname) = PVE::Storage::parse_volume_id ($volid);
+           my $scfg =  PVE::Storage::storage_config ($storecfg, $sid);
+
+           die "can't migrate '$volid' - storagy type '$scfg->{type}' not supported\n"
+               if $scfg->{type} ne 'dir';
+       }
+
+       foreach my $volid (keys %$volhash) {
+           my ($sid, $volname) = PVE::Storage::parse_volume_id ($volid);
+           push @{$rhash->{volumes}}, $volid;
+           PVE::Storage::storage_migrate ($storecfg, $volid, $host, $sid);
+       }
+
+    };
+    die "Failed to sync data - $@" if $@;
+}
+
+sub fork_tunnel {
+    my ($remhost, $lport, $rport) = @_;
+
+    my $cmd = [@ssh_cmd, '-o', 'BatchMode=yes',
+              '-L', "$lport:localhost:$rport", $remhost,
+              'qm', 'mtunnel' ];
+    
+    my $tunnel = PVE::Storage::fork_command_pipe ($cmd);
+
+    my $reader = $tunnel->{reader};
+
+    my $helo;
+    eval { 
+       PVE::Storage::run_with_timeout (60, sub { $helo = <$reader>; }); 
+       die "no reply\n" if !$helo;
+       die "got strange reply from mtunnel ('$helo')\n" 
+           if $helo !~ m/^tunnel online$/;
+    };
+    my $err = $@;
+
+    if ($err) {
+       PVE::Storage::finish_command_pipe ($tunnel);
+       die "can't open migration tunnel - $err";
+    }
+    return $tunnel;
+}
+
+sub finish_tunnel { 
+    my $tunnel = shift;
+
+    my $writer = $tunnel->{writer};
+
+    eval { 
+       PVE::Storage::run_with_timeout (30, sub {
+           print $writer "quit\n";
+           $writer->flush();
+       }); 
+    };
+    my $err = $@;
+       
+    PVE::Storage::finish_command_pipe ($tunnel);
+    
+    die $err if $err;
+}
+
+sub phase1 {
+    my ($conf, $rhash, $running) = @_;
+
+    logmsg ('info', "starting migration of VM $vmid to host '$host'");
+
+    my $loc_res = 0;
+    $loc_res = 1 if $conf->{hostusb};
+    $loc_res = 1 if $conf->{hostpci};
+    $loc_res = 1 if $conf->{serial};
+    $loc_res = 1 if $conf->{parallel};
+
+    if ($loc_res) {
+       if ($running) {
+           die "can't migrate VM which uses local devices\n";
+       } else {
+           logmsg ('info', "migrating VM which uses local devices");
+       }
+    }
+
+    # set migrate lock in config file
+    $rhash->{clearlock} = 1;
+
+    my $settings = { lock => 'migrate' };
+    PVE::QemuServer::change_config_nolock  ($vmid, $settings, {}, 1);
+
+    # copy config to remote host
+    eval {
+       my $cmd = [ @scp_cmd, $conffile, "root\@$host:$conffile"];
+       PVE::Storage::run_command ($cmd);
+       $rhash->{conffile} = 1;
+    };
+    die "Failed to copy config file - $@" if $@;
+
+    sync_disks ($conf, $rhash, $running);
+};
+
+sub phase2 {
+    my ($conf, $rhash) = shift;
+
+    logmsg ('info', "starting VM on remote host '$host'");
+
+    my $rport;
+
+    ## start on remote host 
+    my $cmd = [@rem_ssh, $qm_cmd, '--skiplock', 'start', $vmid, '--incoming', 'tcp'];
+
+    PVE::Storage::run_command ($cmd, outfunc => sub {
+       my $line = shift;
+
+       if ($line =~ m/^migration listens on port (\d+)$/) {
+           $rport = $1;
+       }
+    });
+
+    die "unable to detect remote migration port\n" if !$rport;
+
+    logmsg ('info', "starting migration tunnel");
+    ## create tunnel to remote port
+    my $lport = PVE::QemuServer::next_migrate_port ();
+    $rhash->{tunnel} = fork_tunnel ($host, $lport, $rport);
+
+    logmsg ('info', "starting online/live migration");
+    # start migration
+
+    my $start = time();
+
+    PVE::QemuServer::vm_monitor_command ($vmid, "migrate -d \"tcp:localhost:$lport\"");
+
+    my $lstat = '';
+    while (1) {
+       sleep (2);
+       my $stat = PVE::QemuServer::vm_monitor_command ($vmid, "info migrate", 1);
+       if ($stat =~ m/^Migration status: (active|completed|failed|cancelled)$/im) {
+           my $ms = $1;
+
+           if ($stat ne $lstat) {
+               if ($ms eq 'active') {
+                   my ($trans, $rem, $total) = (0, 0, 0);
+                   $trans = $1 if $stat =~ m/^transferred ram: (\d+) kbytes$/im;
+                   $rem = $1 if $stat =~ m/^remaining ram: (\d+) kbytes$/im;
+                   $total = $1 if $stat =~ m/^total ram: (\d+) kbytes$/im;
+
+                   logmsg ('info', "migration status: $ms (transferred ${trans}KB, " .
+                           "remaining ${rem}KB), total ${total}KB)");
+               } else {
+                   logmsg ('info', "migration status: $ms");
+               }
+           }
+
+           if ($ms eq 'completed') {
+               my $delay = time() - $start;
+               if ($delay > 0) {
+                   my $mbps = sprintf "%.2f", $conf->{memory}/$delay;
+                   logmsg ('info', "migration speed: $mbps MB/s");
+               }
+           }
+
+           if ($ms eq 'failed' || $ms eq 'cancelled') {
+               die "aborting\n"
+           }
+
+           last if $ms ne 'active';
+       } else {
+           die "unable to parse migration status '$stat' - aborting\n";
+       }
+       $lstat = $stat;
+    };
+}
+
+my $errors;
+
+my $starttime = time();
+
+# lock config during migration 
+PVE::QemuServer::lock_config ($vmid, sub {
+
+    eval_int (\&prepare);
+    die $@ if $@;
+
+    my $conf = PVE::QemuServer::load_config($vmid);
+
+    PVE::QemuServer::check_lock ($conf);
+       
+    my $running = 0;
+    if (PVE::QemuServer::check_running ($vmid)) {
+       die "cant migrate running VM without --online\n" if !$opt_online;
+       $running = 1;
+    }
+
+    my $rhash = {};
+    eval_int (sub { phase1 ($conf, $rhash, $running); });
+    my $err = $@;
+
+    if ($err) {
+       if ($rhash->{clearlock}) {
+           my $unset = { lock => 1 };
+           eval { PVE::QemuServer::change_config_nolock ($vmid, {}, $unset, 1) };
+           logmsg ('err', $@) if $@;
+       }
+       if ($rhash->{conffile}) {
+           my $cmd = [ @rem_ssh, '/bin/rm', '-f', $conffile ];
+           eval { PVE::Storage::run_command ($cmd); };
+           logmsg ('err', $@) if $@;
+       }
+       if ($rhash->{volumes}) {
+           foreach my $volid (@{$rhash->{volumes}}) {
+               logmsg ('err', "found stale volume copy '$volid' on host '$host'");
+           }
+       }
+
+       die $err;
+    }
+
+    # vm is now owned by other host
+    my $volids = $rhash->{volumes};
+
+    if ($running) {
+    
+       $rhash = {};
+       eval_int (sub { phase2 ($conf, $rhash); });
+       my $err = $@;
+
+       # always kill tunnel
+       if ($rhash->{tunnel}) {
+           eval_int (sub { finish_tunnel ($rhash->{tunnel}) });
+           if ($@) {
+               logmsg ('err', "stopping tunnel failed - $@");
+               $errors = 1;
+           }
+       }
+
+       # always stop local VM - no interrupts possible
+       eval { PVE::QemuServer::vm_stop ($vmid, 1); };
+       if ($@) {
+           logmsg ('err', "stopping vm failed - $@");
+           $errors = 1;
+       }
+
+       if ($err) {
+           $errors = 1;
+           logmsg ('err', "online migrate failure - $err");
+       }
+    }
+
+    # finalize -- clear migrate lock
+    eval_int (sub {
+       my $cmd = [@rem_ssh, $qm_cmd, 'unlock', $vmid ]; 
+       PVE::Storage::run_command ($cmd);
+    });
+    if ($@) {
+       logmsg ('err', "failed to clear migrate lock - $@");
+       $errors = 1;
+    }
+    
+    unlink $conffile;
+
+    # destroy local copies
+    foreach my $volid (@$volids) {
+       eval_int (sub { PVE::Storage::vdisk_free ($storecfg, $volid); });
+       my $err = $@;
+
+       if ($err) {
+           logmsg ('err', "removing local copy of '$volid' failed - $err");
+           $errors = 1;
+           
+           last if $err =~ /^interrupted by signal$/;
+       }
+    }
+});
+
+my $err = $@;
+
+my $delay = time () - $starttime;
+my $mins = int ($delay/60);
+my $secs = $delay - $mins*60;
+my $hours =  int ($mins/60);
+$mins = $mins - $hours*60;
+
+my $duration = sprintf "%02d:%02d:%02d", $hours, $mins, $secs;
+
+if ($err) {
+    logmsg ('err', $err) if $err;
+    logmsg ('info', "migration aborted");
+    exit (-1);
+}
+
+if ($errors) {
+    logmsg ('info', "migration finished with problems (duration $duration)");
+    exit (-1);
+}
+
+logmsg ('info', "migration finished successfuly (duration $duration)");
+
+exit (0);
+
+__END__
+
+=head1 NAME
+
+qmigrate - utility for VM migration between hardware nodes (kvm/qemu)
+
+=head1 SYNOPSIS
+
+qmigrate [--online] [--verbose] destination_address VMID
+
+=head1 DESCRIPTION
+
+no info available.
+
+
+
diff --git a/qmrestore b/qmrestore
new file mode 100755 (executable)
index 0000000..de32799
--- /dev/null
+++ b/qmrestore
@@ -0,0 +1,420 @@
+#!/usr/bin/perl -w
+#
+#    Copyright (C) 2007-2009 Proxmox Server Solutions GmbH
+#
+#    Copyright: vzdump is under GNU GPL, the GNU General Public License.
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; version 2 dated June, 1991.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the
+#    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+#    MA 02110-1301, USA.
+#
+#    Author: Dietmar Maurer <dietmar@proxmox.com>
+#
+
+use strict;
+use Getopt::Long;
+use Sys::Syslog;
+use File::Path;
+use PVE::VZDump;
+use PVE::Storage;
+
+$ENV{LANG} = "C"; # avoid locale related issues/warnings
+
+openlog ('vzdump', 'cons,pid', 'daemon');
+
+my @std_opts = ('extract', 'storage=s', 'info', 'prealloc', 'unique');
+
+sub print_usage {
+    my $msg = shift;
+
+    print STDERR "ERROR: $msg\n\n" if $msg;
+
+    print STDERR "usage: $0 [OPTIONS] <ARCHIVE> <VMID>\n\n";
+}
+
+sub shellquote {
+    my $str = shift;
+
+    return "''" if !defined ($str) || ($str eq '');
+    
+    die "unable to quote string containing null (\\000) bytes"
+       if $str =~ m/\x00/;
+
+    # from String::ShellQuote
+    if ($str =~ m|[^\w!%+,\-./:@^]|) {
+
+       # ' -> '\''
+       $str =~ s/'/'\\''/g;
+
+       $str = "'$str'";
+       $str =~ s/^''//;
+       $str =~ s/''$//;
+    }
+
+    return $str;
+}
+
+my $quoted_cmd = shellquote ($0);
+foreach my $arg (@ARGV) {
+    $quoted_cmd .= " " . shellquote ($arg);
+}
+
+my $opts = {};
+if (!GetOptions ($opts, @std_opts)) {
+    print_usage ();
+    exit (-1);
+}
+
+if ($#ARGV != 1) {
+    print_usage ();
+    exit (-1);
+} 
+
+my $archive = shift;
+my $vmid = PVE::VZDump::check_vmids ((shift))->[0];
+
+$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
+    die "interrupted by signal\n";
+};
+
+sub debugmsg { PVE::VZDump::debugmsg (@_); } # just a shortcut
+
+sub run_command { PVE::VZDump::run_command (undef, @_); } # just a shortcut
+
+if ($opts->{extract}) {
+
+    # NOTE: this is run as tar subprocess (--to-command)
+
+    my $filename = $ENV{TAR_FILENAME};
+    die "got strange environment -  no TAR_FILENAME\n" if !$filename;
+
+    my $filesize = $ENV{TAR_SIZE};
+    die "got strange file size '$filesize'\n" if !$filesize;
+
+    my $tmpdir = $ENV{VZDUMP_TMPDIR};
+    die "got strange environment -  no VZDUMP_TMPDIR\n" if !$tmpdir;
+
+    my $filetype = $ENV{TAR_FILETYPE} || 'none';
+    die "got strange filetype '$filetype'\n" if $filetype ne 'f';
+
+    my $conffile = "$tmpdir/qemu-server.conf";
+    my $statfile = "$tmpdir/qmrestore.stat";
+
+    if ($opts->{info}) {
+       print STDERR "reading archive member '$filename'\n";
+    } else {
+       print STDERR "extracting '$filename' from archive\n";
+    }
+
+    if ($filename eq 'qemu-server.conf') {
+       my $outfd = IO::File->new ($conffile, "w") ||
+           die "unable to write file '$conffile'\n";
+
+       while (defined (my $line = <>)) {
+           print $outfd $line;
+           print STDERR "CONFIG: $line" if $opts->{info};
+       }
+
+       $outfd->close();
+
+       exit (0);
+    }
+
+    if ($opts->{info}) {
+       exec 'dd', 'bs=256K', "of=/dev/null";
+       die "couldn't exec dd: $!\n";
+    }
+
+    my $conffd = IO::File->new ($conffile, "r") ||
+       die "unable to read file '$conffile'\n";
+
+    my $map;
+    while (defined (my $line = <$conffd>)) {
+       if ($line =~ m/^\#vzdump\#map:(\S+):(\S+):(\d+):(\S*):$/) {
+           $map->{$2} = { virtdev => $1, size => $3, storeid => $4 };
+       }
+    }
+    close ($conffd);
+
+    my $statfd = IO::File->new ($statfile, "a") ||
+       die "unable to open file '$statfile'\n";
+
+    if ($filename !~ m/^.*\.([^\.]+)$/){
+       die "got strange filename '$filename'\n";
+    }
+    my $format = $1;
+
+    my $path;
+
+    if (!$map) {
+       print STDERR "restoring old style vzdump archive - " .
+           "no device map inside archive\n";
+       die "can't restore old style archive to storage '$opts->{storage}'\n" 
+           if $opts->{storage} && $opts->{storage} ne 'local';
+
+       my $dir = "/var/lib/vz/images/$vmid";
+       mkpath $dir;
+
+       $path = "$dir/$filename";
+
+       print $statfd "vzdump::$path\n";
+       $statfd->close();
+
+    } else {
+
+       my $info = $map->{$filename};
+       die "no vzdump info for '$filename'\n" if !$info;
+
+       if ($filename !~ m/^vm-disk-$info->{virtdev}\.([^\.]+)$/){
+           die "got strange filename '$filename'\n";
+       }
+
+       if ($filesize != $info->{size}) {
+           die "detected size difference for '$filename' " .
+               "($filesize != $info->{size})\n";
+       }       
+
+       my $storeid;
+       if ($opts->{storage}) {
+           $storeid = $opts->{storage};
+       } else {
+           $storeid = $info->{storeid} || 'local';
+       }
+
+       my $cfg = PVE::Storage::load_config();
+       my $scfg = PVE::Storage::storage_config ($cfg, $storeid);
+
+       my $alloc_size = ($filesize + 1024 - 1)/1024;
+       if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+           # hack: we just alloc a small file (32K) - we overwrite it anyways
+           $alloc_size = 32;
+       } else {
+           die "unable to restore '$filename' to storage '$storeid'\n" . 
+               "storage type '$scfg->{type}' does not support format '$format\n"
+               if $format ne 'raw';
+       }
+
+       my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $vmid,
+                                              $format, undef, $alloc_size);
+
+       print STDERR "new volume ID is '$volid'\n";
+
+       print $statfd "vzdump:$info->{virtdev}:$volid\n";
+       $statfd->close();
+
+       $path = PVE::Storage::path ($cfg, $volid);
+    }
+
+    print STDERR "restore data to '$path' ($filesize bytes)\n";
+
+    if ($opts->{prealloc} || $format ne 'raw' || (-b $path)) {
+       exec 'dd', 'bs=256K', "of=$path";
+       die "couldn't exec dd: $!\n";
+    } else {
+       exec '/usr/lib/qemu-server/sparsecp', $path;
+       die "couldn't exec sparsecp: $!\n";
+    }
+}
+
+sub restore_cleanup {
+    my $statfile = shift;
+
+    return if $opts->{info};
+
+    debugmsg ('info', "starting cleanup");
+    if (my $fd = IO::File->new ($statfile, "r")) {
+       while (defined (my $line = <$fd>)) {
+           if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) {
+               my $volid = $2;
+               eval {
+                   if ($volid =~ m|^/|) {
+                       unlink $volid || die 'unlink failed\n';
+                   } else {
+                       my $cfg = PVE::Storage::load_config();    
+                       PVE::Storage::vdisk_free ($cfg, $volid);
+                   }
+                   debugmsg ('info', "temporary volume '$volid' sucessfuly removed");  
+               };
+               debugmsg ('err', "unable to cleanup '$volid' - $@") if $@;
+           } else {
+               debugmsg ('info', "unable to parse line in statfile - $line");
+           }
+       }
+       $fd->close();
+    }
+}
+
+sub restore_qemu {
+    my ($archive, $vmid, $tmpdir) = @_;
+
+    local $ENV{VZDUMP_TMPDIR} = $tmpdir;
+    my $subcmd = shellquote ("--to-command=${quoted_cmd}\ --extract");
+    my $cmd = "tar xf '$archive' $subcmd";
+    run_command ($cmd);
+
+    return if $opts->{info};
+
+    # reed new mapping
+    my $map = {};
+    my $statfile = "$tmpdir/qmrestore.stat";
+    if (my $fd = IO::File->new ($statfile, "r")) {
+       while (defined (my $line = <$fd>)) {
+           if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) {
+               $map->{$1} = $2 if $1;
+           } else {
+               debugmsg ('info', "unable to parse line in statfile - $line");
+           }
+       }
+       $fd->close();
+    }
+
+    my $confsrc = "$tmpdir/qemu-server.conf";
+
+    my $srcfd = new IO::File ($confsrc, "r") ||
+       die "unable to open file '$confsrc'\n";
+
+    my $conffile = PVE::QemuServer::config_file ($vmid);
+    my $tmpfn = "$conffile.$$.tmp";
+
+    my $outfd = new IO::File ($tmpfn, "w") ||
+       die "unable to write config for VM $vmid\n";
+
+    eval {
+       while (defined (my $line = <$srcfd>)) {
+           next if $line =~ m/^\#vzdump\#/;
+           next if $line =~ m/^lock:/;
+
+           if (($line =~ m/^((vlan)\d+):(.*)$/) && ($opts->{unique})) {
+               my ($id,$ethcfg) = ($1,$3);
+               $ethcfg =~ s/^\s+//;
+               my ($model, $mac) = split(/\=/,$ethcfg);
+               my $printvlan = PVE::QemuServer::print_vlan (PVE::QemuServer::parse_vlan ($model));
+               print $outfd "$id: $printvlan\n";
+           } elsif ($line =~ m/^((ide|scsi|virtio)\d+):(.*)$/) {
+               my $virtdev = $1;
+               my $value = $2;
+               if ($line =~ m/backup=no/) {
+                   print $outfd "#$line";
+               } elsif ($virtdev && $map->{$virtdev}) {
+                   my $di = PVE::QemuServer::parse_drive ($virtdev, $value);
+                   $di->{file} = $map->{$virtdev};
+                   $value = PVE::QemuServer::print_drive ($vmid, $di);
+                   print $outfd "$virtdev: $value\n";
+               } else {
+                   print $outfd $line;
+               }
+           } else {
+               print $outfd $line;
+           }
+       }
+    };
+    my $err = $@;
+
+    $outfd->close();
+
+    if ($err) {
+       unlink $tmpfn;
+       die $err;
+    } else {
+       rename $tmpfn, $conffile;
+    }
+}
+
+my $firstfile = PVE::VZDump::read_firstfile ($archive);
+if ($firstfile ne 'qemu-server.conf') {
+    die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n";
+}
+
+my $tmpdir = "/var/tmp/vzdumptmp$$";
+
+PVE::QemuServer::lock_config ($vmid, sub {
+
+    my $conffile = PVE::QemuServer::config_file ($vmid);
+
+    die "unable to restore VM '$vmid' - VM already exists\n"
+       if -f $conffile;
+
+    mkpath $tmpdir;
+
+    eval {
+       debugmsg ('info', "restore QemuServer backup '$archive' " .
+                 "using ID $vmid", undef, 1) if !$opts->{info};
+
+       restore_qemu ($archive, $vmid, $tmpdir);
+
+       if ($opts->{info}) {
+           debugmsg ('info', "reading '$archive' successful");
+       } else {
+           debugmsg ('info', "restore QemuServer backup '$archive' successful", 
+                     undef, 1);
+       }
+    };
+    my $err = $@;
+
+    if ($err) {
+       local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
+           debugmsg ('info', "got interrupt - ignored (cleanup phase)");
+       };
+
+       restore_cleanup ("$tmpdir/qmrestore.stat") if $err;
+    }
+
+    die $err if $err;
+});
+
+my $err = $@;
+
+rmtree $tmpdir;
+
+if ($err) {
+    if ($opts->{info}) {
+       debugmsg ('info', "reading '$archive' failed - $err");
+    } else {
+
+       debugmsg ('err', "restore QemuServer backup '$archive' failed - $err", 
+                 undef, 1);
+    }
+    exit (-1);
+}
+
+exit (0);
+
+__END__
+
+=head1 NAME
+                                          
+qmrestore - restore QemuServer vzdump backups
+
+=head1 SYNOPSIS
+
+qmrestore [OPTIONS] <archive> <VMID>
+
+ --info                    read/verify archive and print relevant 
+                           information (test run)
+
+ --unique                 assign a unique random ethernet address
+
+ --storage <STORAGE_ID>    restore to storage <STORAGE_ID>
+
+ --prealloc                never generate sparse files
+
+=head1 DESCRIPTION
+
+Restore the QemuServer vzdump backup <archive> to virtual machine
+<VMID>. Volumes are allocated on the original storage if there is no
+C<--storage> specified.
+
+=head1 SEE ALSO
+
+    vzdump(1) vzrestore(1)
diff --git a/qmupdate b/qmupdate
new file mode 100755 (executable)
index 0000000..c679632
--- /dev/null
+++ b/qmupdate
@@ -0,0 +1,234 @@
+#!/usr/bin/perl -w
+
+use strict;
+use IO::File;
+use Digest::SHA1;
+
+# 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::SHA1::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;
+
diff --git a/sparsecp.c b/sparsecp.c
new file mode 100644 (file)
index 0000000..eaa3085
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+    Copyright (C) 2007-2009 Proxmox Server Solutions GmbH
+
+    Copyright: vzdump is under GNU GPL, the GNU General Public License.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; version 2 dated June, 1991.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the
+    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+    MA 02110-1301, USA.
+
+    Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <signal.h>
+
+#include "utils.c"
+
+#define BLOCKSIZE 512*8
+
+static char *outname;
+
+static void 
+cleanup (void)
+{
+  if (outname) 
+    unlink (outname);
+}
+
+void term_handler()
+{
+  fprintf (stderr, "received signal - terminate process\n");
+  exit(-1);
+}
+
+size_t
+sparse_cp (int infd, int outfd) 
+{
+  size_t total = 0;
+  size_t count;
+  char buffer[BLOCKSIZE];
+  int last_write_made_hole = 0;
+
+  while ((count = safe_read (infd, buffer, sizeof (buffer))) > 0) {
+    if (block_is_zero (buffer, count)) {
+
+      if (lseek (outfd, count, SEEK_CUR) < 0) {
+       perror ("cannot lseek\n");
+       exit (-1);
+      }
+      last_write_made_hole = 1;
+    } else {
+      full_write (outfd, buffer, count);
+      last_write_made_hole = 0;
+    }
+    total += count;
+  }
+
+  if (last_write_made_hole) {
+    if (ftruncate (outfd, total) < 0) {
+      perror ("cannot ftruncate\n");
+      exit (-1);
+    }
+  }
+
+  return total;
+}
+
+int
+main (int argc, char **argv)
+{
+  struct sigaction sa;
+
+  if (argc != 2) {
+    fprintf (stderr, "wrong number of arguments\n");
+    exit (-1);
+  }
+
+  time_t starttime = time(NULL);
+
+  outname = argv[1];
+
+  int outfd;
+
+  if ((outfd = open(outname, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) {
+    fprintf (stderr, "unable to open file '%s' - %s\n", 
+            outname, strerror (errno));
+    exit (-1);
+  }
+  atexit(cleanup);
+
+  setsig(&sa, SIGINT, term_handler, SA_RESTART);
+  setsig(&sa, SIGQUIT, term_handler, SA_RESTART);
+  setsig(&sa, SIGTERM, term_handler, SA_RESTART);
+  setsig(&sa, SIGPIPE, term_handler, SA_RESTART);
+
+  size_t total = sparse_cp (0, outfd);
+
+  close (outfd);
+
+  time_t delay = time(NULL) - starttime;
+  if (delay <= 0) delay = 1;
+
+  fprintf (stderr, "%zu bytes copied, %d s, %.2f MiB/s\n", total, delay,
+          (total/(1024*1024))/(float)delay);
+
+  outname = NULL;
+
+  exit (0);
+}
diff --git a/utils.c b/utils.c
new file mode 100644 (file)
index 0000000..5187940
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,135 @@
+/*
+    Copyright (C) 2007-2009 Proxmox Server Solutions GmbH
+
+    Copyright: vzdump is under GNU GPL, the GNU General Public License.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; version 2 dated June, 1991.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the
+    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+    MA 02110-1301, USA.
+
+    Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+
+/* Set a signal handler */
+static void 
+setsig (struct sigaction *sa, int sig, void (*fun)(int), int flags)
+{
+  sa->sa_handler = fun;
+  sa->sa_flags = flags;
+  sigemptyset(&sa->sa_mask);
+  sigaction(sig, sa, NULL);
+}
+
+int
+block_is_zero (char const *buffer, size_t size)
+{
+  while (size--)
+    if (*buffer++)
+      return 0;
+
+  return 1;
+}
+
+ssize_t 
+safe_read(int fd, char *buf, size_t count)
+{
+  ssize_t n;
+
+  do {
+    n = read(fd, buf, count);
+  } while (n < 0 && errno == EINTR);
+
+  return n;
+}
+
+int 
+full_read(int fd, char *buf, size_t len)
+{
+  size_t n;
+  size_t total;
+
+  total = 0;
+
+  while (len > 0) {
+    n = safe_read(fd, buf, len);
+
+    if (n == 0)
+           return total;
+
+    if (n < 0)
+           break;
+
+    buf += n;
+    total += n;
+    len -= n;
+  }
+
+  if (len) {
+         fprintf (stderr, "ERROR: incomplete read detected\n");
+         exit (-1);
+  }
+
+  return total;
+}
+
+ssize_t 
+safe_write(int fd, char *buf, size_t count)
+{
+  ssize_t n;
+
+  do {
+    n = write(fd, buf, count);
+  } while (n < 0 && errno == EINTR);
+
+  return n;
+}
+
+int 
+full_write(int fd, char *buf, size_t len)
+{
+  size_t n;
+  size_t total;
+
+  total = 0;
+
+  while (len > 0) {
+    n = safe_write(fd, buf, len);
+
+    if (n < 0)
+      break;
+
+    buf += n;
+    total += n;
+    len -= n;
+  }
+
+  if (len) {
+    fprintf (stderr, "ERROR: incomplete write detected\n");
+    exit (-1);
+  }
+
+  return total;
+}
diff --git a/vmtar.c b/vmtar.c
new file mode 100644 (file)
index 0000000..f94aee6
--- /dev/null
+++ b/vmtar.c
@@ -0,0 +1,565 @@
+/*
+    Copyright (C) 2007-2009 Proxmox Server Solutions GmbH
+
+    Copyright: vzdump is under GNU GPL, the GNU General Public License.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; version 2 dated June, 1991.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the
+    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+    MA 02110-1301, USA.
+
+    Author: Dietmar Maurer <dietmar@proxmox.com>
+
+    NOTE: the tar specific code is copied from the GNU tar package (just 
+    slighly modified to fit our needs).
+*/
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <signal.h>
+
+#include "utils.c"
+
+
+#define BLOCKSIZE 512
+#define BUFFER_BLOCKS 32
+
+static char *outname;
+
+struct writebuffer 
+{
+  int fd;
+  char buffer[BUFFER_BLOCKS*BLOCKSIZE];
+  size_t bpos;
+  size_t total;
+};
+
+/* OLDGNU_MAGIC uses both magic and version fields, which are contiguous. */
+#define OLDGNU_MAGIC "ustar  " /* 7 chars and a null */
+
+struct posix_header
+{                              /* byte offset */
+  char name[100];              /*   0 */
+  char mode[8];                        /* 100 */
+  char uid[8];                 /* 108 */
+  char gid[8];                 /* 116 */
+  char size[12];               /* 124 */
+  char mtime[12];              /* 136 */
+  char chksum[8];              /* 148 */
+  char typeflag;               /* 156 */
+  char linkname[100];          /* 157 */
+  char magic[6];               /* 257 */
+  char version[2];             /* 263 */
+  char uname[32];              /* 265 */
+  char gname[32];              /* 297 */
+  char devmajor[8];            /* 329 */
+  char devminor[8];            /* 337 */
+  char prefix[155];            /* 345 */
+                               /* 500 */
+};
+
+struct sparse
+{                              /* byte offset */
+  char offset[12];             /*   0 */
+  char numbytes[12];           /*  12 */
+                               /*  24 */
+};
+
+struct oldgnu_header
+{                              /* byte offset */
+  char unused_pad1[345];       /*   0 */
+  char atime[12];              /* 345 Incr. archive: atime of the file */
+  char ctime[12];              /* 357 Incr. archive: ctime of the file */
+  char offset[12];             /* 369 Multivolume archive: the offset of
+                                  the start of this volume */
+  char longnames[4];           /* 381 Not used */
+  char unused_pad2;            /* 385 */
+  struct sparse sp[4];
+                               /* 386 */
+  char isextended;             /* 482 Sparse file: Extension sparse header
+                                  follows */
+  char realsize[12];           /* 483 Sparse file: Real size*/
+                               /* 495 */
+};
+
+struct sparse_header
+{                              /* byte offset */
+  struct sparse sp[21];        /*   0 */
+  char isextended;             /* 504 */
+                               /* 505 */
+};
+
+union block
+{
+  char buffer[BLOCKSIZE];
+  struct posix_header header;
+  struct oldgnu_header oldgnu_header;
+  struct sparse_header sparse_header;
+};
+
+
+struct sp_entry
+{
+  off_t offset;
+  size_t bytes;
+};
+
+struct sp_array {
+  size_t real_size;
+  size_t effective_size;
+  size_t avail; 
+  size_t size; 
+  struct sp_entry *map;
+};
+
+static void 
+cleanup (void)
+{
+  if (outname) 
+    unlink (outname);
+}
+
+void term_handler()
+{
+  fprintf (stderr, "received signal - terminate process\n");
+  exit(-1);
+}
+
+struct sp_array*
+sparray_new (void) {
+  struct sp_array *ma = malloc (sizeof (struct sp_array));
+  if (!ma) {
+    fprintf (stderr, "ERROR: memory allocation failure\n"); 
+    exit (-1);
+  }
+  ma->real_size = 0;
+  ma->effective_size = 0;
+  ma->avail = 0; 
+  ma->size = 1024; 
+  ma->map = malloc (ma->size * sizeof (struct sp_entry));
+  if (!ma->map) {
+    fprintf (stderr, "ERROR: memory allocation failure\n"); 
+    exit (-1);
+  }
+  return ma;
+}
+
+void
+sparray_resize (struct sp_array *ma)
+{
+  ma->size += 1024;
+  if (!(ma->map = realloc (ma->map, ma->size * sizeof (struct sp_entry)))) {
+    fprintf (stderr, "ERROR: memory allocation failure\n"); 
+    exit (-1);
+  } 
+}
+
+void
+sparray_add (struct sp_array *ma, off_t offset, size_t bytes)
+{
+       
+  if (ma->avail == ma->size) {
+    sparray_resize(ma);
+  }
+  ma->map[ma->avail].offset = offset;
+  ma->map[ma->avail].bytes = bytes;
+  ma->avail++;
+}
+
+static void
+to_base256 (uintmax_t value, char *where, size_t size)
+{
+  uintmax_t v = value;
+  size_t i = size - 1;
+
+  where[0] = 1 << 7;
+
+  do {
+    where[i--] = v & ((1 << 8) - 1);
+    v >>= 8;
+  } while (i);
+}
+
+static void
+to_octal (uintmax_t value, char *where, size_t size)
+{
+  uintmax_t v = value;
+  size_t i = size - 1;
+
+  where[i] = '\0';
+  do {
+    where[--i] = '0' + (v & ((1 << 3) - 1));
+    v >>= 3;
+  } while (i);
+}
+
+/* The maximum uintmax_t value that can be represented with DIGITS digits,
+   assuming that each digit is BITS_PER_DIGIT wide.  */
+#define MAX_VAL_WITH_DIGITS(digits, bits_per_digit) \
+   ((digits) * (bits_per_digit) < sizeof (uintmax_t) * 8 \
+    ? ((uintmax_t) 1 << ((digits) * (bits_per_digit))) - 1 \
+    : (uintmax_t) -1)
+
+/* The maximum uintmax_t value that can be represented with octal
+   digits and a trailing NUL in BUFFER.  */
+#define MAX_OCTAL_VAL(buffer) MAX_VAL_WITH_DIGITS (sizeof (buffer) - 1, 3)
+
+void
+off12_to_chars (char *p, off_t v)
+{
+  if (v < 0) {
+    fprintf (stderr, "ERROR: internal error - got negative offset\n");
+    exit (-1);
+  }
+
+  uintmax_t value = (uintmax_t) v;
+
+  if (value <= MAX_VAL_WITH_DIGITS (11, 3)) {
+    to_octal (value, p, 12);
+  } else {
+    to_base256 (value, p, 12);
+  }
+}
+
+char *
+buffer_block(struct writebuffer *wbuf)
+{
+  size_t space = sizeof (wbuf->buffer) - wbuf->bpos;
+  char *blk;
+
+  if (space >= BLOCKSIZE) {
+    blk = wbuf->buffer + wbuf->bpos;
+    wbuf->bpos += BLOCKSIZE;
+  } else {
+    full_write (wbuf->fd, wbuf->buffer, wbuf->bpos);
+    wbuf->total += wbuf->bpos;
+    wbuf->bpos = BLOCKSIZE;
+    blk = wbuf->buffer;
+  }
+  return blk;
+}
+
+struct writebuffer*
+buffer_new(int fd)
+{
+  struct writebuffer *wbuf = calloc (1, sizeof (struct writebuffer));
+
+  if (!wbuf) {
+    fprintf (stderr, "ERROR: memory allocation failure\n"); 
+    exit (-1);
+  }
+
+  wbuf->fd = fd;
+
+  return wbuf;
+}
+
+void
+buffer_flush(struct writebuffer *wbuf)
+{
+  full_write (wbuf->fd, wbuf->buffer, wbuf->bpos);
+  wbuf->total += wbuf->bpos;
+  wbuf->bpos = 0;
+}
+
+void
+dump_header (struct writebuffer *wbuf, const char *filename, time_t mtime, struct sp_array *ma)
+{
+  union block *blk = (union block *)buffer_block (wbuf);
+  memset (blk->buffer, 0, BLOCKSIZE);
+  
+  if (strlen(filename)>98) {
+    fprintf (stderr, "ERROR: filename '%s' too long\n", filename); 
+    exit (-1);
+  }
+
+  strncpy (blk->header.name, filename, 100);
+
+  sprintf (blk->header.mode, "%07o", 0644);
+  sprintf (blk->header.uid, "%07o", 0);
+  sprintf (blk->header.gid, "%07o", 0);
+  off12_to_chars (blk->header.mtime, mtime);
+
+  memcpy (blk->header.chksum, "        ", 8);
+
+  blk->header.typeflag = ma->avail ? 'S' : '0';
+  
+  sprintf (blk->header.magic, "%s", OLDGNU_MAGIC);
+
+  sprintf (blk->header.uname, "%s", "root");
+  sprintf (blk->header.gname, "%s", "root");
+
+  size_t ind = 0;
+  if (ind < ma->avail) {
+    size_t i;
+    for (i = 0;i < 4 && ind < ma->avail; i++, ind++) {
+      off12_to_chars (blk->oldgnu_header.sp[i].offset, ma->map[ind].offset);
+      off12_to_chars (blk->oldgnu_header.sp[i].numbytes, ma->map[ind].bytes);
+    }
+  }
+
+  if (ma->avail > 4)
+    blk->oldgnu_header.isextended = 1;
+
+  off12_to_chars (blk->header.size, ma->effective_size);
+  off12_to_chars (blk->oldgnu_header.realsize, ma->real_size);
+
+  int sum = 0;
+  char *p = blk->buffer;
+  int i;
+  for (i = BLOCKSIZE; i-- != 0; )
+    sum += 0xFF & *p++;
+
+  sprintf (blk->header.chksum, "%6o", sum);
+
+  while (ind < ma->avail) {
+    blk = (union block *)buffer_block (wbuf);
+    memset (blk->buffer, 0, BLOCKSIZE);
+    size_t i;
+    for (i = 0;i < 21 && ind < ma->avail; i++, ind++) {
+      off12_to_chars (blk->sparse_header.sp[i].offset, ma->map[ind].offset);
+      off12_to_chars (blk->sparse_header.sp[i].numbytes, ma->map[ind].bytes);
+    }
+    if (ind < ma->avail)
+      blk->sparse_header.isextended = 1;
+
+  }
+}
+
+int
+scan_sparse_file (int fd, struct sp_array *ma)
+{
+  char buffer[BLOCKSIZE];
+  size_t count;
+  off_t offset = 0;
+  off_t file_size = 0;
+  size_t sp_bytes = 0;
+  off_t sp_offset = 0;
+
+  if (lseek (fd, 0, SEEK_SET) < 0)
+    return 0;
+
+  while ((count = full_read (fd, buffer, sizeof (buffer))) > 0) {
+    if (block_is_zero (buffer, count)) {
+      if (sp_bytes) {
+       sparray_add (ma, sp_offset, sp_bytes);
+       sp_bytes = 0;
+      }
+    } else {
+      file_size += count;
+      if (!sp_bytes)
+       sp_offset = offset;
+      sp_bytes += count;
+    }
+    offset += count;
+  }
+
+  if (sp_bytes == 0)
+    sp_offset = offset;
+
+  sparray_add (ma, sp_offset, sp_bytes);
+
+  ma->real_size = offset;
+  ma->effective_size = file_size;
+
+  return 1;
+}
+
+int
+dump_sparse_file (int fd, struct writebuffer *wbuf, struct sp_array *ma)
+{
+  if (lseek (fd, 0, SEEK_SET) < 0)
+    return 0;
+
+  int i;
+  size_t dumped_size = 0;
+  for (i = 0; i < ma->avail; i++) {
+    struct sp_entry *e = &ma->map[i];
+    if (lseek (fd, e->offset, SEEK_SET) < 0)
+      return 0;
+
+    off_t bytes_left = e->bytes;
+
+    while (bytes_left > 0) {
+      size_t bufsize = (bytes_left > BLOCKSIZE) ? BLOCKSIZE : bytes_left;
+      size_t bytes_read;
+
+      char *blkbuf = buffer_block (wbuf);
+      if ((bytes_read = full_read (fd, blkbuf, bufsize)) < 0) {
+       return 0;
+      }
+
+      if (!bytes_read) {
+       fprintf (stderr, "ERROR: got unexpected EOF\n");
+       return 0;
+      }
+
+      memset (blkbuf + bytes_read, 0, BLOCKSIZE - bytes_read);
+
+      dumped_size += bytes_read;
+
+      bytes_left -= bytes_read;
+    }
+  }
+
+  return 1;
+}
+
+int
+main (int argc, char **argv)
+{
+  struct sigaction sa;
+
+  while (1) {
+    int option_index = 0;
+    static struct option long_options[] = {
+      {"output", 1, 0, 'o'},
+      {0, 0, 0, 0}
+    };
+
+    char c = getopt_long (argc, argv, "o:", long_options, &option_index);
+    if (c == -1)
+      break;
+
+    switch (c) {
+    case 'o':
+      outname = optarg;
+      break;
+    default:
+      fprintf (stderr, "?? getopt returned character code 0%o ??\n", c);
+      exit (-1);
+    }
+  }
+
+  int numargs = argc - optind;
+  if (numargs <= 0 || (numargs % 2)) {
+      fprintf (stderr, "wrong number of arguments\n");
+      exit (-1);
+  }
+
+  time_t starttime = time(NULL);
+
+  int outfd;
+
+  if (outname) {
+    if ((outfd = open(outname, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) {
+      fprintf (stderr, "unable to open archive '%s' - %s\n", 
+              outname, strerror (errno));
+      exit (-1);
+    }
+    atexit(cleanup);
+  } else {
+    outfd = fileno (stdout);
+  }
+
+  setsig(&sa, SIGINT, term_handler, SA_RESTART);
+  setsig(&sa, SIGQUIT, term_handler, SA_RESTART);
+  setsig(&sa, SIGTERM, term_handler, SA_RESTART);
+  setsig(&sa, SIGPIPE, term_handler, SA_RESTART);
+
+  int saved_optind = optind;
+  while (optind < argc) {
+    char *source = argv[optind];
+    optind += 2;
+    struct stat fs;
+
+    if (stat (source, &fs) != 0) {
+      fprintf (stderr, "unable to read '%s' - %s\n", 
+              source, strerror (errno));
+      exit (-1);
+    }
+
+    if (!(S_ISREG(fs.st_mode) || S_ISBLK(fs.st_mode))) {
+      fprintf (stderr, "unable to read '%s' - not a file or block device\n", 
+              source);
+      exit (-1);      
+    }
+  }
+
+  optind = saved_optind;
+
+  struct writebuffer *wbuf = buffer_new (outfd);
+
+  while (optind < argc) {
+    char *source = argv[optind++];
+    char *archivename = argv[optind++];
+
+    int fd;
+
+    fprintf (stderr, "adding '%s' to archive ('%s')\n", source, archivename);
+
+    if ((fd = open(source, O_RDONLY)) == -1) {
+      fprintf (stderr, "unable to open '%s' - %s\n", 
+              source, strerror (errno));
+      exit (-1);
+    }
+
+    struct stat fs;
+
+    if (fstat (fd, &fs) != 0) {
+      fprintf (stderr, "unable to stat '%s' - %s\n", 
+              source, strerror (errno));
+      exit (-1);
+    }
+
+    time_t ctime = fs.st_mtime;
+
+    struct sp_array *ma = sparray_new();
+    if (!scan_sparse_file (fd, ma)) {
+      fprintf (stderr, "scanning '%s' failed\n", source); 
+      exit (-1);
+    }
+
+    dump_header (wbuf, archivename, ctime, ma);
+
+    if (!dump_sparse_file (fd, wbuf, ma)) {
+      fprintf (stderr, "writing '%s' to archive failed\n", source); 
+      exit (-1);
+    }
+
+    free (ma);
+
+    close (fd);
+
+  }
+
+  // write tar end
+  char *buf = buffer_block (wbuf);
+  memset (buf, 0, BLOCKSIZE);
+  buf = buffer_block (wbuf);
+  memset (buf, 0, BLOCKSIZE);
+
+  buffer_flush (wbuf);
+
+  close (outfd);
+
+  time_t delay = time(NULL) - starttime;
+  if (delay <= 0) delay = 1;
+
+  fprintf (stderr, "Total bytes written: %zu (%.2f MiB/s)\n", wbuf->total,
+          (wbuf->total/(1024*1024))/(float)delay);
+
+  outname = NULL;
+
+  exit (0);
+}