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:
commit
1e3baf05f2
492
ChangeLog
Normal file
492
ChangeLog
Normal 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
141
Makefile
Normal 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
6
PVE/API2/Makefile
Normal 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
742
PVE/API2/Qemu.pm
Normal 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
6
PVE/Makefile
Normal 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
2666
PVE/QemuServer.pm
Normal file
File diff suppressed because it is too large
Load Diff
4
PVE/VZDump/Makefile
Normal file
4
PVE/VZDump/Makefile
Normal 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
460
PVE/VZDump/QemuServer.pm
Normal 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
10
TODO
Normal 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
218
changelog.Debian
Normal 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
10
control.in
Normal 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
21
copyright
Normal 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
48
gen-vmconf-pod.pl
Executable 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
291
pcitest.pl
Executable 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
48
postinst
Normal 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
8
postrm
Executable 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
68
pve-bridge
Executable 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
25
pve-usb.cfg
Normal 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
45
qemu.init.d
Normal 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
479
qm
Executable 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
|
504
qmigrate
Executable file
504
qmigrate
Executable 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
420
qmrestore
Executable 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
234
qmupdate
Executable 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
132
sparsecp.c
Normal 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
135
utils.c
Normal 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
565
vmtar.c
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user