5
0
mirror of git://git.proxmox.com/git/qemu-server.git synced 2024-12-22 13:34:06 +03:00

imported from svn 'qemu-server/pve2'

This commit is contained in:
Dietmar Maurer 2011-08-23 07:47:04 +02:00
commit 1e3baf05f2
27 changed files with 8851 additions and 0 deletions

492
ChangeLog Normal file
View File

@ -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)

141
Makefile Normal file
View File

@ -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/

6
PVE/API2/Makefile Normal file
View File

@ -0,0 +1,6 @@
SOURCES= \
Qemu.pm
.PHONY: install
install:
install -D -m 0644 Qemu.pm ${DESTDIR}${PERLDIR}/PVE/API2/Qemu.pm

742
PVE/API2/Qemu.pm Normal file
View File

@ -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;

6
PVE/Makefile Normal file
View File

@ -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

2666
PVE/QemuServer.pm Normal file

File diff suppressed because it is too large Load Diff

4
PVE/VZDump/Makefile Normal file
View File

@ -0,0 +1,4 @@
.PHONY: install
install:
install -D -m 0644 QemuServer.pm ${DESTDIR}${PERLDIR}/PVE/VZDump/QemuServer.pm

460
PVE/VZDump/QemuServer.pm Normal file
View File

@ -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;

10
TODO Normal file
View File

@ -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

218
changelog.Debian Normal file
View File

@ -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

10
control.in Normal file
View File

@ -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

21
copyright Normal file
View File

@ -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'.

48
gen-vmconf-pod.pl Executable file
View File

@ -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

291
pcitest.pl Executable file
View File

@ -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;

48
postinst Normal file
View File

@ -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

8
postrm Executable file
View File

@ -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

68
pve-bridge Executable file
View File

@ -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;

25
pve-usb.cfg Normal file
View File

@ -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"

45
qemu.init.d Normal file
View File

@ -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

479
qm Executable file
View File

@ -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

1073
qm.old Executable file

File diff suppressed because it is too large Load Diff

504
qmigrate Executable file
View File

@ -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.

420
qmrestore Executable file
View File

@ -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)

234
qmupdate Executable file
View File

@ -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;

132
sparsecp.c Normal file
View File

@ -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);
}

135
utils.c Normal file
View File

@ -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;
}

565
vmtar.c Normal file
View File

@ -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);
}