├── .gitignore ├── .travis.yml ├── COPYING ├── GPL-2.0 ├── LGPL-2.1 ├── Makefile ├── Makefile.inc ├── Makefile.incl ├── Makefile.version ├── README.md ├── docs └── overview.rst ├── etc ├── Makefile ├── bash_completion.d │ └── ploop ├── logrotate.d │ └── ploop ├── modules-load.d │ └── ploop.conf └── tmpfiles.d │ └── ploop.conf ├── gensym.sh ├── include ├── Makefile ├── dynload.h ├── libploop.h ├── libvolume.h ├── linux │ ├── blkpg.h │ ├── fiemap.h │ ├── fs.h │ ├── ioctl.h │ └── types.h ├── ploop1_image.h └── ploop_if.h ├── lib ├── .gitignore ├── Makefile ├── balloon.c ├── balloon_util.c ├── bit_ops.h ├── cbt.c ├── cbt.h ├── check.c ├── cleanup.c ├── cleanup.h ├── crc32.c ├── crypt.c ├── defrag.c ├── delta_read.c ├── delta_sysfs.c ├── deprecated.c ├── di.c ├── dm.c ├── fsutils.c ├── gpt.c ├── list.h ├── lock.c ├── logger.c ├── merge.c ├── ploop-copy.c ├── ploop.c ├── ploop.h ├── ploop.pc.in ├── qcow.c ├── snapshot.c ├── util.c ├── uuid.c ├── volume.c └── xml.c ├── python ├── .gitignore ├── Makefile ├── libploop │ ├── __init__.py │ └── libploopmodule.c └── setup.py ├── scripts ├── Makefile ├── crypthelper ├── mount.ploop └── umount.ploop ├── setver.sh ├── targets.list ├── test ├── Makefile ├── functions ├── ploop-volume.sh ├── test ├── test-cbt ├── test-change-fmt_version ├── test-device-grow ├── test-device-snapshot ├── test-fs-resize ├── test-pcopy.py ├── test-snapshot └── test-sparse └── tools ├── .gitignore ├── Makefile ├── README ├── common.c ├── common.h ├── macros.tmac ├── ploop-balloon.c ├── ploop-cbt.c ├── ploop-check.c ├── ploop-copy.c ├── ploop-e4defrag.c ├── ploop-grow.c ├── ploop-merge.c ├── ploop-snapshot.c ├── ploop-stat.c ├── ploop-test.c ├── ploop-volume.c ├── ploop.8.in ├── ploop.c └── ploop.sgml /.gitignore: -------------------------------------------------------------------------------- 1 | cscope.out 2 | tags 3 | ploop-*.tar.bz2 4 | *.patch 5 | .build-id 6 | .version-id 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | compiler: gcc 4 | 5 | before_install: 6 | - sudo apt-get update -qq 7 | 8 | install: 9 | - sudo apt-get install make gcc libxml++2.6-dev e2fslibs-dev 10 | 11 | script: 12 | - make -j2 && sudo make install 13 | 14 | notifications: 15 | email: 16 | on_success: change 17 | on_failure: always 18 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | This software is licenced under the GNU GENERAL PUBLIC LICENCE Version 2 | 2. Except that any software in the lib/ directory is for the creation of a 3 | linkable library to the tools and is licensed under the GNU LESSER GENERAL 4 | PUBLIC LICENCE Version 2.1. Contributing Authors agree that their code is 5 | submitted under the licence appropriate for its location within the source 6 | tree (GPL except for LGPL in lib/) and agree that any future patches, provided 7 | they are accepted into the project, may change the licence of their code from 8 | GPL to LGPL by moving pieces of it into lib/ or LGPL to GPL by moving pieces 9 | of it out of lib/ 10 | 11 | Note that the only valid version of the GPL is THIS particular version 12 | of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly 13 | otherwise stated. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include Makefile.inc 2 | 3 | NAMEVER=$(NAME)-$(VERSION)$(RELEASE) 4 | TARBALL=$(NAMEVER).tar.bz2 5 | LICENSES=COPYING GPL-2.0 6 | LLICENSES=$(LICENSES) LGPL-2.1 7 | 8 | SUBDIRS=include lib tools scripts etc python test 9 | 10 | all install clean distclean: 11 | @set -e; \ 12 | for d in $(SUBDIRS); do $(MAKE) -C $$d $@; done 13 | .PHONY: all install clean 14 | 15 | check-api: 16 | $(MAKE) -C include $@ 17 | .PHONY: check-api 18 | 19 | dist: check-api tar 20 | tar: $(TARBALL) 21 | .PHONY: dist tar 22 | 23 | $(TARBALL): clean 24 | rm -f ../$(NAMEVER) 25 | ln -s `pwd | awk -F / '{print $$NF}'` ../$(NAMEVER) 26 | tar --directory .. --exclude-vcs --exclude .depend \ 27 | --exclude-from .gitignore \ 28 | -cvhjf ../$(TARBALL) $(NAMEVER) 29 | rm -f $(TARBALL) 30 | mv ../$(TARBALL) . 31 | rm -f ../$(NAMEVER) 32 | 33 | rpms: tar 34 | rpmbuild -ta $(TARBALL) ${RPMB_ARGS} 35 | .PHONY: rpms 36 | 37 | cov: clean 38 | rm -rf cov-int 39 | cov-build --dir cov-int make 40 | tar czf cov.tgz cov-int 41 | rm -rf cov-int 42 | git describe --tags HEAD 43 | .PHONY: cov 44 | 45 | install-licenses: 46 | for f in $(LLICENSES); do \ 47 | for tdir in $(DDOCDIR) $(LDOCDIR); do \ 48 | mkdir -p $(DESTDIR)$$tdir; \ 49 | install -m 644 $$f $(DESTDIR)$$tdir; \ 50 | done; \ 51 | done 52 | mkdir -p $(DESTDIR)$(DOCDIR); \ 53 | for f in $(LICENSES); do \ 54 | install -m 644 $$f $(DESTDIR)$(DOCDIR); \ 55 | done 56 | 57 | # Add optional local rules 58 | -include Makefile.local 59 | -------------------------------------------------------------------------------- /Makefile.inc: -------------------------------------------------------------------------------- 1 | PLOOPROOT ?= . 2 | 3 | include $(PLOOPROOT)/Makefile.incl 4 | 5 | NAME=ploop 6 | VERSION=$(shell cat $(PLOOPROOT)/Makefile.version) 7 | RELEASE= 8 | 9 | CC=gcc 10 | INSTALL=install 11 | SBINDIR=/sbin 12 | USRSBINDIR=/usr/sbin 13 | INCDIR=/usr/include/ploop 14 | LIBDIR=/usr/lib 15 | # For DEB-based systems 16 | DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null) 17 | ifneq "$(DEB_HOST_MULTIARCH)" "" 18 | LIBDIR=/usr/lib/$(DEB_HOST_MULTIARCH) 19 | # For RPM-based systems 20 | else ifeq "$(shell uname -m)" "x86_64" 21 | LIBDIR=/usr/lib64 22 | endif 23 | LOCKDIR=/var/lock/ploop 24 | MANDIR=/usr/share/man 25 | DOCDIR=/usr/share/doc/$(NAME)-$(VERSION) 26 | LDOCDIR=/usr/share/doc/$(NAME)-lib-$(VERSION) 27 | DDOCDIR=/usr/share/doc/$(NAME)-devel-$(VERSION) 28 | MAN8DIR=$(MANDIR)/man8 29 | TMPFILESDIR=/usr/lib/tmpfiles.d 30 | LOGROTATEDIR=/etc/logrotate.d 31 | COMPLETIONDIR=/etc/bash_completion.d 32 | MODULESLOADDIR=/etc/modules-load.d 33 | LIBSCRIPTDIR=/usr/libexec/ploop 34 | TESTDIR=/usr/libexec/ploop-test 35 | DEBUG=yes 36 | 37 | CFLAGS := $(if $(DEBUG),-g -O0 -DDEBUG,-O2) $(CFLAGS) 38 | CFLAGS += -I. -I../include -fno-strict-aliasing \ 39 | -Wall -Wstrict-prototypes -Werror -Wformat-security -Werror=format-truncation=0 \ 40 | -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \ 41 | -DPRODUCT_NAME_SHORT=\"$(PRODUCT_NAME_SHORT)\" 42 | CFLAGS += $(RPM_OPT_FLAGS) $(CPPFLAGS) 43 | CFLAGS += $(if $(PLOOP_LOG_FILE), -DPLOOP_LOG_FILE=\"$(PLOOP_LOG_FILE)\") 44 | #CFLAGS += -DDEBUG_TIMES 45 | 46 | LDFLAGS += $(if $(DEBUG),-g -rdynamic,) 47 | 48 | export CFLAGS CC 49 | 50 | ifeq ($(strip $(V)),) 51 | E = @echo 52 | Q = @ 53 | else 54 | E = @\# 55 | Q = 56 | endif 57 | export E Q 58 | 59 | %.o: %.c 60 | $(E) " CC " $@ 61 | $(Q) $(CC) $(CFLAGS) -c $< -o $@ 62 | 63 | .depend: 64 | $(E) " DEP " $@ 65 | $(Q) $(CC) $(CFLAGS) -M $^ > $@ 66 | -------------------------------------------------------------------------------- /Makefile.incl: -------------------------------------------------------------------------------- 1 | PRODUCT_NAME_SHORT?=OpenVZ 2 | PRODUCT_URL?=http://openvz.org/Ploop 3 | -------------------------------------------------------------------------------- /Makefile.version: -------------------------------------------------------------------------------- 1 | 9.0.30 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/kolyshkin/ploop.svg?branch=master)](https://travis-ci.org/kolyshkin/ploop) 2 | 3 | ploop 4 | ===== 5 | 6 | OpenVZ ploop 7 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | .. contents:: Table of contents 2 | :depth: 3 3 | 4 | Overview 5 | ======== 6 | 7 | Ploop library provides API to manage image files in **ploop** or **qcow2** format. 8 | Device mapper layer is used to crate block device and work with image as a device, 9 | 10 | Mount 11 | ================= 12 | 13 | The mount action maps image to block device. 14 | 15 | ploop 16 | ----- 17 | * Create block device on ploop image 18 | 19 | $ dmsetup create --table "0 [falloc_new_clu] ploop [... ]" 20 | 21 | * Load CBT if present (see `Set CBT for device`_) 22 | 23 | qcow2 24 | ----- 25 | * Create block device on qcow2 image 26 | 27 | $ dmsetup create --table "0 qcow2 " 28 | 29 | * Load CBT if present (see `Store/load dirty bitmap to/from qcow2 image`_) 30 | 31 | Unmount 32 | ======= 33 | 34 | Sync data to image file and remove block device. 35 | 36 | ploop 37 | ----- 38 | 39 | * Store CBT if present (see `Get CBT from device`_) 40 | * Remove device 41 | 42 | $ dmsetup remove 43 | 44 | qcow2 45 | ----- 46 | 47 | * Store CBT if present (see `Move bitmap from ploop to qcow2`_) 48 | * Remove device 49 | 50 | $ dmsetup remoe 51 | 52 | Resize 53 | ====== 54 | 55 | Grow 56 | ---- 57 | 58 | * Grow device 59 | * resize GPT partition if exists 60 | * resize file system 61 | 62 | Shrink 63 | ------ 64 | * Get balloon file fd 65 | 66 | fd = ioctl(fd, XFS_IOC_OPEN_BALLOON, 0) 67 | 68 | fd = ioctl(fd, EXT4_IOC_OPEN_BALLOON, 0 69 | 70 | * Inflate balloon file 71 | 72 | fallocate(fd, size) 73 | 74 | 75 | Create snapshot 76 | =============== 77 | 78 | Create a checkpoint and start new changes from that point. 79 | This allows to revert to that point in time. 80 | 81 | ploop 82 | ----- 83 | 84 | The create snapshot action adds extra image on top of the active 85 | image and set it as active, the previous active image became 'ro' 86 | 87 | * Create new image 88 | * Suspend device 89 | 90 | $ dmsetup suspend 91 | 92 | * Reload device with new image 93 | 94 | $ dmsetup reload --table "0 ploop ... " 95 | 96 | * resume 97 | 98 | qcow2 99 | ----- 100 | * Suspend device 101 | 102 | $ dmsetup suspend 103 | 104 | * Create image snapshot 105 | 106 | $ qemu-img snapshot -c driver=qcow2,file.driver=file,file.filename=,file.locking=off 107 | 108 | * Reload device to apply new changes 109 | 110 | $ dmsetup reload --table "0 qcow2 " 111 | 112 | * Resume 113 | 114 | $ dmsetup resume DEV 115 | 116 | Delete snaphot 117 | ============== 118 | 119 | ploop 120 | ----- 121 | 122 | The delete snapshot action merges data from child to parent image. 123 | There are three cases of online snapshot deletion 124 | 125 | 1. Child and parent images are 'ro' 126 | 127 | * Copy changed blocks from child to parent. 128 | * Reload device without child image 129 | * Remove child image 130 | 131 | 2. Child is TOP image and there are more than two images. 132 | 133 | * Merge TOP image 134 | 135 | $ dmsetup message DEV 0 merge 136 | 137 | * Remove TOP image 138 | 139 | 3. There only 2 images the BASE an the TOP. 140 | 141 | * Switch the BASE image to 'rw' mode 142 | * Set deny to resume flag on device 143 | 144 | $ dmsetup message 0 set_noresume 1 145 | 146 | * Suspend device 147 | * Mark base image in 'zeroed' transition state 148 | * Zero clusters in BAT of the BASE image which preset in the TOP image 149 | * Swap images, BASE will be TOP 150 | 151 | $ dmsetup message 0 flip_upper_deltas 152 | 153 | * Drop deny to resume flag 154 | 155 | $ dmsetup message 0 set_noresume 0 156 | 157 | * Resume device 158 | * Merge TOP image 159 | 160 | $ dmsetup message DEV 0 merge 161 | 162 | * Remove TOP image 163 | 164 | qcow2 165 | _____ 166 | 167 | * Suspend device 168 | 169 | $ dmsetup suspend 170 | 171 | * Delete image snapshot 172 | 173 | $ qemu-img snapshot -d 174 | 175 | * Reload device 176 | 177 | $ dmsetup reload --table "0 qcow2 178 | 179 | * Resume device 180 | 181 | $ dmsetup resume 182 | 183 | Switch snapshot 184 | =============== 185 | 186 | Revert to a previously created snapshot. 187 | 188 | ploop 189 | ------ 190 | 191 | * Suspend device 192 | 193 | $ dmsetup suspend 194 | 195 | * Switch image snapshot 196 | 197 | 1. create new TOP image 198 | 2. add TOP image on top of image with snapshot ID we switched on 199 | 200 | * Reload device 201 | 202 | $ dmsetup reload --table "0 qcow2 [... ] 203 | 204 | * Resume device 205 | 206 | $ dmsetup resume 207 | 208 | qcow2 209 | ----- 210 | 211 | * Suspend device 212 | 213 | $ dmsetup suspend 214 | 215 | * Switch image snapshot 216 | 217 | $ qemu-img snapshot -a 218 | 219 | * Reload device 220 | 221 | $ dmsetup reload --table "0 qcow2 222 | 223 | * Resume device 224 | 225 | $ dmsetup resume 226 | 227 | 228 | Store/load dirty bitmap to/from qcow2 image 229 | =========================================== 230 | qemu-kvm is used to manage dirty bitmap in qcow2 image. 231 | 232 | Start QEMU 233 | ---------- 234 | 235 | Start QEMU with two block devices: raw ploop device, so that QEMU can get CBT by ioctl and qcow2 node (so that QEMU can store bitmaps to it). We know, that ploop is backed by same qcow2 file, but QEMU doesn't know it and consider them as different files. 236 | 237 | To pass different files we define two different fd sets. 238 | 239 | qemu-kvm -add-fd fd=10,set=1,opaque="qcow2-path" -add-fd fd=11,set=2,opaque="ploop" 240 | :: 241 | 242 | qemu-kvm -S -nodefaults -nographic \ 243 | -add-fd fd=14,set=1,opaque="ro:/path/to/ploop/device" \ # FD of ploop device. It will be used only call ioctl to get the CBT 244 | -add-fd fd=15,set=2,opaque="rw:/path/to/disk.qcow2" \ # FD of qcow2. It will be used to store the CBT into it 245 | -blockdev '{"node-name": "vz-ploop", "driver": "host_device", "filename": "/dev/fdset/1"}' \ # block-node of ploop 246 | -blockdev '{"node-name": "vz-protocol-node", "driver": "file", "filename": "/dev/fdset/2", "locking": "off"} \ # protocol node of qcow2 file. Note locking=off, as lock is held by ploop utility. Used only to create qcow2 node on top of it, we'll not manipulate with protocol node directly 247 | -blockdev '{"node-name": "vz-qcow2-node", "driver": "qcow2", "file": "vz-protocol-node", "__vz_keep-dirty-bit": true} # format node of qcow2 file. 248 | 249 | Note: 250 | 251 | * we disable locking on qcow2 file 252 | * we use __vz_keep-dirty-bit=true so that Qemu don't touch qcow2 dirty bit: don't check on start, don't reset it neither on start nor on stop. 253 | * driver: host_device is used for opening the device, not driver: file, like for regular files. 254 | 255 | Move bitmap from ploop to qcow2 256 | ------------------------------- 257 | 258 | `start QEMU`_ 259 | 260 | move CBT by qmp command 261 | :: 262 | 263 | qmp transaction { 264 | block-dirty-bitmap-add {"node": "vz-qcow2-node", "name": "UUID", "persistent": true} 265 | block-dirty-bitmap-merge { "node": "vz-qcow2-node", "target": "UUID", "bitmaps": [{"node": "vz-ploop", "name": "UUID", "__vz_pull": true}]} 266 | } 267 | 268 | Note: 269 | 270 | * persistent=true - this means that bitmap should be saved on Qemu stop. 271 | 272 | Move bitmap from qcow2 to ploop node 273 | ------------------------------------ 274 | 275 | `Start QEMU`_ 276 | 277 | start CBT and set it by command: 278 | :: 279 | 280 | qmp: block-dirty-bitmap-merge { "node": "my-ploop", "target": "name-of-dirty-bitmap", "__vz_push": true, "bitmaps": [{"node": "my-qcow2-node", "name": "UUID"}]} 281 | 282 | Kernal interface to manage CBT 283 | ============================== 284 | 285 | Set CBT for device 286 | ------------------ 287 | 288 | 1. Start CBT 289 | :: 290 | 291 | ioctl(fd, BLKCBTSTART, struct blk_user_cbt_info*ci) 292 | ci.ci_blksize is block size (usually 64K). 293 | ci.ci_uuid is CBT. 294 | The rest ci fields has to be zeroed. 295 | 296 | ERRORS: Any error is critical. 297 | 298 | 2. Load CBT mask 299 | :: 300 | 301 | ioctl(fd, BLKCBTSET, struct blk_user_cbt_info *ci) 302 | ci.ci_extent_count = CBT_MAX_EXTENTS (ci.ci_extent_count is number of passed extents) 303 | ci.ci_mapped_extents is equal to ci.ci_extent_count 304 | ci.ci_extents are array of dirty extents you want to pass 305 | ci.ci_uuid is the same as in BLKCBTSTART 306 | The rest of fields has to be zeroed. 307 | 308 | ERRORS: Any error is critical (we should either drop CBT from image or break start). 309 | 310 | Get CBT from device 311 | ------------------- 312 | 313 | 1, Merge CBT snapshot back. It exists in case of there was failed backup, 314 | :: 315 | 316 | ioctl(fd, BLKCBTMISC, struct blk_user_cbt_misc_info *cmi) 317 | cmi.action = CBT_SNAP_MERGE_BACK; 318 | cmi.uuid = uuid; 319 | 320 | ERRORS: 321 | 322 | -ENODEV is not critical, it means (there is no a snapshot). 323 | 324 | The rest of errors are critical (we stop CT without saving CBT). 325 | 326 | 2. Get CBT mask. 327 | :: 328 | 329 | ioctl(fd, BLKCBTGET, struct blk_user_cbt_info *ci): 330 | ci.ci_extent_count is number of extents (max is CBT_MAX_EXTENTS == 512) 331 | ci.ci_start is start of range you interested in bytes 332 | ci.ci_length is length of that range 333 | 334 | On exit the ioctl returns extents in ci.ci_extents and populates ci.ci_uuid. 335 | 336 | ERRORS: Any error is critical 337 | 338 | 3. Stop CBT 339 | :: 340 | 341 | ioctl(fd, BLKCBTSTOP, NULL) 342 | 343 | ERRORS: Errors are not critical 344 | 345 | 346 | Online image migration 347 | ====================== 348 | 349 | Online image migration logic consist from 3 stages 350 | 351 | 1. start tracking and copy allocated blocks 352 | 2. iteratively copy changed blocks 353 | 3. suspend device and copy changed blocks 354 | 355 | Block allocation information is taken from image header. 356 | Chaned block tracking is based based on dm-tracking driver. 357 | 358 | Tracking API: 359 | 360 | * create tracking device 361 | 362 | dmsetup create tracking_dev --table "0 tracking DEV" 363 | 364 | * start tracking 365 | 366 | dmsetup supend tracking_dev 367 | 368 | dmsetup message tracking_dev 0 tracking_start 369 | 370 | dmsetup resume tracking_dev 371 | 372 | * stop tracking 373 | 374 | dmsetup message tracking_dev 0 tracking_stop 375 | 376 | * get next changed cluster 377 | 378 | dmsetup message tracking_dev 0 tracking_get_next 379 | 380 | 381 | 382 | 383 | 384 | -------------------------------------------------------------------------------- /etc/Makefile: -------------------------------------------------------------------------------- 1 | PLOOPROOT = .. 2 | 3 | include $(PLOOPROOT)/Makefile.inc 4 | 5 | all clean distclean: 6 | .PHONY: all clean distclean 7 | 8 | install_tmpfiles: 9 | $(Q) $(INSTALL) -d $(DESTDIR)$(TMPFILESDIR) 10 | $(Q) $(INSTALL) -m 644 tmpfiles.d/ploop.conf $(DESTDIR)$(TMPFILESDIR) 11 | 12 | install_logrotate: 13 | $(Q) $(INSTALL) -d $(DESTDIR)/$(LOGROTATEDIR) 14 | $(Q) $(INSTALL) -m 644 logrotate.d/ploop $(DESTDIR)/$(LOGROTATEDIR) 15 | 16 | install_bash_completion: 17 | $(Q) $(INSTALL) -d $(DESTDIR)/$(COMPLETIONDIR) 18 | $(Q) $(INSTALL) -m 644 bash_completion.d/ploop $(DESTDIR)/$(COMPLETIONDIR) 19 | 20 | install_modulesload: 21 | $(Q) $(INSTALL) -d $(DESTDIR)/$(MODULESLOADDIR) 22 | $(Q) $(INSTALL) -m 644 modules-load.d/ploop.conf $(DESTDIR)/$(MODULESLOADDIR) 23 | 24 | install: install_tmpfiles install_bash_completion install_modulesload $(if $(PLOOP_LOG_FILE), install_logrotate) 25 | 26 | .PHONY: install install_tmpfiles install_bash_completion install_logrotate install_modulesload 27 | -------------------------------------------------------------------------------- /etc/bash_completion.d/ploop: -------------------------------------------------------------------------------- 1 | _ploop() 2 | { 3 | 4 | local cur=${COMP_WORDS[COMP_CWORD]} 5 | local prev=${COMP_WORDS[COMP_CWORD-1]} 6 | 7 | local ploop_cmds="init mount umount check convert resize balloon snapshot snapshot-delete \ 8 | snapshot-merge snapshot-switch snapshot-list tsnapshot \ 9 | restore-descriptor replace encrypt start stop delete clear \ 10 | merge grow copy stat info list" 11 | 12 | local ploop_check_opts="--force --hard-force --check --ro --silent --raw --drop-inuse --blocksize --repair-sparse" 13 | local ploop_snapshot_list_opts="--no-header --uuid --id --snapshot --output" 14 | local ploop_balloon_opts="show status clear change complete check repair discard" 15 | local ploop_balloon_discard_opts="--automount --defrag --to-free --min-block --stat" 16 | 17 | case $COMP_CWORD in 18 | 1) #Command or common options: 19 | COMPREPLY=( $( compgen -W "$ploop_cmds" -- $cur ) ) 20 | ;; 21 | *) #Print additional options only if the current current symbol is '-' 22 | if [[ "${cur::1}" == "-" ]]; then 23 | local cmd=${COMP_WORDS[1]} 24 | case "$cmd" in 25 | check) 26 | COMPREPLY=( $( compgen -W "$ploop_check_opts" -- $cur ) ) 27 | ;; 28 | snapshot-list) 29 | COMPREPLY=( $( compgen -W "$ploop_snapshot_list_opts" -- $cur ) ) 30 | ;; 31 | balloon) 32 | #Print additional options only for 'ploop balloon discard' 33 | if [[ ${COMP_WORDS[2]} == "discard" ]]; then 34 | COMPREPLY=( $( compgen -W "$ploop_balloon_discard_opts" -- $cur ) ) 35 | fi 36 | ;; 37 | *) 38 | COMPREPLY=( $( compgen -W "" -- $cur ) ) 39 | ;; 40 | esac 41 | else 42 | local cmd=${prev} 43 | case "$cmd" in 44 | balloon) 45 | COMPREPLY=( $( compgen -W "$ploop_balloon_opts" -- $cur ) ) 46 | ;; 47 | *) #If we are have not matches, then completion set - the list of files/directories. It's need for convenience of specifying the path to the ploop. 48 | _filedir 49 | ;; 50 | esac 51 | fi 52 | ;; 53 | esac 54 | } && 55 | complete -F _ploop ploop 56 | 57 | #Function for alias 'ploop-balloon' 58 | _ploop_balloon() 59 | { 60 | 61 | local cur=${COMP_WORDS[COMP_CWORD]} 62 | local prev=${COMP_WORDS[COMP_CWORD-1]} 63 | 64 | local ploop_balloon_cmds="show status clear change complete check repair discard" 65 | 66 | local ploop_balloon_discard_opts="--automount --defrag --to-free --min-block --stat" 67 | 68 | case $COMP_CWORD in 69 | 1) 70 | #Command or common options: 71 | COMPREPLY=( $( compgen -W "$ploop_balloon_cmds" -- $cur ) ) 72 | ;; 73 | *) if [[ "${cur::1}" == "-" ]]; then 74 | local cmd=${COMP_WORDS[1]} 75 | case "$cmd" in 76 | discard) 77 | COMPREPLY=( $( compgen -W "$ploop_balloon_discard_opts" -- $cur ) ) 78 | ;; 79 | *) 80 | COMPREPLY=( $( compgen -W "" -- $cur ) ) 81 | ;; 82 | esac 83 | else 84 | _filedir 85 | fi 86 | ;; 87 | esac 88 | } && 89 | complete -F _ploop_balloon ploop-balloon 90 | 91 | #Function for alias 'ploop-volume' 92 | _ploop_volume() 93 | { 94 | 95 | local cur=${COMP_WORDS[COMP_CWORD]} 96 | local prev=${COMP_WORDS[COMP_CWORD-1]} 97 | 98 | local ploop_volume_cmds="create clone snapshot delete switch" 99 | 100 | case $COMP_CWORD in 101 | 1) 102 | #Command or common options: 103 | COMPREPLY=( $( compgen -W "$ploop_volume_cmds" -- $cur ) ) 104 | ;; 105 | *) 106 | case "$prev" in 107 | *) 108 | _filedir 109 | ;; 110 | esac 111 | ;; 112 | esac 113 | 114 | } && 115 | complete -F _ploop_volume ploop-volume 116 | 117 | #Function for alias 'ploop-cbt' 118 | _ploop_cbt() 119 | { 120 | 121 | local cur=${COMP_WORDS[COMP_CWORD]} 122 | local prev=${COMP_WORDS[COMP_CWORD-1]} 123 | 124 | local ploop_cbt_cmds="dump drop show" 125 | 126 | case $COMP_CWORD in 127 | 1) 128 | #Command or common options: 129 | COMPREPLY=( $( compgen -W "$ploop_cbt_cmds" -- $cur ) ) 130 | ;; 131 | *) 132 | case "$prev" in 133 | *) 134 | _filedir 135 | ;; 136 | esac 137 | ;; 138 | esac 139 | } && 140 | complete -F _ploop_cbt ploop-cbt -------------------------------------------------------------------------------- /etc/logrotate.d/ploop: -------------------------------------------------------------------------------- 1 | /var/log/ploop.log { 2 | monthly 3 | rotate 6 4 | copytruncate 5 | notifempty 6 | missingok 7 | } 8 | -------------------------------------------------------------------------------- /etc/modules-load.d/ploop.conf: -------------------------------------------------------------------------------- 1 | ploop 2 | dm_qcow2 3 | dm_tracking 4 | -------------------------------------------------------------------------------- /etc/tmpfiles.d/ploop.conf: -------------------------------------------------------------------------------- 1 | d /run/lock/ploop 0755 root root - 2 | -------------------------------------------------------------------------------- /gensym.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | SELF=$(basename $0) 4 | usage() { 5 | echo "Usage: $SELF " 1>&2 6 | echo " $SELF include/libploop.h dynload.h" 1>&2 7 | echo " $SELF include/libploop.h symbols.c" 1>&2 8 | exit 1 9 | } 10 | 11 | INPUT=$1 12 | if ! test -r $INPUT; then 13 | echo "$SELF: can't read input file $INPUT" 1>&2 14 | usage 15 | fi 16 | shift 17 | 18 | # Extract only function prototypes from libploop.h 19 | # 1 Only leave part inside extern "C" { .... } 20 | # 2 Remove 'extern "C"' and the #ifdef/#endif 21 | # 3 Remove empty lines and lines with /* comments 22 | # 4 Remove extra spaces and tabs 23 | # 5 Remove PLOOP_DEPRECATED mark 24 | # 6 Remove newlines, only add newlines after ; 25 | extract_functions() { 26 | cat $INPUT | \ 27 | sed -n -e '/^extern "C" {$/,/^}$/p' | \ 28 | sed -n -e '3,$p' | head -n-2 | \ 29 | grep -v '^$' | grep -v '^\/\*' | \ 30 | sed 's/[ \t][ \t]*/ /g' | \ 31 | sed 's/[ \t]*PLOOP_DEPRECATED[ \t]*//' | \ 32 | tr -d '\n' | sed 's/;/;\n/g' 33 | } 34 | 35 | disclaimer() { 36 | echo "/* This file is auto-generated from $INPUT by $SELF." 37 | echo " * DO NOT EDIT" 38 | echo " */" 39 | } 40 | 41 | gen_h() { 42 | # Make list of pointers to functions 43 | extract_functions | sort | \ 44 | sed 's/^\([^(]*\)ploop_\([a-z_]*\)/\t\1(*\2)/' 45 | } 46 | 47 | gen_c() { 48 | disclaimer 49 | echo 50 | echo "/* avoid -Werror=deprecated-declarations linker error */" 51 | echo "#define PLOOP_DEPRECATED" 52 | echo "#include " 53 | echo "#include " 54 | echo 55 | echo "void ploop_resolve_functions(struct ploop_functions * f) {" 56 | echo "#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))" 57 | echo "BUILD_BUG_ON(sizeof(*f) != 128*sizeof(void *));" 58 | # Initialize the structure with names 59 | extract_functions | \ 60 | sed 's/^.*[* ]ploop_\([a-z_]*\)(.*$/\1/' | \ 61 | awk '{printf "\tf->%-30s\t= ploop_%s;\n", $1, $1; }' 62 | 63 | echo "};" 64 | } 65 | 66 | case $1 in 67 | *.h) 68 | gen_h > $1 69 | ;; 70 | *.c) 71 | gen_c > $1 72 | ;; 73 | run) 74 | # Run any function -- for debug/testing 75 | if test -z "$2"; then 76 | echo "Usage: $SELF run " 77 | fi 78 | $2 79 | ;; 80 | *) 81 | usage 82 | ;; 83 | esac 84 | -------------------------------------------------------------------------------- /include/Makefile: -------------------------------------------------------------------------------- 1 | PLOOPROOT = .. 2 | 3 | include $(PLOOPROOT)/Makefile.inc 4 | 5 | INCLUDES=libploop.h libvolume.h ploop_if.h ploop1_image.h dynload.h 6 | 7 | all: $(INCLUDES) check-api 8 | .PHONY: all 9 | 10 | clean: 11 | $(E) " CLEAN " 12 | $(Q) rm -f new-dynload.h 13 | .PHONY: clean 14 | 15 | distclean: clean 16 | .PHONY: distclean 17 | 18 | install: 19 | $(Q) $(INSTALL) -d $(DESTDIR)$(INCDIR) 20 | $(E) " INSTALL " $(INCLUDES) 21 | $(Q) $(INSTALL) -m 644 $(INCLUDES) $(DESTDIR)$(INCDIR) 22 | .PHONY: install 23 | 24 | check-api: ../gensym.sh libploop.h 25 | $(E) " CHECK " dynload.h 26 | $(Q) $^ new-dynload.h 27 | $(Q) sed -e '1,/^struct ploop_functions {$$/d' \ 28 | -e '/\/\* struct ploop_functions \*\/$$/,$$d' dynload.h | \ 29 | grep -vE '^[[:space:]]*/\*.**/$$' | \ 30 | grep -v '^ void \*padding\[' | \ 31 | grep -v '^ void (\*obsolete_' | \ 32 | sort > cur-dynload.h 33 | $(Q) diff -u cur-dynload.h new-dynload.h || \ 34 | ( echo "*** API CHANGED, PLEASE UPDATE dynload.h"; exit 1 ) 35 | $(Q) rm -f cur-dynload.h new-dynload.h 36 | .PHONY: check-api 37 | -------------------------------------------------------------------------------- /include/dynload.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef _PLOOP_DYNLOAD_H_ 21 | #define _PLOOP_DYNLOAD_H_ 22 | 23 | #include 24 | #include 25 | 26 | struct ploop_functions { 27 | /* 1.5 */ 28 | void (*obsolete_1)(void); 29 | int (*set_component_name)(struct ploop_disk_images_data *di, const char *component_name); 30 | void (*obsolete_2)(void); 31 | char *(*get_base_delta_uuid)(struct ploop_disk_images_data *di); 32 | int (*get_top_delta_fname)(struct ploop_disk_images_data *di, char *out, int len); 33 | void (*obsolete_3)(void); 34 | int (*find_dev)(const char *component_name, const char *delta, char *buf, int size); 35 | int (*get_dev_by_mnt)(const char *path, char *buf, int size); 36 | int (*get_mnt_by_dev)(const char *dev, char *buf, int size); 37 | int (*get_dev)(struct ploop_disk_images_data *di, char *out, int len); 38 | int (*get_partition_by_mnt)(const char *path, char *buf, int size); 39 | int (*create_image)(struct ploop_create_param *param); 40 | int (*mount_image)(struct ploop_disk_images_data *di, struct ploop_mount_param *param); 41 | int (*mount_snapshot)(struct ploop_disk_images_data *di, struct ploop_mount_param *param); 42 | int (*umount)(const char *device, struct ploop_disk_images_data *di); 43 | int (*umount_image)(struct ploop_disk_images_data *di); 44 | int (*resize_image)(struct ploop_disk_images_data *di, struct ploop_resize_param *param); 45 | int (*convert_image)(struct ploop_disk_images_data *di, int mode, int flags); 46 | int (*get_info_by_descr)(const char *descr, struct ploop_info *info); 47 | int (*create_snapshot)(struct ploop_disk_images_data *di, struct ploop_snapshot_param *param); 48 | int (*merge_snapshot)(struct ploop_disk_images_data *di, struct ploop_merge_param *param); 49 | int (*switch_snapshot)(struct ploop_disk_images_data *di, const char *uuid, int flags); 50 | int (*delete_snapshot)(struct ploop_disk_images_data *di, const char *guid); 51 | int (*delete_top_delta)(struct ploop_disk_images_data *di); 52 | int (*find_top_delta_name_and_format)( const char *device, char *image, size_t image_size, char *format, size_t format_size); 53 | char *(*find_parent_by_guid)(struct ploop_disk_images_data *di, const char *guid); 54 | int (*uuid_generate)(char *uuid, int len); 55 | const char *(*get_last_error)(void); 56 | int (*set_log_file)(const char *fname); 57 | void (*set_log_level)(int level); 58 | void (*set_verbose_level)(int level); 59 | void (*cancel_operation)(void); 60 | int (*discard_get_stat)(struct ploop_disk_images_data *di, struct ploop_discard_stat *pd_stat); 61 | int (*discard)(struct ploop_disk_images_data *di, struct ploop_discard_param *param); 62 | /* 1.6 */ 63 | int (*switch_snapshot_ex)(struct ploop_disk_images_data *di, struct ploop_snapshot_switch_param *param); 64 | int (*complete_running_operation)(const char *device); 65 | /* 1.7: no new functions */ 66 | /* 1.8 */ 67 | int (*is_large_disk_supported)(void); 68 | int (*get_spec)(struct ploop_disk_images_data *di, struct ploop_spec *spec); 69 | /* 1.9 */ 70 | int (*get_devs)(struct ploop_disk_images_data *di, char **out[]); 71 | void (*free_array)(char *array[]); 72 | /* 1.10: no new functions */ 73 | /* 1.11 */ 74 | int (*replace_image)(struct ploop_disk_images_data *di, struct ploop_replace_param *param); 75 | int (*open_dd)(struct ploop_disk_images_data **di, const char *fname); 76 | void (*close_dd)(struct ploop_disk_images_data *di); 77 | int (*create_temporary_snapshot)(struct ploop_disk_images_data *di, struct ploop_tsnapshot_param *param, int *holder_fd); 78 | int (*is_mounted)(struct ploop_disk_images_data *di); 79 | /* 1.12 */ 80 | int (*copy_receiver)(struct ploop_copy_receive_param *arg); 81 | int (*get_max_size)(unsigned int blocksize, unsigned long long *max); 82 | int (*create_dd)(const char *ddxml, struct ploop_create_param *param); 83 | /* 1.13 */ 84 | int (*set_max_delta_size)(struct ploop_disk_images_data *di, unsigned long long size); 85 | /* VZ7 7.0.27 */ 86 | int (*get_base_delta_fname)(struct ploop_disk_images_data *di, char *out, int len); 87 | int (*read_dd)(struct ploop_disk_images_data *di); 88 | /* padding for up to 64 pointers */ 89 | int (*get_part)(struct ploop_disk_images_data *di, const char *dev, char *partname, int len); 90 | int (*get_devname)(struct ploop_disk_images_data *di, const char *dev, char *devname, int dlen, char *partname, int plen); 91 | int (*set_encryption_keyid)(struct ploop_disk_images_data *di, const char *keyid); 92 | int (*encrypt_image)(struct ploop_disk_images_data *di, struct ploop_encrypt_param *param); 93 | int (*init_device)(const char *device, struct ploop_create_param *param); 94 | int (*resize_blkdev)(const char *device, off_t new_size); 95 | void (*set_umount_timeout)(struct ploop_disk_images_data *di, int timeout); 96 | int (*init_image)(struct ploop_disk_images_data *di, struct ploop_create_param *param); 97 | int (*drop_cbt)(struct ploop_disk_images_data *di); 98 | int (*clone_dd)(struct ploop_disk_images_data *di, const char *guid, const char *target); 99 | struct ploop_bitmap *(*get_used_bitmap_from_image)(struct ploop_disk_images_data *di, const char *guid); 100 | void (*release_bitmap)(struct ploop_bitmap *bmap); 101 | struct ploop_bitmap *(*get_tracking_bitmap_from_image)(struct ploop_disk_images_data *di, const char *guid); 102 | int (*get_fs_info)(const char *descr, struct ploop_fs_info *info, int size); 103 | int (*image_defrag)(const char *image, int flags); 104 | int (*get_names)(const char *devname, char **names[]); 105 | int (*dm_message)(const char *devname, const char *msg, char **out); 106 | int (*resume_device)(const char *devname); 107 | int (*store_statfs_info)(const char *mnt, char *image); 108 | int (*suspend_device)(const char *devname); 109 | void (*free_dm_message)(char *msg); 110 | int (*tg_deinit)(const char *devtg, struct ploop_tg_data *data); 111 | int (*tg_info)(const char *devname, struct ploop_tg_info *param); 112 | int (*tg_init)(const char *dev, const char *tg, unsigned int tg_blocksize, struct ploop_tg_data *out); 113 | int (*get_mnt_info)(const char *partname, struct ploop_mnt_info *info); 114 | int (*compact)(struct ploop_compact_param *param); 115 | int (*make_dd_from_imgs)(struct ploop_disk_images_data **di, char **imgs); 116 | int (*make_dd_from_device)(struct ploop_disk_images_data **di, const char *device); 117 | void *padding[49]; 118 | }; /* struct ploop_functions */ 119 | 120 | __attribute__ ((visibility("default"))) 121 | void ploop_resolve_functions(struct ploop_functions * f); 122 | #endif 123 | -------------------------------------------------------------------------------- /include/libvolume.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef _LIBVOLUME_H_ 21 | #define _LIBVOLUME_H_ 22 | 23 | #pragma GCC visibility push(default) 24 | 25 | #include 26 | 27 | struct ploop_volume_tree_element; 28 | 29 | SLIST_HEAD(ploop_volume_list_head, ploop_volume_tree_element); 30 | 31 | struct ploop_volume_tree_element { 32 | SLIST_ENTRY(ploop_volume_tree_element) next; 33 | struct ploop_volume_list_head children; 34 | 35 | char* path; 36 | }; 37 | 38 | struct ploop_volume_data { 39 | const char *m_path; 40 | const char *i_path; 41 | }; 42 | 43 | struct ploop_volume_info { 44 | off_t size; 45 | }; 46 | 47 | struct ploop_create_param; 48 | #ifdef __cplusplus 49 | extern "C" { 50 | #endif 51 | 52 | int ploop_volume_create(struct ploop_volume_data *vol, 53 | struct ploop_create_param *param); 54 | int ploop_volume_clone(const char *src, struct ploop_volume_data *vol); 55 | int ploop_volume_snapshot(const char *src, struct ploop_volume_data *vol); 56 | int ploop_volume_delete(const char *path); 57 | int ploop_volume_switch(const char *from, const char *to); 58 | int ploop_volume_get_info(const char *path, struct ploop_volume_info *info, int size); 59 | int ploop_volume_get_tree(const char *path, struct ploop_volume_list_head *root, int size); 60 | void ploop_volume_clear_tree(struct ploop_volume_list_head *root); 61 | 62 | #ifdef __cplusplus 63 | } 64 | #endif 65 | 66 | #pragma GCC visibility pop 67 | #endif 68 | -------------------------------------------------------------------------------- /include/linux/blkpg.h: -------------------------------------------------------------------------------- 1 | #ifndef _LINUX_BLKPG_H 2 | #define _LINUX_BLKPG_H 3 | 4 | /* 5 | * Partition table and disk geometry handling 6 | * 7 | * A single ioctl with lots of subfunctions: 8 | * 9 | * Device number stuff: 10 | * get_whole_disk() (given the device number of a partition, 11 | * find the device number of the encompassing disk) 12 | * get_all_partitions() (given the device number of a disk, return the 13 | * device numbers of all its known partitions) 14 | * 15 | * Partition stuff: 16 | * add_partition() 17 | * delete_partition() 18 | * test_partition_in_use() (also for test_disk_in_use) 19 | * 20 | * Geometry stuff: 21 | * get_geometry() 22 | * set_geometry() 23 | * get_bios_drivedata() 24 | * 25 | * For today, only the partition stuff - aeb, 990515 26 | */ 27 | 28 | #include 29 | 30 | #define BLKPG _IO(0x12,105) 31 | 32 | /* The argument structure */ 33 | struct blkpg_ioctl_arg { 34 | int op; 35 | int flags; 36 | int datalen; 37 | void *data; 38 | }; 39 | 40 | /* The subfunctions (for the op field) */ 41 | #define BLKPG_ADD_PARTITION 1 42 | #define BLKPG_DEL_PARTITION 2 43 | #define BLKPG_RESIZE_PARTITION 3 44 | #define BLKPG_GET_PARTITION 4 45 | 46 | /* Sizes of name fields. Unused at present. */ 47 | #define BLKPG_DEVNAMELTH 64 48 | #define BLKPG_VOLNAMELTH 64 49 | 50 | /* The data structure for ADD_PARTITION and DEL_PARTITION */ 51 | struct blkpg_partition { 52 | long long start; /* starting offset in bytes */ 53 | long long length; /* length in bytes */ 54 | int pno; /* partition number */ 55 | char devname[BLKPG_DEVNAMELTH]; /* partition name, like sda5 or c0d1p2, 56 | to be used in kernel messages */ 57 | char volname[BLKPG_VOLNAMELTH]; /* volume label */ 58 | }; 59 | 60 | #endif /* _LINUX_BLKPG_H */ 61 | -------------------------------------------------------------------------------- /include/linux/fiemap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * FS_IOC_FIEMAP ioctl infrastructure. 3 | * 4 | * Some portions copyright (C) 2007 Cluster File Systems, Inc 5 | * 6 | * Authors: Mark Fasheh 7 | * Kalpak Shah 8 | * Andreas Dilger 9 | */ 10 | 11 | #ifndef _LINUX_FIEMAP_H 12 | #define _LINUX_FIEMAP_H 13 | 14 | #include 15 | 16 | struct fiemap_extent { 17 | __u64 fe_logical; /* logical offset in bytes for the start of 18 | * the extent from the beginning of the file */ 19 | __u64 fe_physical; /* physical offset in bytes for the start 20 | * of the extent from the beginning of the disk */ 21 | __u64 fe_length; /* length in bytes for this extent */ 22 | __u64 fe_reserved64[2]; 23 | __u32 fe_flags; /* FIEMAP_EXTENT_* flags for this extent */ 24 | __u32 fe_reserved[3]; 25 | }; 26 | 27 | struct fiemap { 28 | __u64 fm_start; /* logical offset (inclusive) at 29 | * which to start mapping (in) */ 30 | __u64 fm_length; /* logical length of mapping which 31 | * userspace wants (in) */ 32 | __u32 fm_flags; /* FIEMAP_FLAG_* flags for request (in/out) */ 33 | __u32 fm_mapped_extents;/* number of extents that were mapped (out) */ 34 | __u32 fm_extent_count; /* size of fm_extents array (in) */ 35 | __u32 fm_reserved; 36 | struct fiemap_extent fm_extents[0]; /* array of mapped extents (out) */ 37 | }; 38 | 39 | #define FIEMAP_MAX_OFFSET (~0ULL) 40 | 41 | #define FIEMAP_FLAG_SYNC 0x00000001 /* sync file data before map */ 42 | #define FIEMAP_FLAG_XATTR 0x00000002 /* map extended attribute tree */ 43 | 44 | #define FIEMAP_FLAGS_COMPAT (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR) 45 | 46 | #define FIEMAP_EXTENT_LAST 0x00000001 /* Last extent in file. */ 47 | #define FIEMAP_EXTENT_UNKNOWN 0x00000002 /* Data location unknown. */ 48 | #define FIEMAP_EXTENT_DELALLOC 0x00000004 /* Location still pending. 49 | * Sets EXTENT_UNKNOWN. */ 50 | #define FIEMAP_EXTENT_ENCODED 0x00000008 /* Data can not be read 51 | * while fs is unmounted */ 52 | #define FIEMAP_EXTENT_DATA_ENCRYPTED 0x00000080 /* Data is encrypted by fs. 53 | * Sets EXTENT_NO_BYPASS. */ 54 | #define FIEMAP_EXTENT_NOT_ALIGNED 0x00000100 /* Extent offsets may not be 55 | * block aligned. */ 56 | #define FIEMAP_EXTENT_DATA_INLINE 0x00000200 /* Data mixed with metadata. 57 | * Sets EXTENT_NOT_ALIGNED.*/ 58 | #define FIEMAP_EXTENT_DATA_TAIL 0x00000400 /* Multiple files in block. 59 | * Sets EXTENT_NOT_ALIGNED.*/ 60 | #define FIEMAP_EXTENT_UNWRITTEN 0x00000800 /* Space allocated, but 61 | * no data (i.e. zero). */ 62 | #define FIEMAP_EXTENT_MERGED 0x00001000 /* File does not natively 63 | * support extents. Result 64 | * merged for efficiency. */ 65 | 66 | #endif /* _LINUX_FIEMAP_H */ 67 | -------------------------------------------------------------------------------- /include/linux/ioctl.h: -------------------------------------------------------------------------------- 1 | #ifndef _LINUX_IOCTL_H 2 | #define _LINUX_IOCTL_H 3 | 4 | #include 5 | 6 | #endif /* _LINUX_IOCTL_H */ 7 | 8 | -------------------------------------------------------------------------------- /include/linux/types.h: -------------------------------------------------------------------------------- 1 | #ifndef _LINUX_TYPES_H 2 | #define _LINUX_TYPES_H 3 | 4 | #include 5 | 6 | #ifndef __ASSEMBLY__ 7 | 8 | 9 | 10 | /* 11 | * Below are truly Linux-specific types that should never collide with 12 | * any application/library that wants linux/types.h. 13 | */ 14 | 15 | #ifdef __CHECKER__ 16 | #define __bitwise__ __attribute__((bitwise)) 17 | #else 18 | #define __bitwise__ 19 | #endif 20 | #ifdef __CHECK_ENDIAN__ 21 | #define __bitwise __bitwise__ 22 | #else 23 | #define __bitwise 24 | #endif 25 | 26 | typedef __u16 __bitwise __le16; 27 | typedef __u16 __bitwise __be16; 28 | typedef __u32 __bitwise __le32; 29 | typedef __u32 __bitwise __be32; 30 | typedef __u64 __bitwise __le64; 31 | typedef __u64 __bitwise __be64; 32 | 33 | typedef __u16 __bitwise __sum16; 34 | typedef __u32 __bitwise __wsum; 35 | 36 | #endif /* __ASSEMBLY__ */ 37 | #endif /* _LINUX_TYPES_H */ 38 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | .depend 2 | *.o 3 | libploop.a 4 | libploop.so 5 | symbols.c 6 | ploop.pc 7 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | PLOOPROOT = .. 2 | 3 | include $(PLOOPROOT)/Makefile.inc 4 | 5 | V_MAJOR := $(word 1,$(subst ., ,$(VERSION))) 6 | V_MINOR := $(patsubst $(V_MAJOR).%,%,$(VERSION)) 7 | 8 | LIBPLOOP := libploop.a 9 | LIBPLOOP_SO := libploop.so 10 | LIBPLOOP_SO_X := $(LIBPLOOP_SO).$(V_MAJOR) 11 | LIBPLOOP_SO_X_Y := $(LIBPLOOP_SO_X).$(V_MINOR) 12 | 13 | PC=$(NAME).pc 14 | PCDIR=$(LIBDIR)/pkgconfig 15 | 16 | LIBOBJS=uuid.o \ 17 | delta_read.o \ 18 | delta_sysfs.o \ 19 | dm.o \ 20 | balloon_util.o \ 21 | check.o \ 22 | crypt.o \ 23 | defrag.c \ 24 | ploop.o \ 25 | xml.o \ 26 | logger.o \ 27 | balloon.o \ 28 | lock.o \ 29 | fsutils.o \ 30 | gpt.o \ 31 | crc32.o \ 32 | merge.o \ 33 | util.o \ 34 | ploop-copy.o \ 35 | di.o \ 36 | cleanup.o \ 37 | deprecated.o \ 38 | snapshot.o \ 39 | symbols.o \ 40 | cbt.o \ 41 | volume.o \ 42 | qcow.c 43 | 44 | SOURCES=$(LIBOBJS:.o=.c) 45 | GENERATED=symbols.c 46 | 47 | CFLAGS += $(shell pkg-config libxml-2.0 --cflags) -fPIC -fvisibility=hidden 48 | LDFLAGS+= -shared -Wl,-soname,$(LIBPLOOP_SO_X) 49 | LDLIBS += $(shell pkg-config libxml-2.0 openssl uuid --libs) -ldevmapper -lblkid -ljson-c -lpthread -lrt 50 | 51 | all: $(LIBPLOOP) $(LIBPLOOP_SO) $(PC) 52 | .PHONY: all 53 | 54 | $(PC): $(PC).in 55 | $(E) " GEN " $@ 56 | $(Q) sed \ 57 | -e 's|@VERSION@|$(VERSION)|g' \ 58 | -e 's|@LIBDIR@|$(LIBDIR)|g' \ 59 | $^ > $@ 60 | 61 | symbols.c: ../gensym.sh ../include/libploop.h 62 | $(E) " GEN " $@ 63 | $(Q) $^ $@ 64 | 65 | 66 | $(LIBPLOOP): $(LIBOBJS) 67 | $(E) " LINK " $@ 68 | $(Q) $(AR) rcs $@ $+ 69 | $(Q) ranlib $@ 70 | 71 | $(LIBPLOOP_SO_X_Y): $(LIBOBJS) 72 | $(E) $(LDLIBS) 73 | $(E) " LINK " $@ 74 | $(Q) $(CC) $(CFLAGS) $(LDFLAGS) $^ ${LDLIBS} -o $@ 75 | 76 | $(LIBPLOOP_SO_X): $(LIBPLOOP_SO_X_Y) 77 | $(E) " LN_S " $@ 78 | $(Q) ln -sf $^ $@ 79 | 80 | $(LIBPLOOP_SO): $(LIBPLOOP_SO_X) 81 | $(E) " LN_S " $@ 82 | $(Q) ln -sf $^ $@ 83 | 84 | .depend: $(filter-out $(GENERATED),$(SOURCES)) 85 | -include .depend 86 | 87 | install-lockdir: 88 | $(Q) $(INSTALL) -d $(DESTDIR)$(LOCKDIR) 89 | .PHONY: install-lockdir 90 | 91 | install-pc: $(PC) 92 | $(E) " INSTALL " $(PC) 93 | $(Q) $(INSTALL) -d $(DESTDIR)$(PCDIR) 94 | $(Q) $(INSTALL) $(PC) $(DESTDIR)$(PCDIR) 95 | .PHONY: install-pc 96 | 97 | install: all install-lockdir install-pc 98 | $(Q) $(INSTALL) -d $(DESTDIR)$(LIBDIR) 99 | $(E) " INSTALL " $(LIBPLOOP) 100 | $(Q) $(INSTALL) -m 644 $(LIBPLOOP) $(DESTDIR)$(LIBDIR) 101 | $(E) " INSTALL " $(LIBPLOOP_SO_X_Y) 102 | $(Q) $(INSTALL) -m 755 $(LIBPLOOP_SO_X_Y) $(DESTDIR)$(LIBDIR) 103 | $(E) " INSTALL " $(LIBPLOOP_SO_X) $(LIBPLOOP_SO) 104 | $(Q) cp -a $(LIBPLOOP_SO_X) $(LIBPLOOP_SO) $(DESTDIR)$(LIBDIR) 105 | .PHONY: install 106 | 107 | clean: 108 | $(E) " CLEAN " 109 | $(Q) rm -f $(GENERATED) *.o *.a *.so *.so.* .depend 110 | .PHONY: clean 111 | 112 | distclean: clean 113 | $(Q) rm -f .depend 114 | .PHONY: distclean 115 | -------------------------------------------------------------------------------- /lib/bit_ops.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2005-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This file is part of Virtuozzo Core Libraries. Virtuozzo Core 6 | * Libraries is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published 8 | * by the Free Software Foundation; either version 2.1 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library. If not, see 18 | * or write to Free Software Foundation, 19 | * 51 Franklin Street, Fifth Floor Boston, MA 02110, USA. 20 | * 21 | * Our contact details: Virtuozzo IP Holdings GmbH, Vordergasse 59, 8200 22 | * Schaffhausen, Switzerland; http://www.virtuozzo.com/. 23 | * 24 | */ 25 | #ifndef _STD_BITOPS_H_ 26 | #define _STD_BITOPS_H_ 27 | 28 | #include 29 | 30 | // Get __u-aligned size of a bitmap in bytes 31 | #define BMAP_SZ(bits) ((((bits) + 31) >> 5) << 2) 32 | 33 | // Get a bit of a bitmap 34 | static __inline int BMAP_GET(void const* bmap, unsigned int bit) 35 | { 36 | return !!(((unsigned int const*)bmap)[bit >> 5] & (1 << (bit & 31))); 37 | } 38 | 39 | // Set a bit of a bitmap 40 | static __inline void BMAP_SET(void* bmap, unsigned int bit) 41 | { 42 | ((unsigned int*)bmap)[bit >> 5] |= (1 << (bit & 31)); 43 | } 44 | 45 | static __inline void BMAP_SET_BLOCK(void* bmap, unsigned int Start, 46 | unsigned int Size) 47 | { 48 | unsigned int End; 49 | int Len, ByteSize; 50 | 51 | for (End = Start + Size; Start < End && Start & 0x7; Start++) 52 | BMAP_SET(bmap, Start); 53 | // now - Start is byte-aligned 54 | 55 | Len = End - Start; 56 | if (Len <= 0) 57 | return; 58 | 59 | ByteSize = Len & ~0x7; 60 | if (ByteSize) 61 | { 62 | // Avoid using stdlib functions for better compatibility 63 | unsigned char* p = (unsigned char*)bmap + Start / 8; 64 | for (Len = ByteSize / 8; Len != 0; Len--, p++) 65 | *p = 0xFF; 66 | Start += ByteSize; 67 | } 68 | 69 | for (; Start < End; Start++) 70 | BMAP_SET(bmap, Start); 71 | } 72 | 73 | // Clear a bit of a bitmap 74 | static __inline void BMAP_CLR(void* bmap, unsigned int bit) 75 | { 76 | ((unsigned int*)bmap)[bit >> 5] &= ~(1 << (bit & 31)); 77 | } 78 | 79 | static __inline unsigned char* BMAP_COUNT_TBL(void) 80 | { 81 | static unsigned char t[0x100], *pt = 0; 82 | if (!pt) 83 | { 84 | int i; 85 | for (i = 0; i < 0x100; ++i) 86 | { 87 | unsigned char m, cnt = 0; 88 | for (m = 1; m; m <<= 1) 89 | if (m & i) 90 | ++cnt; 91 | t[i] = cnt; 92 | } 93 | pt = t; 94 | } 95 | return pt; 96 | } 97 | 98 | // Count the number of set bits in a bitmap of the given size (in bytes) 99 | static size_t BMAP_COUNT_IN_BYTES(void const* bmap, size_t len) 100 | { 101 | size_t cnt = 0; 102 | unsigned char const* ptr = (unsigned char const*)bmap, *end = ptr + len; 103 | for (; ptr < end; ++ptr) 104 | cnt += BMAP_COUNT_TBL()[*ptr]; 105 | return cnt; 106 | } 107 | 108 | // Count the number of set/cleared bits in a bitmap of the given size (in bits) 109 | #define BMAP_COUNT(bmap, bits) BMAP_COUNT_IN_BYTES(bmap, ((bits) + 7) >> 3) 110 | static __inline size_t BMAP_COUNT_ZERO(void const* bmap, unsigned int bits) 111 | { 112 | return bits - BMAP_COUNT(bmap, bits); 113 | } 114 | 115 | 116 | /** 117 | * @brief 118 | * Find the least significant bit 119 | * 120 | * @param 121 | * original unsigned int 122 | * 123 | * @return 124 | * position of the least significant bit set, 125 | * and -1 if all the bits are 0 126 | */ 127 | static __inline int BitFindLowestSet(unsigned int uVal) 128 | { 129 | int r; 130 | __asm__( 131 | "bsfl %1, %0\n\t" 132 | "jnz 1f\n\t" 133 | "movl $-1, %0\n" 134 | "1:" 135 | : "=r"(r) 136 | : "r"(uVal)); 137 | return r; 138 | } 139 | 140 | /** 141 | * @brief 142 | * Find the most significant bit 143 | * 144 | * @param 145 | * original unsigned int 146 | * 147 | * @return 148 | * position of the most significant bit set, 149 | * and -1 if all bits are 0 150 | */ 151 | static __inline int BitFindHighestSet(unsigned int uVal) 152 | { 153 | int r; 154 | __asm__( 155 | "bsrl %1, %0\n\t" 156 | "jnz 1f\n\t" 157 | "movl $-1, %0\n" 158 | "1:" 159 | : "=r"(r) 160 | : "r"(uVal)); 161 | return r; 162 | } 163 | 164 | /** 165 | * @brief 166 | * Find the least bit is set in uVal 167 | * 168 | * @param 169 | * 170 | * 171 | * @return 172 | * position of the least significant bit set, 173 | * and -1 if all the bits are 0 174 | */ 175 | static __inline int BitFindLowestSet64(__u64 uVal) 176 | { 177 | return __builtin_ffsll(uVal) - 1; 178 | } 179 | 180 | /** 181 | * @brief 182 | * Find the least bit is clear in uVal 183 | * 184 | * @param 185 | * 186 | * 187 | * @return 188 | * position of the least significant bit clear, 189 | * and -1 if all the bits are 1 190 | */ 191 | static __inline int BitFindLowestClear64(__u64 uVal) 192 | { 193 | return BitFindLowestSet64(~uVal); 194 | } 195 | 196 | #define BITS_PER___u64 (sizeof(__u64) << 3) 197 | #define BIT_MASK___u64 ((__u64)0x3F) 198 | #define BIT2POS64(pos) ((pos) / BITS_PER___u64) 199 | #define BIT_ALL_SET64 (~(__u64)0) 200 | #define BMAP_SZ64(bits) ((((bits) + 63) >> 6) << 3) 201 | 202 | /** 203 | * @brief 204 | * Find the least significant bit in a bitmap 205 | * 206 | * @param bmap pointer to a bit array 207 | * @param size size of a bitmap (in bits!) 208 | * @param off position to start from (in bits!) 209 | * 210 | * @return 211 | * position of the least significant bit set, 212 | * and -1 if all bits are 0 213 | */ 214 | static __inline __s64 BitFindNextSet64(__u64 const* bmap, 215 | __u32 size, 216 | __u32 off) 217 | { 218 | __u64 const* p = bmap + BIT2POS64(off); 219 | __u32 pos = off & ~BIT_MASK___u64; 220 | __u64 val; 221 | 222 | if (NULL == bmap) 223 | return -2; 224 | if (off >= size) 225 | return -1; 226 | 227 | size -= pos; 228 | off &= BIT_MASK___u64; 229 | if (off != 0) { 230 | val = *(p++); 231 | val &= (BIT_ALL_SET64 << off); 232 | if (size < BITS_PER___u64) 233 | goto check_tail; 234 | if (val != 0) 235 | goto found; 236 | size -= BITS_PER___u64; 237 | pos += BITS_PER___u64; 238 | } 239 | 240 | while (size & ~BIT_MASK___u64) { 241 | val = *(p++); 242 | if (val != 0) 243 | goto found; 244 | pos += BITS_PER___u64; 245 | size -= BITS_PER___u64; 246 | } 247 | 248 | if (!size) 249 | return -1; 250 | val = *p; 251 | 252 | check_tail: 253 | val &= BIT_ALL_SET64 >> (BITS_PER___u64 - size); 254 | if (val == 0) 255 | return -1; // no bits set 256 | 257 | found: 258 | // do not care about -1, at least one bit is always set here 259 | return pos + BitFindLowestSet64(val); 260 | } 261 | 262 | /** 263 | * @brief 264 | * Find the least bit is clear in a bitmap 265 | * 266 | * @param bmap pointer to a bit array 267 | * @param size size of a bitmap (in bits!) 268 | * @param off position to start from (in bits!) 269 | * 270 | * @return 271 | * position of the least bit is clear, 272 | * and -1 if all bits are 1 273 | */ 274 | static __inline __s64 BitFindNextClear64(__u64 const* bmap, 275 | __u32 size, 276 | __u32 off) 277 | { 278 | __u64 const* p = bmap + BIT2POS64(off); 279 | __u32 pos = off & ~BIT_MASK___u64; 280 | __u64 val; 281 | 282 | if (NULL == bmap) 283 | return -1; 284 | if (off >= size) 285 | return -1; 286 | 287 | size -= pos; 288 | off &= BIT_MASK___u64; 289 | if (off != 0) { 290 | val = *(p++); 291 | val |= BIT_ALL_SET64 >> (BITS_PER___u64 - off); // set all bits before desired 292 | if (size < BITS_PER___u64) 293 | goto check_tail; 294 | if (val != BIT_ALL_SET64) 295 | goto found; 296 | size -= BITS_PER___u64; 297 | pos += BITS_PER___u64; 298 | } 299 | 300 | while (size & ~BIT_MASK___u64) { 301 | val = *(p++); 302 | if (val != BIT_ALL_SET64) 303 | goto found; 304 | pos += BITS_PER___u64; 305 | size -= BITS_PER___u64; 306 | } 307 | 308 | if (!size) 309 | return -1; 310 | 311 | val = *p; 312 | 313 | check_tail: 314 | val |= BIT_ALL_SET64 << size; 315 | if (BIT_ALL_SET64 == val) 316 | return -1; // no bits cleared 317 | 318 | found: 319 | return pos + BitFindLowestClear64(val); 320 | } 321 | 322 | /* 323 | * Swap bytes in the 16 bit value 324 | * 325 | * @param 16 bit value to swap 326 | * 327 | * @return Swapped 16 bit value 328 | */ 329 | #undef bswap_16 330 | static __inline __u16 bswap_16(__u16 x) { 331 | __u32 r = x; 332 | __asm__ __volatile__ ( 333 | "xchg %%ah, %%al\n\t" 334 | : "=a"(r) : "0"(r)); 335 | return r; 336 | } 337 | 338 | /* 339 | * Swap bytes in the 32 bit value 340 | * 341 | * @param 32 bit value to swap 342 | * 343 | * @return Swapped 32 bit value 344 | */ 345 | #undef bswap_32 346 | static __inline __u32 bswap_32(__u32 u) { 347 | __asm__ __volatile__ ( 348 | "bswap %%eax\n\t" 349 | : "=a"(u) : "0"(u)); 350 | return u; 351 | } 352 | 353 | /* 354 | * Swap bytes in the 64 bit value 355 | * 356 | * @param 64 bit value to swap 357 | * 358 | * @return Swapped 64 bit value 359 | */ 360 | #undef bswap_64 361 | static __inline __u64 bswap_64(__u64 x) { 362 | return (((__u64)bswap_32((__u32)(x&0xffffffffull)))<<32) | 363 | (bswap_32((__u32)(x>>32))); 364 | } 365 | 366 | #endif 367 | -------------------------------------------------------------------------------- /lib/cbt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2005-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This file is part of Virtuozzo Core Libraries. Virtuozzo Core 6 | * Libraries is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published 8 | * by the Free Software Foundation; either version 2.1 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library. If not, see 18 | * or write to Free Software Foundation, 19 | * 51 Franklin Street, Fifth Floor Boston, MA 02110, USA. 20 | * 21 | * Our contact details: Virtuozzo IP Holdings GmbH, Vordergasse 59, 8200 22 | * Schaffhausen, Switzerland; http://www.virtuozzo.com/. 23 | */ 24 | 25 | #ifndef CBT_H 26 | #define CBT_H 27 | 28 | #define CBT_DEFAULT_BLKSIZE 65536 29 | 30 | struct ext_context; 31 | 32 | void free_ext_context(struct ext_context *ctx); 33 | struct ext_context *create_ext_context(void); 34 | int read_optional_header_from_image(struct ext_context *ctx, 35 | const char *img_name, int flags); 36 | int write_empty_cbt_to_image(const char *fname, const char *prev_fname, 37 | const __u8 *cbt_u); 38 | int delta_save_optional_header(int devfd, struct delta *delta, 39 | void *or_data, struct ploop_pvd_dirty_bitmap_raw *raw); 40 | int send_dirty_bitmap_to_kernel(struct ext_context *ctx, const char *devname, 41 | const char *img_name); 42 | int save_dirty_bitmap(int devfd, struct delta *delta, off_t offcet, void *buf, 43 | __u32 *size, void *or_data, writer_fn wr, void *data); 44 | int cbt_start(int devfd, const __u8 *uuid, __u32 blksize); 45 | int cbt_stop(int devfd); 46 | int cbt_get_dirty_bitmap_metadata(int devfd, __u8 *uuid, __u32 *blksize); 47 | int cbt_get_and_clear(int devfd, void **data); 48 | int cbt_get(int devfd, writer_fn wr, void *data); 49 | int cbt_put(int devfd, void *data, size_t size, off_t pos); 50 | int cbt_set_uuid(int devfd, const __u8 *uuid); 51 | int cbt_snapshot_prepare(int lfd, const unsigned char *cbt_uuid, 52 | void **or_data); 53 | int cbt_snapshot(int lfd, const unsigned char *cbt_uuid, const char *prev_delta, 54 | void *or_data); 55 | int cbt_dump(struct ploop_disk_images_data *di, const char *dev, 56 | const char *fname); 57 | PL_EXT int ploop_move_cbt(const char *dst, const char *src); 58 | PL_EXT int ploop_cbt_dump_info_from_image(const char *image); 59 | PL_EXT int ploop_cbt_dump_info(struct ploop_disk_images_data *di); 60 | PL_EXT int ploop_dump_cbt(struct ploop_disk_images_data *di, const char *fname); 61 | 62 | 63 | #endif /* CBT_H */ 64 | -------------------------------------------------------------------------------- /lib/cleanup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "list.h" 25 | #include "cleanup.h" 26 | #include "ploop.h" 27 | struct ploop_cancel_handle 28 | { 29 | list_head_t head; 30 | int flags; 31 | }; 32 | 33 | struct ploop_cleanup_hook { 34 | list_elem_t list; 35 | cleanup_FN fn; 36 | void *data; 37 | }; 38 | 39 | static __thread struct ploop_cancel_handle __cancel_data; 40 | 41 | /* set cancel flag 42 | * * Note: this function also clear the flag 43 | * */ 44 | int is_operation_cancelled(void) 45 | { 46 | struct ploop_cancel_handle *cancel_data; 47 | 48 | cancel_data = ploop_get_cancel_handle(); 49 | if (cancel_data->flags) { 50 | cancel_data->flags = 0; 51 | return 1; 52 | } 53 | return 0; 54 | } 55 | 56 | struct ploop_cancel_handle *ploop_get_cancel_handle(void) 57 | { 58 | return &__cancel_data; 59 | } 60 | 61 | struct ploop_cleanup_hook *register_cleanup_hook(cleanup_FN fn, void *data) 62 | { 63 | struct ploop_cleanup_hook *h; 64 | struct ploop_cancel_handle *handle = ploop_get_cancel_handle(); 65 | list_head_t *head = &handle->head; 66 | 67 | if (head->next == NULL) 68 | list_head_init(head); 69 | 70 | h = malloc(sizeof(struct ploop_cleanup_hook)); 71 | if (h == NULL) 72 | return NULL; 73 | h->fn = fn; 74 | h->data = data; 75 | list_add(&h->list, head); 76 | 77 | return h; 78 | } 79 | 80 | void unregister_cleanup_hook(struct ploop_cleanup_hook *h) 81 | { 82 | if (h != NULL) { 83 | list_del(&h->list); 84 | free(h); 85 | } 86 | } 87 | 88 | void ploop_cancel_operation(void) 89 | { 90 | struct ploop_cleanup_hook *it; 91 | struct ploop_cancel_handle *handle = ploop_get_cancel_handle(); 92 | list_head_t *head = &handle->head; 93 | 94 | handle->flags = 1; 95 | 96 | if (head->next != NULL) { 97 | list_for_each(it, head, list) { 98 | it->fn(it->data); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/cleanup.h: -------------------------------------------------------------------------------- 1 | #ifndef __CLEANUP_H__ 2 | #define __CLEANUP_H__ 3 | 4 | struct ploop_cleanup_hook; 5 | struct ploop_cancel_handle; 6 | 7 | typedef void (* cleanup_FN) (void *data); 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | int is_operation_cancelled(void); 13 | struct ploop_cancel_handle *ploop_get_cancel_handle(void); 14 | struct ploop_cleanup_hook *register_cleanup_hook(cleanup_FN f, void *data); 15 | void unregister_cleanup_hook(struct ploop_cleanup_hook *h); 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | #endif 20 | -------------------------------------------------------------------------------- /lib/crc32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | static const __u32 crc32map[] = { 24 | 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 25 | 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, 26 | 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 27 | 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 28 | 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 29 | 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 30 | 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 31 | 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, 32 | 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 33 | 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 34 | 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 35 | 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 36 | 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, 37 | 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 38 | 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 39 | 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, 40 | 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 41 | 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, 42 | 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 43 | 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 44 | 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, 45 | 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 46 | 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 47 | 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, 48 | 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 49 | 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, 50 | 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 51 | 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 52 | 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, 53 | 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 54 | 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 55 | 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, 56 | 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 57 | 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 58 | 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 59 | 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 60 | 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, 61 | 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 62 | 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 63 | 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, 64 | 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 65 | 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 66 | 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 67 | 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 68 | 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, 69 | 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 70 | 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 71 | 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 72 | 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 73 | 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, 74 | 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 75 | 0x2d02ef8dL 76 | }; 77 | 78 | __u32 ploop_crc32(const unsigned char *buf, unsigned long len) 79 | { 80 | __u32 crc = 0xFFFFFFFFUL; 81 | 82 | while (len--) 83 | crc = crc32map[(crc ^ *buf++) & 0xff] ^(crc >> 8); 84 | return crc ^ 0xFFFFFFFFUL; 85 | } 86 | -------------------------------------------------------------------------------- /lib/crypt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2005-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "libploop.h" 31 | #include "ploop.h" 32 | 33 | #define CRYPT_BIN "/usr/libexec/ploop/crypthelper" 34 | #define CRYPT_PREFIX "CRYPT-" 35 | 36 | const char *get_basename(const char *path) 37 | { 38 | char *x = strrchr(path, '/'); 39 | 40 | return x ? ++x : path; 41 | } 42 | 43 | const char *crypt_get_device_name(const char *part, char *out, int len) 44 | { 45 | snprintf(out, len, "/dev/mapper/"CRYPT_PREFIX"%s", get_basename(part)); 46 | 47 | return out; 48 | } 49 | 50 | int get_crypt_layout(const char *devname, const char *partname) 51 | { 52 | if (strstr(devname, CRYPT_PREFIX)) 53 | return CRYPT_V2; 54 | if (strstr(partname, CRYPT_PREFIX)) 55 | return CRYPT_V1; 56 | return CRYPT_NONE; 57 | } 58 | 59 | static char **get_param(const char *devname, const char *partname, 60 | const char *keyid) 61 | { 62 | char x[256]; 63 | int i = 0; 64 | char **env = (char **)malloc(sizeof(char *)* 4); 65 | 66 | if (env == NULL) 67 | return NULL; 68 | 69 | if (devname) { 70 | snprintf(x, sizeof(x), "DEVICE=%s", devname); 71 | env[i++] = strdup(x); 72 | } 73 | 74 | if (partname) { 75 | snprintf(x, sizeof(x), "DEVICE_NAME=%s", partname); 76 | env[i++] = strdup(x); 77 | } 78 | if (keyid) { 79 | snprintf(x, sizeof(x), "KEYID=%s", keyid); 80 | env[i++] = strdup(x); 81 | } 82 | env[i] = NULL; 83 | 84 | return env; 85 | 86 | } 87 | 88 | static int do_crypt(const char *action, const char *devname, 89 | const char *partname, const char *keyid) 90 | { 91 | int ret = 0, rc = 0; 92 | char *const arg[] = {CRYPT_BIN,(char *) action, NULL}; 93 | char **env = get_param(devname, partname, keyid); 94 | 95 | ploop_log(0, "Crypt %s dev=%s part=%s", 96 | action, devname ?: "", partname ?: ""); 97 | if (run_prg_rc(arg, env, 0, &rc)) 98 | ret = SYSEXIT_CRYPT; 99 | 100 | if (rc) { 101 | ploop_err(0, "Command %s %s exited with code %d", 102 | arg[0], arg[1], rc); 103 | ret = SYSEXIT_CRYPT; 104 | } 105 | ploop_free_array(env); 106 | 107 | return ret; 108 | } 109 | 110 | int crypt_init(const char *device, const char *keyid) 111 | { 112 | return do_crypt("init", device, NULL, keyid); 113 | } 114 | 115 | int crypt_open(const char *device, const char *keyid) 116 | { 117 | int rc, luks; 118 | char cryptdev[64]; 119 | char name[64]; 120 | 121 | rc = is_luks(device, &luks); 122 | if (rc) 123 | return rc; 124 | 125 | snprintf(cryptdev, sizeof(cryptdev), "%s", device); 126 | if (!luks) { 127 | int gpt; 128 | 129 | rc = has_partition(device, &gpt); 130 | if (rc) 131 | return rc; 132 | 133 | if (gpt) 134 | snprintf(cryptdev, sizeof(cryptdev), "%sp1", device); 135 | } 136 | 137 | crypt_get_device_name(cryptdev, name, sizeof(name)); 138 | rc = do_crypt("open", cryptdev, get_basename(name), keyid); 139 | if (rc) 140 | return rc; 141 | 142 | if (luks) 143 | reread_part(name); 144 | 145 | return 0; 146 | } 147 | 148 | static int crypt_close_v1(const char *devname) 149 | { 150 | return do_crypt("close", NULL, devname, NULL); 151 | } 152 | 153 | static int crypt_close_v2(const char *devname, const char *partname) 154 | { 155 | int ret = dm_remove(partname, PLOOP_UMOUNT_TIMEOUT); 156 | if (ret) 157 | return ret; 158 | 159 | return do_crypt("close", NULL, devname, NULL); 160 | } 161 | 162 | int crypt_close(const char *devname, const char *partname) 163 | { 164 | if (get_crypt_layout(devname, partname) == CRYPT_V2) 165 | return crypt_close_v2(devname, partname); 166 | 167 | return crypt_close_v1(partname); 168 | } 169 | 170 | int crypt_resize(const char *part) 171 | { 172 | return do_crypt("resize", NULL, part, NULL); 173 | } 174 | 175 | int crypt_changekey(const char *device, const char *keyid, 176 | const char *new_keyid) 177 | { 178 | return do_crypt("changekey", device, new_keyid, keyid); 179 | } 180 | 181 | static int do_copy(char *src, char *dst) 182 | { 183 | char s[PATH_MAX]; 184 | char *arg[] = {"rsync", "-a", "--acls", "--xattrs", "--hard-links", 185 | s, dst, NULL}; 186 | 187 | snprintf(s, sizeof(s), "%s/", src); 188 | 189 | return run_prg(arg); 190 | } 191 | 192 | static int encrypt_image(struct ploop_disk_images_data *di, 193 | struct ploop_encrypt_param *param) 194 | { 195 | int ret; 196 | char dir[PATH_MAX]; 197 | char ddxml[PATH_MAX]; 198 | char image[PATH_MAX]; 199 | char bak[PATH_MAX] = ""; 200 | const char *keyid = param->keyid && param->keyid[0] != '\0' ? 201 | param->keyid : NULL; 202 | struct ploop_mount_param m = { 203 | .mount_data = (char *)param->mnt_opts, 204 | }; 205 | struct ploop_mount_param m_enc = { 206 | .mount_data = (char *)param->mnt_opts, 207 | }; 208 | struct ploop_disk_images_data *di_enc = NULL; 209 | struct ploop_create_param c_enc = { 210 | .size = di->size, 211 | .image = image, 212 | .blocksize = di->blocksize, 213 | .keyid = keyid, 214 | .fstype = "", 215 | }; 216 | int wipe = param->flags & PLOOP_ENC_WIPE; 217 | 218 | 219 | if (ploop_is_mounted(di)) { 220 | ploop_err(0, "Encryption of mounted disk image is prohibited"); 221 | return SYSEXIT_PARAM; 222 | } 223 | 224 | ploop_log(0, "Encrypt ploop image %s", di->images[0]->file); 225 | get_basedir(di->images[0]->file, dir, sizeof(dir) - 4); 226 | strcat(dir, "enc"); 227 | if (mkdir(dir, 0755 ) && errno != EEXIST) { 228 | ploop_err(errno, "mkdir %s", dir); 229 | ploop_unlock_dd(di); 230 | return SYSEXIT_MKDIR; 231 | } 232 | 233 | snprintf(image, sizeof(image), "%s/%s", dir, 234 | get_basename(di->images[0]->file)); 235 | ret = ploop_create_image(&c_enc); 236 | if (ret) 237 | goto err; 238 | 239 | snprintf(ddxml, sizeof(ddxml), "%s/" DISKDESCRIPTOR_XML, dir); 240 | ret = ploop_open_dd(&di_enc, ddxml); 241 | if (ret) 242 | goto err; 243 | 244 | ret = ploop_read_dd(di_enc); 245 | if (ret) 246 | goto err; 247 | 248 | ret = auto_mount_image(di_enc, &m_enc); 249 | if (ret) 250 | goto err; 251 | 252 | ret = auto_mount_image(di, &m); 253 | if (ret) 254 | goto err; 255 | 256 | ret = do_copy(m.target, m_enc.target); 257 | if (ret) 258 | goto err; 259 | 260 | ret = ploop_umount(m.device, di); 261 | if (ret) 262 | goto err; 263 | 264 | ret = ploop_umount(m_enc.device, di_enc); 265 | if (ret) 266 | goto err; 267 | 268 | if (wipe && di->enc == NULL && keyid != NULL) { 269 | snprintf(bak, sizeof(bak), "%s.orig", di->images[0]->file); 270 | if (rename(di->images[0]->file, bak)) { 271 | ploop_err(errno, "Can't rename %s to %s", 272 | di->images[0]->file, bak); 273 | ret = SYSEXIT_RENAME; 274 | goto err; 275 | } 276 | } 277 | 278 | if (rename(image, di->images[0]->file)) { 279 | ploop_err(errno, "Can't rename %s to %s", 280 | image, di->images[0]->file); 281 | ret = SYSEXIT_RENAME; 282 | goto err; 283 | } 284 | 285 | if (rename(ddxml, di->runtime->xml_fname)) { 286 | ploop_err(errno, "Can't rename %s to %s", 287 | ddxml, di->runtime->xml_fname); 288 | ret = SYSEXIT_RENAME; 289 | goto err; 290 | } 291 | 292 | if (bak[0] != '\0') { 293 | char *cmd[] = {"shred", "-n1", bak, NULL}; 294 | run_prg(cmd); 295 | if (unlink(bak)) 296 | ploop_err(errno, "Can't unlink %s", bak); 297 | } 298 | 299 | ploop_log(0, "Ploop image %s has been successfully encrypted", 300 | di->images[0]->file); 301 | 302 | free_mount_param(&m); 303 | free_mount_param(&m_enc); 304 | if (di_enc) 305 | ploop_close_dd(di_enc); 306 | 307 | ploop_unlock_dd(di); 308 | 309 | return 0; 310 | 311 | err: 312 | if (m.device[0] != '\0') 313 | ploop_umount(m.device, di); 314 | if (m_enc.device[0] != '\0') 315 | ploop_umount(m_enc.device, di_enc); 316 | 317 | if (wipe && di->enc != NULL && keyid == NULL ) { 318 | char *cmd[] = {"shred", "-n1", image, NULL}; 319 | run_prg(cmd); 320 | } 321 | 322 | char *cmd[] = {"rm", "-rf", dir, NULL}; 323 | run_prg(cmd); 324 | 325 | free_mount_param(&m); 326 | free_mount_param(&m_enc); 327 | if (di_enc) 328 | ploop_close_dd(di_enc); 329 | 330 | 331 | return ret; 332 | } 333 | 334 | static int change_key(struct ploop_disk_images_data *di, 335 | struct ploop_encrypt_param *param) 336 | { 337 | int ret, rc; 338 | char ddxml[PATH_MAX]; 339 | char tmp[PATH_MAX]; 340 | char devname[64]; 341 | int was_mounted = 0; 342 | struct ploop_mount_param m = {}; 343 | char *keyid; 344 | 345 | if (di->enc == NULL || param->keyid[0] == '\0') 346 | return encrypt_image(di, param); 347 | 348 | keyid = strdupa(di->enc->keyid); 349 | 350 | ploop_log(0, "Change encryption key %s -> %s", 351 | keyid, param->keyid); 352 | rc = ploop_find_dev_by_dd(di, m.device, sizeof(m.device)); 353 | if (rc == -1) { 354 | ret = SYSEXIT_SYS; 355 | } else if (rc != 0) { 356 | ret = ploop_mount(di, NULL, &m, (di->mode == PLOOP_RAW_MODE)); 357 | if (ret) 358 | return ret; 359 | was_mounted = 1; 360 | } 361 | 362 | ret = get_partition_device_name(m.device, devname, sizeof(devname)); 363 | if (ret) 364 | goto err; 365 | 366 | get_disk_descriptor_fname(di, ddxml, sizeof(ddxml)); 367 | snprintf(tmp, sizeof(tmp), "%s.tmp", ddxml); 368 | ret = set_encryption_keyid(di, param->keyid); 369 | if (ret) 370 | goto err; 371 | ret = ploop_store_diskdescriptor(tmp, di); 372 | if (ret) 373 | goto err; 374 | 375 | ret = crypt_changekey(devname, keyid, param->keyid); 376 | if (ret) 377 | goto err; 378 | 379 | if (rename(tmp, ddxml)) { 380 | ploop_err(errno, "Cannot rename %s -> %s", tmp, ddxml); 381 | ret = SYSEXIT_RENAME; 382 | goto err; 383 | } 384 | 385 | ret = 0; 386 | err: 387 | if (was_mounted) 388 | ploop_umount(m.device, di); 389 | unlink(tmp); 390 | 391 | return ret; 392 | } 393 | 394 | int ploop_encrypt_image(struct ploop_disk_images_data *di, 395 | struct ploop_encrypt_param *param) 396 | { 397 | int ret; 398 | 399 | if (param->keyid == NULL) { 400 | ploop_err(9, "Keyid is not specified"); 401 | return SYSEXIT_PARAM; 402 | } 403 | 404 | if (ploop_lock_dd(di)) 405 | return SYSEXIT_LOCK; 406 | 407 | if (di->images == NULL) { 408 | ploop_unlock_dd(di); 409 | return SYSEXIT_PARAM; 410 | } 411 | 412 | if (param->flags & PLOOP_ENC_REENCRYPT) 413 | ret = encrypt_image(di, param); 414 | else 415 | ret = change_key(di, param); 416 | 417 | ploop_unlock_dd(di); 418 | return ret; 419 | } 420 | -------------------------------------------------------------------------------- /lib/defrag.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Virtuozzo International GmbH. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "bit_ops.h" 35 | #include "ploop.h" 36 | 37 | #ifndef copy_file_range 38 | ssize_t copy_file_range(int fd_in, loff_t *off_in, int fd_out, 39 | loff_t *off_out, size_t len, unsigned int flags) 40 | { 41 | return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, 42 | off_out, len, flags); 43 | } 44 | #endif 45 | 46 | static int update_bat(struct delta *delta, __u32 clu, __u32 old, __u32 new) 47 | { 48 | off_t off = sizeof(struct ploop_pvd_header) + (clu * sizeof(__u32)); 49 | 50 | ploop_log(0, "Update BAT cluster: %d off: %lu %d->%d", 51 | clu, off, old, new); 52 | new <<= ploop_fmt_log(delta->version); 53 | return write_safe(delta->fd, &new, sizeof(new), off, 54 | "Cannot update BAT"); 55 | } 56 | 57 | static int reallocate_cluster(struct delta *delta, __u32 clu, 58 | __u32 src, __u32 dst) 59 | { 60 | int rc; 61 | off_t s, d; 62 | __u32 cluster = S2B(delta->blocksize); 63 | int len = cluster; 64 | 65 | s = (off_t)src * cluster; 66 | d = (off_t)dst * cluster; 67 | 68 | ploop_log(0, "Reallocate cluster #%d data from %u/off: %lu to %u/off: %lu", 69 | clu, src, s, dst, d); 70 | while (len) { 71 | int r = copy_file_range(delta->fd, &s, delta->fd, &d, len, 0); 72 | if (r <= 0) { 73 | ploop_err(errno, "copy_file_range"); 74 | return -1; 75 | } 76 | len -= r; 77 | s += r; 78 | d += r; 79 | } 80 | 81 | rc = update_bat(delta, clu, src, dst); 82 | if (rc) 83 | return rc; 84 | 85 | return fsync_safe(delta->fd); 86 | } 87 | 88 | static int do_defrag(struct delta *delta,__u64 *hole_bitmap, 89 | int hole_bitmap_size, int nr_clusters) 90 | 91 | { 92 | unsigned int i, rc, dst = 0, n = 0, log; 93 | __u32 cluster, off; 94 | struct ploop_pvd_header *hdr = (struct ploop_pvd_header *) delta->hdr0; 95 | 96 | log = ploop_fmt_log(delta->version); 97 | cluster = S2B(delta->blocksize); 98 | 99 | for (i = 0; i < hdr->m_Size; i++) { 100 | int l2_cluster = (i + PLOOP_MAP_OFFSET) / (cluster / sizeof(__u32)); 101 | __u32 l2_slot = (i + PLOOP_MAP_OFFSET) % (cluster / sizeof(__u32)); 102 | if (delta->l2_cache != l2_cluster) { 103 | if (PREAD(delta, delta->l2, cluster, (off_t)l2_cluster * cluster)) 104 | return -1; 105 | delta->l2_cache = l2_cluster; 106 | } 107 | 108 | off = delta->l2[l2_slot] >> log; 109 | if (off == 0) 110 | continue; 111 | if (off < hole_bitmap_size) 112 | continue; 113 | if (off < nr_clusters) 114 | continue; 115 | dst = BitFindNextSet64(hole_bitmap, hole_bitmap_size, dst); 116 | if (dst == -1) { 117 | ploop_log(0, "No free clusters found"); 118 | break; 119 | } 120 | if (dst > off) 121 | continue; 122 | rc = reallocate_cluster(delta, i, off, dst); 123 | if (rc) 124 | return rc; 125 | 126 | dst++; 127 | n++; 128 | } 129 | 130 | if (n) 131 | ploop_log(0, "cluster defragmentation: total: %d allocated: %d reallocated: %d", 132 | hdr->m_Size, nr_clusters, n); 133 | 134 | return 0; 135 | } 136 | 137 | int build_hole_bitmap(struct delta *delta, __u64 **hole_bitmap, 138 | __u32 *hole_bitmap_size, int *nr_clusters) 139 | { 140 | int log, nr_clu_in_bat; 141 | __u32 clu, cluster, off; 142 | __u64 size; 143 | 144 | nr_clu_in_bat = delta->l1_size; 145 | *hole_bitmap_size = delta->l1_size + delta->l2_size; 146 | size = (*hole_bitmap_size + 7) / 8; /* round up byte */ 147 | size = (size + sizeof(unsigned long)-1) & ~(sizeof(unsigned long)-1); 148 | if (p_memalign((void *)hole_bitmap, sizeof(__u64), size)) 149 | return SYSEXIT_MALLOC; 150 | memset(*hole_bitmap, 0xff, size); 151 | 152 | log = ploop_fmt_log(delta->version); 153 | cluster = S2B(delta->blocksize); 154 | 155 | for (clu = 0; clu < nr_clu_in_bat; clu++) 156 | BMAP_CLR(*hole_bitmap, clu); 157 | 158 | for (clu = 0; clu < delta->l2_size; clu++) { 159 | int l2_cluster = (clu + PLOOP_MAP_OFFSET) / (cluster / sizeof(__u32)); 160 | __u32 l2_slot = (clu + PLOOP_MAP_OFFSET) % (cluster / sizeof(__u32)); 161 | if (delta->l2_cache != l2_cluster) { 162 | if (PREAD(delta, delta->l2, cluster, (off_t)l2_cluster * cluster)) 163 | return SYSEXIT_READ; 164 | delta->l2_cache = l2_cluster; 165 | } 166 | 167 | if (delta->l2[l2_slot] == 0) 168 | continue; 169 | 170 | off = delta->l2[l2_slot] >> log; 171 | if (off < *hole_bitmap_size) { 172 | BMAP_CLR(*hole_bitmap, off); 173 | *nr_clusters += 1; 174 | } else 175 | ploop_err(0, "Cluster %d[%d] allocated outside devce %d", 176 | clu, off, *hole_bitmap_size); 177 | } 178 | delta->l2_cache = -1; 179 | 180 | return 0; 181 | } 182 | 183 | int build_alloc_bitmap(struct delta *delta, __u64 **bitmap, 184 | __u32 *bitmap_size, int *nr_clusters) 185 | { 186 | __u32 clu, cluster, off; 187 | __u64 size; 188 | 189 | *bitmap_size = delta->l1_size + delta->l2_size; 190 | size = (*bitmap_size + 7) / 8; /* round up byte */ 191 | size = (size + sizeof(unsigned long)-1) & ~(sizeof(unsigned long)-1); 192 | if (p_memalign((void *)bitmap, sizeof(__u64), size)) 193 | return SYSEXIT_MALLOC; 194 | memset(*bitmap, 0, size); 195 | 196 | cluster = S2B(delta->blocksize); 197 | 198 | for (clu = 0; clu < delta->l2_size; clu++) { 199 | int l2_cluster = (clu + PLOOP_MAP_OFFSET) / (cluster / sizeof(__u32)); 200 | __u32 l2_slot = (clu + PLOOP_MAP_OFFSET) % (cluster / sizeof(__u32)); 201 | if (delta->l2_cache != l2_cluster) { 202 | if (PREAD(delta, delta->l2, cluster, (off_t)l2_cluster * cluster)) 203 | return SYSEXIT_READ; 204 | delta->l2_cache = l2_cluster; 205 | } 206 | 207 | if (delta->l2[l2_slot] == 0) 208 | continue; 209 | 210 | off = clu; 211 | ploop_log(0, "[%u]->%u %u", l2_slot, delta->l2[l2_slot], off); 212 | if (off < *bitmap_size) { 213 | BMAP_SET(*bitmap, off); 214 | *nr_clusters += 1; 215 | } else 216 | ploop_err(0, "Cluster %d[%d] allocated outside devce %d", 217 | clu, off, *bitmap_size); 218 | } 219 | delta->l2_cache = -1; 220 | 221 | return 0; 222 | } 223 | 224 | int image_defrag(struct delta *delta) 225 | { 226 | int rc = 0, nr_clusters; 227 | __u32 hole_bitmap_size; 228 | __u64 *hole_bitmap; 229 | 230 | rc = build_hole_bitmap(delta, &hole_bitmap, 231 | &hole_bitmap_size, &nr_clusters); 232 | if (rc || nr_clusters == 0) 233 | goto err; 234 | rc = do_defrag(delta, hole_bitmap, hole_bitmap_size, nr_clusters); 235 | err: 236 | free(hole_bitmap); 237 | 238 | return rc; 239 | } 240 | 241 | int ploop_image_defrag(const char *image, int flags) 242 | { 243 | int rc; 244 | struct delta d = {}; 245 | 246 | rc = open_delta(&d, image, O_RDWR, OD_ALLOW_DIRTY); 247 | if (rc) 248 | return rc; 249 | 250 | rc = image_defrag(&d); 251 | close_delta(&d); 252 | 253 | return rc; 254 | } 255 | 256 | int ploop_image_shuffle(const char *image, int nr, int flags) 257 | { 258 | int rc, nr_clusters, i, n = 0, log; 259 | __u32 hole_bitmap_size, dst, cluster, off; 260 | __u64 *hole_bitmap; 261 | struct delta d = {}; 262 | struct ploop_pvd_header *hdr; 263 | 264 | rc = open_delta(&d, image, O_RDWR, OD_ALLOW_DIRTY); 265 | if (rc) 266 | return rc; 267 | 268 | rc = build_hole_bitmap(&d, &hole_bitmap, &hole_bitmap_size, &nr_clusters); 269 | if (rc || nr_clusters == 0) 270 | goto err; 271 | 272 | hdr = (struct ploop_pvd_header *) d.hdr0; 273 | ploop_log(0, "Image %s clusters: %d total: %d", 274 | image, nr_clusters, hdr->m_Size); 275 | dst = MAX((d.l2_size + d.l1_size), d.alloc_head); 276 | log = ploop_fmt_log(d.version); 277 | cluster = S2B(d.blocksize); 278 | for (i = 0; i < hdr->m_Size; i++) { 279 | int l2_cluster = (i + PLOOP_MAP_OFFSET) / (cluster / sizeof(__u32)); 280 | __u32 l2_slot = (i + PLOOP_MAP_OFFSET) % (cluster / sizeof(__u32)); 281 | if (d.l2_cache != l2_cluster) { 282 | if (PREAD(&d, d.l2, cluster, (off_t)l2_cluster * cluster)) 283 | return -1; 284 | d.l2_cache = l2_cluster; 285 | } 286 | 287 | off = d.l2[l2_slot] >> log; 288 | if (off == 0) 289 | continue; 290 | rc = reallocate_cluster(&d, i, off, ++dst); 291 | if (rc) 292 | return rc; 293 | if (n++ >= nr) 294 | break; 295 | } 296 | 297 | err: 298 | free(hole_bitmap); 299 | close_delta(&d); 300 | 301 | return rc; 302 | } 303 | -------------------------------------------------------------------------------- /lib/deprecated.c: -------------------------------------------------------------------------------- 1 | #include "libploop.h" 2 | #include "ploop.h" 3 | 4 | #define DEPR_ERROR \ 5 | ploop_err(0, "%s is deprecated, DO NOT USE!", __func__) 6 | 7 | char *ploop_get_base_delta_uuid(struct ploop_disk_images_data *di) { 8 | DEPR_ERROR; 9 | 10 | return NULL; 11 | } 12 | -------------------------------------------------------------------------------- /lib/list.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIST_H__ 2 | #define __LIST_H__ 3 | #include "stdlib.h" 4 | 5 | struct list_elem; 6 | typedef struct { 7 | struct list_elem *prev, *next; 8 | } list_head_t; 9 | 10 | typedef struct list_elem { 11 | struct list_elem *prev, *next; 12 | } list_elem_t; 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | static inline void list_head_init(list_head_t *head) 18 | { 19 | head->next = (list_elem_t *)head; 20 | head->prev = (list_elem_t *)head; 21 | } 22 | 23 | static inline void list_elem_init(list_elem_t *entry) 24 | { 25 | entry->next = entry; 26 | entry->prev = entry; 27 | } 28 | 29 | static inline void list_add(list_elem_t *new_el, list_head_t *list) 30 | { 31 | new_el->next = list->next; 32 | new_el->prev = (list_elem_t *)list; 33 | list->next->prev = new_el; 34 | list->next = new_el; 35 | } 36 | 37 | static inline void list_add_tail(list_elem_t *new_el, list_head_t *list) 38 | { 39 | new_el->next = (list_elem_t *)list; 40 | new_el->prev = list->prev; 41 | list->prev->next = new_el; 42 | list->prev = new_el; 43 | } 44 | 45 | static inline void list_del(list_elem_t *el) 46 | { 47 | el->prev->next = el->next; 48 | el->next->prev = el->prev; 49 | el->prev = NULL; 50 | el->next = NULL; 51 | } 52 | 53 | static inline void list_del_init(list_elem_t *el) 54 | { 55 | el->prev->next = el->next; 56 | el->next->prev = el->prev; 57 | list_elem_init(el); 58 | } 59 | 60 | static inline int list_is_init(const list_head_t *h) 61 | { 62 | return h->next == NULL; 63 | } 64 | 65 | static inline int list_empty(const list_head_t *h) 66 | { 67 | if (list_is_init(h)) 68 | return 1; 69 | return h->next == (const list_elem_t *)h; 70 | } 71 | 72 | static inline int list_elem_inserted(const list_elem_t *el) 73 | { 74 | return el->next != el; 75 | } 76 | 77 | static inline void list_moveall(list_head_t *src, list_head_t *dst) 78 | { 79 | list_add((list_elem_t *)dst, src); 80 | list_del((list_elem_t *)src); 81 | list_head_init(src); 82 | } 83 | #ifdef __cplusplus 84 | } 85 | #endif 86 | 87 | 88 | #define LIST_HEAD_INIT(head) {(void *) &(head), (void *) &(head)} 89 | 90 | #define LIST_HEAD(name) \ 91 | list_head_t name = LIST_HEAD_INIT(name) 92 | 93 | #define list_entry(ptr, type, field) \ 94 | ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->field))) 95 | 96 | #define list_first_entry(head, type, field) \ 97 | list_entry((head)->next, type, field) 98 | 99 | #define list_for_each(entry, head, field) \ 100 | for (entry = list_entry((head)->next, typeof(*entry), field);\ 101 | &entry->field != (list_elem_t*)(head); \ 102 | entry = list_entry(entry->field.next, typeof(*entry), field)) 103 | 104 | #define list_for_each_prev(entry, head, field) \ 105 | for (entry = list_entry((head)->prev, typeof(*entry), field);\ 106 | &entry->field != (list_elem_t*)(head); \ 107 | entry = list_entry(entry->field.prev, typeof(*entry), field)) 108 | 109 | #define list_for_each_prev_continue(pos, head, field) \ 110 | for (pos = list_entry(pos->field.prev, typeof(*pos), field); \ 111 | &pos->field != (list_elem_t*)(head); \ 112 | pos = list_entry(pos->field.prev, typeof(*pos), field)) 113 | 114 | #define list_for_each_safe(entry, tmp, head, field) \ 115 | for (entry = list_entry((head)->next, typeof(*entry), field),\ 116 | tmp = list_entry(entry->field.next, typeof(*entry), field); \ 117 | &entry->field != (list_elem_t*)(head); \ 118 | entry = tmp, \ 119 | tmp = list_entry(tmp->field.next, typeof(*tmp), field)) 120 | 121 | #endif /* __LIST_H__ */ 122 | -------------------------------------------------------------------------------- /lib/lock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "ploop.h" 35 | 36 | #define PLOOP_GLOBAL_LOCK_FILE PLOOP_LOCK_DIR"/ploop.lck" 37 | 38 | #define LOCK_EXCL_BASE 200 39 | #define LOCK_LEN 5 40 | #define LOCK_EXCL_LONG 300 41 | static int create_file(char *fname) 42 | { 43 | int fd; 44 | 45 | fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644); 46 | if (fd == -1) { 47 | ploop_err(errno, "Can't create file %s", 48 | fname); 49 | return -1; 50 | } 51 | if (fchmod(fd, 0644)) 52 | ploop_err(errno, "Can't chmod(0644) on %s", fname); 53 | 54 | close(fd); 55 | return 0; 56 | } 57 | 58 | int do_open(const char *fname, int flags) 59 | { 60 | useconds_t total = 0; 61 | useconds_t wait = 10000; // initial wait time 0.01s 62 | useconds_t maxwait = 5 * 1000000; // max wait time per iteration 5s 63 | useconds_t maxtotal = 60 * 1000000; // max total wait time 60s 64 | 65 | do { 66 | int fd = open(fname, flags); 67 | if (fd != -1) 68 | return fd; 69 | else if (errno != EBUSY) 70 | return -1; 71 | 72 | if (total > maxtotal) 73 | break; 74 | 75 | usleep(wait); 76 | total += wait; 77 | wait *= 2; 78 | if (wait > maxwait) 79 | wait = maxwait; 80 | } while (1); 81 | 82 | return -1; 83 | } 84 | 85 | static int lock_fcntl(int fd, int cmd, off_t start, off_t len, 86 | short type, struct flock *out) 87 | { 88 | int rc; 89 | struct flock fl = { 90 | .l_whence = SEEK_SET, 91 | .l_start = start, 92 | .l_len = len, 93 | .l_type = type, 94 | }; 95 | 96 | rc = TEMP_FAILURE_RETRY(fcntl(fd, cmd, &fl)); 97 | if (rc) { 98 | ploop_err(errno, "Can not lock"); 99 | return rc; 100 | } 101 | 102 | if (out) 103 | memcpy(out, &fl, sizeof(struct flock)); 104 | 105 | return 0; 106 | } 107 | 108 | static int lock_set(int fd, off_t start) 109 | { 110 | return lock_fcntl(fd, F_SETLK, start, LOCK_LEN, F_RDLCK, NULL); 111 | } 112 | 113 | static int do_lock_set(int fd, int long_op) 114 | { 115 | int rc; 116 | 117 | if (long_op) { 118 | rc = lock_set(fd, LOCK_EXCL_LONG); 119 | if (rc) 120 | return rc; 121 | } 122 | return lock_set(fd, LOCK_EXCL_BASE); 123 | } 124 | 125 | static int lock_test(int fd, off_t start, unsigned int tm) 126 | { 127 | int rc; 128 | struct flock fl; 129 | 130 | do { 131 | rc = lock_fcntl(fd, F_GETLK, start, LOCK_LEN, F_WRLCK, &fl); 132 | if (rc) 133 | return rc; 134 | if (fl.l_type == F_UNLCK) 135 | return 0; 136 | if (tm) 137 | sleep(1); 138 | } while (tm--); 139 | 140 | ploop_err(0, "Already locked by pid %d", fl.l_pid); 141 | 142 | return -1; 143 | } 144 | 145 | static int do_lock_test(int fd, unsigned int timeout) 146 | { 147 | int r; 148 | 149 | /* do not wait if long lock obtained */ 150 | r = lock_test(fd, LOCK_EXCL_LONG, 0); 151 | if (r) 152 | return r; 153 | return lock_test(fd, LOCK_EXCL_BASE, timeout); 154 | } 155 | 156 | static int do_lock_unlock(int fd) 157 | { 158 | int rc; 159 | 160 | rc = lock_fcntl(fd, F_SETLK, LOCK_EXCL_LONG, LOCK_LEN, F_UNLCK, NULL); 161 | rc |= lock_fcntl(fd, F_SETLK, LOCK_EXCL_BASE, LOCK_LEN, F_UNLCK, NULL); 162 | 163 | return rc; 164 | } 165 | 166 | int lock(const char *fname, int long_op, unsigned int timeout) 167 | { 168 | int fd, r; 169 | 170 | if ((fd = do_open(fname, O_RDONLY | O_CLOEXEC)) == -1) { 171 | ploop_err(errno, "Can not open %s", fname); 172 | return -1; 173 | } 174 | 175 | r = do_lock_test(fd, timeout); 176 | if (r) 177 | goto err_close; 178 | 179 | r = do_lock_set(fd, long_op); 180 | if (r) 181 | goto err_close; 182 | 183 | r = do_lock_test(fd, 0); 184 | if (r) 185 | goto err; 186 | 187 | return fd; 188 | 189 | err: 190 | do_lock_unlock(fd); 191 | err_close: 192 | close(fd); 193 | return -1; 194 | } 195 | 196 | void ploop_unlock(int *lckfd) 197 | { 198 | if (*lckfd != -1) { 199 | do_lock_unlock(*lckfd); 200 | close(*lckfd); 201 | *lckfd = -1; 202 | } 203 | } 204 | 205 | int ploop_lock_di(struct ploop_disk_images_data *di) 206 | { 207 | char fname[PATH_MAX]; 208 | 209 | if (di == NULL) 210 | return 0; 211 | 212 | if (di->runtime->image_fmt == QCOW_FMT) { 213 | snprintf(fname, sizeof(fname), "%s", di->runtime->xml_fname); 214 | } else { 215 | if (ploop_read_dd(di)) 216 | return -1; 217 | 218 | if (get_delta_fname(di, get_base_delta_uuid(di), fname, sizeof(fname))) 219 | return -1; 220 | } 221 | 222 | di->runtime->lckfd = lock(fname, 0, LOCK_TIMEOUT); 223 | if (di->runtime->lckfd == -1) 224 | return -1; 225 | return 0; 226 | } 227 | 228 | void ploop_unlock_di(struct ploop_disk_images_data *di) 229 | { 230 | if (di != NULL) 231 | ploop_unlock(&di->runtime->lckfd); 232 | } 233 | 234 | void ploop_unlock_dd(struct ploop_disk_images_data *di) 235 | { 236 | return ploop_unlock_di(di); 237 | } 238 | 239 | int ploop_global_lock(void) 240 | { 241 | if (access(PLOOP_GLOBAL_LOCK_FILE, F_OK)) { 242 | if (access(PLOOP_LOCK_DIR, F_OK) && 243 | mkdir(PLOOP_LOCK_DIR, 0700) && errno != EEXIST) { 244 | ploop_err(errno, "Failed to create " PLOOP_LOCK_DIR); 245 | return -1; 246 | } 247 | if (create_file(PLOOP_GLOBAL_LOCK_FILE)) 248 | return -1; 249 | } 250 | return lock(PLOOP_GLOBAL_LOCK_FILE, 0, LOCK_TIMEOUT); 251 | } 252 | -------------------------------------------------------------------------------- /lib/logger.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "ploop.h" 30 | 31 | static int _s_log_enable = 1; 32 | static int _s_log_level = 3; 33 | static int _s_log_verbose_level = PLOOP_LOG_NOCONSOLE; // disable stdout/stderr 34 | static FILE *_s_log_file = NULL; 35 | static struct timeval start = { }; 36 | 37 | 38 | /* Thread Local Storage */ 39 | static __thread char _g_log_buf[LOG_BUF_SIZE]; 40 | 41 | static char *get_buffer(void) 42 | { 43 | return _g_log_buf; 44 | } 45 | #define PLOOP_LOG_FILE "/var/log/ploop.log" 46 | #ifdef PLOOP_LOG_FILE 47 | static FILE *_s_ploop_log_file = NULL; 48 | __attribute__((constructor)) void __ploop_init (void) 49 | { 50 | 51 | _s_ploop_log_file = fopen(PLOOP_LOG_FILE, "a"); 52 | } 53 | 54 | __attribute__((destructor)) void __ploop_deinit (void) 55 | { 56 | if (_s_ploop_log_file) 57 | fclose(_s_ploop_log_file); 58 | } 59 | #endif 60 | 61 | static inline void get_date(char *buf, int len) 62 | { 63 | struct tm *p_tm_time; 64 | time_t ptime; 65 | 66 | ptime = time(NULL); 67 | p_tm_time = localtime(&ptime); 68 | strftime(buf, len, "%Y-%m-%dT%T%z", p_tm_time); 69 | } 70 | 71 | static void timediff(struct timeval *from, struct timeval *to) 72 | { 73 | to->tv_sec -= from->tv_sec; 74 | if (to->tv_usec >= from->tv_usec) 75 | to->tv_usec -= from->tv_usec; 76 | else { 77 | to->tv_sec--; 78 | to->tv_usec += 1000000 - from->tv_usec; 79 | } 80 | } 81 | 82 | const static char* get_ts(void) 83 | { 84 | static char buf[16]; 85 | const static char empty[] = ""; 86 | struct timeval t; 87 | 88 | if (_s_log_verbose_level < PLOOP_LOG_TIMESTAMPS) 89 | return empty; 90 | 91 | gettimeofday(&t, NULL); 92 | timediff(&start, &t); 93 | snprintf(buf, sizeof(buf), "[%2u.%06u] ", 94 | (unsigned)t.tv_sec, (unsigned)t.tv_usec); 95 | 96 | return buf; 97 | } 98 | 99 | static void logger_ap(int level, int err_no, const char *format, va_list ap) 100 | { 101 | char buf[LOG_BUF_SIZE]; 102 | char date[64]; 103 | char *err_buf; 104 | int r; 105 | int errno_tmp = errno; 106 | FILE *std = (level < 0 ? stderr : stdout); 107 | 108 | r = vsnprintf(buf, sizeof(buf), format, ap); 109 | if ((r < sizeof(buf) - 1) && err_no) { 110 | snprintf(buf + r, sizeof(buf) - r, ": %s", 111 | strerror(err_no)); 112 | } 113 | 114 | #ifdef PLOOP_LOG_FILE 115 | if (_s_ploop_log_file && _s_log_level >= level) { 116 | get_date(date, sizeof(date)); 117 | fprintf(_s_ploop_log_file, "%s pid=%d: %s\n", date, getpid(), buf); 118 | fflush(_s_ploop_log_file); 119 | } 120 | #endif 121 | 122 | if (_s_log_enable) { 123 | if (_s_log_verbose_level != PLOOP_LOG_NOCONSOLE && 124 | _s_log_verbose_level >= level) { 125 | fprintf(std, "%s%s\n", get_ts(), buf); 126 | fflush(std); 127 | } 128 | 129 | if (_s_log_level >= level && _s_log_file != NULL) { 130 | get_date(date, sizeof(date)); 131 | fprintf(_s_log_file, "%s : %s\n", date, buf); 132 | fflush(_s_log_file); 133 | } 134 | } 135 | if (level < 0 && (err_buf = get_buffer()) != NULL) 136 | strcpy(err_buf, buf); /* Preserve error */ 137 | errno = errno_tmp; 138 | } 139 | 140 | void ploop_log(int level, const char *format, ...) 141 | { 142 | va_list ap; 143 | 144 | va_start(ap, format); 145 | logger_ap(level, 0, format, ap); 146 | va_end(ap); 147 | } 148 | 149 | void __ploop_err(int err_no, const char *format, ...) 150 | { 151 | va_list ap; 152 | int err = errno; 153 | 154 | va_start(ap, format); 155 | logger_ap(-1, err_no, format, ap); 156 | va_end(ap); 157 | errno = err; 158 | } 159 | 160 | const char *ploop_get_last_error(void) 161 | { 162 | return get_buffer(); 163 | } 164 | 165 | void time_init(void) { 166 | if (start.tv_sec == 0 && start.tv_usec == 0) 167 | gettimeofday(&start, NULL); 168 | } 169 | 170 | void ploop_set_log_level(int level) 171 | { 172 | time_init(); 173 | _s_log_level = level; 174 | } 175 | 176 | int ploop_get_log_level(void) 177 | { 178 | return _s_log_level; 179 | } 180 | 181 | int ploop_get_verbose_level(void) 182 | { 183 | return _s_log_verbose_level; 184 | } 185 | 186 | void ploop_set_verbose_level(int level) 187 | { 188 | time_init(); 189 | _s_log_verbose_level = level; 190 | } 191 | 192 | int ploop_set_log_file(const char *fname) 193 | { 194 | FILE *fp = NULL; 195 | 196 | time_init(); 197 | 198 | if (fname != NULL) { 199 | fp = fopen(fname, "a"); 200 | if (fp == NULL) { 201 | __ploop_err(errno, "Can't open %s", fname); 202 | return -1; 203 | } 204 | } 205 | 206 | if (_s_log_file != NULL) 207 | fclose(_s_log_file); 208 | _s_log_file = fp; 209 | 210 | return 0; 211 | } 212 | -------------------------------------------------------------------------------- /lib/ploop.pc.in: -------------------------------------------------------------------------------- 1 | libdir=@LIBDIR@ 2 | 3 | Name: ploop 4 | Version: @VERSION@ 5 | Description: ploop library 6 | Requires: 7 | Requires.private: libxml-2.0 8 | Libs: -L${libdir} -lploop 9 | Libs.private: -lrt -lpthread 10 | Cflags: 11 | -------------------------------------------------------------------------------- /lib/uuid.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "ploop.h" 30 | 31 | /* UUID generator. We use DCE style random UUIDs, it is easy. */ 32 | 33 | #define UUID_NLEN 16 34 | #define UUID_FMT "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" 35 | 36 | struct helper_uuid_t { 37 | __u32 time_low; 38 | __u16 time_mid; 39 | __u16 time_hi_and_version; 40 | __u16 clock_seq; 41 | __u8 node[6]; 42 | }; 43 | 44 | static int uuid_new(unsigned char * uuid) 45 | { 46 | int res; 47 | int fd; 48 | 49 | fd = open("/dev/urandom", O_RDONLY); 50 | if (fd < 0) 51 | return SYSEXIT_OPEN; 52 | res = read(fd, uuid, UUID_NLEN); 53 | close(fd); 54 | if (res < 0) 55 | return SYSEXIT_READ; 56 | 57 | if (res != UUID_NLEN) { 58 | errno = EINVAL; 59 | return SYSEXIT_READ; 60 | } 61 | 62 | /* Version: random */ 63 | uuid[6] = (uuid[6] & 0x0F) | 0x40; 64 | 65 | /* Variant: DCE. The highest bit is 1, the next is 0. 66 | * Note, three bits allcoated but value of the third 67 | * is arbitrary. 68 | */ 69 | uuid[8] = (uuid[8] & 0x3F) | 0x80; 70 | 71 | return 0; 72 | } 73 | 74 | static void uuid_unpack(unsigned char *in, struct helper_uuid_t *uu) 75 | { 76 | unsigned char *ptr = in; 77 | __u32 tmp; 78 | 79 | tmp = *ptr++; 80 | tmp = (tmp << 8) | *ptr++; 81 | tmp = (tmp << 8) | *ptr++; 82 | tmp = (tmp << 8) | *ptr++; 83 | uu->time_low = tmp; 84 | 85 | tmp = *ptr++; 86 | tmp = (tmp << 8) | *ptr++; 87 | uu->time_mid = tmp; 88 | 89 | tmp = *ptr++; 90 | tmp = (tmp << 8) | *ptr++; 91 | uu->time_hi_and_version = tmp; 92 | 93 | tmp = *ptr++; 94 | tmp = (tmp << 8) | *ptr++; 95 | uu->clock_seq = tmp; 96 | 97 | memcpy(uu->node, ptr, 6); 98 | } 99 | 100 | static const char *uuid2str(unsigned char *in, char *out, int len) 101 | { 102 | struct helper_uuid_t uuid; 103 | 104 | uuid_unpack(in, &uuid); 105 | snprintf(out, len, UUID_FMT, 106 | uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, 107 | uuid.clock_seq >> 8, uuid.clock_seq & 0xFF, 108 | uuid.node[0], uuid.node[1], uuid.node[2], 109 | uuid.node[3], uuid.node[4], uuid.node[5]); 110 | return out; 111 | } 112 | 113 | static const char *prl_uuid2str(unsigned char *in, char *out, int len) 114 | { 115 | assert(!(len < 39)); 116 | 117 | out[0] = '{'; 118 | uuid2str(in, out + 1, len - 2); 119 | out[37] = '}'; 120 | out[38] = '\0'; 121 | return out; 122 | } 123 | 124 | int ploop_uuid_generate(char *uuid, int len) 125 | { 126 | int ret; 127 | unsigned char uu[16]; 128 | 129 | ret = uuid_new(uu); 130 | if (ret) { 131 | ploop_err(errno, "Can't generate uuid"); 132 | return ret; 133 | } 134 | prl_uuid2str(uu, uuid, len); 135 | return 0; 136 | } 137 | 138 | int gen_uuid_pair(char *uuid1, int len1, char *uuid2, int len2) 139 | { 140 | int ret; 141 | 142 | ret = ploop_uuid_generate(uuid1, len1); 143 | if (ret) 144 | return ret; 145 | ret = ploop_uuid_generate(uuid2, len2); 146 | if (ret) 147 | return ret; 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.pyc 3 | 4 | -------------------------------------------------------------------------------- /python/Makefile: -------------------------------------------------------------------------------- 1 | PLOOPROOT = .. 2 | 3 | include $(PLOOPROOT)/Makefile.inc 4 | 5 | all: 6 | $(Q) CFLAGS= python3 setup.py build 7 | .PHONY: all 8 | 9 | clean: 10 | $(E) " CLEAN " 11 | $(Q) rm -rf build/ 12 | .PHONY: clean 13 | 14 | distclean: clean 15 | .PHONY: distclean 16 | 17 | install: 18 | $(Q) CFLAGS= python3 setup.py install \ 19 | $(if $(DESTDIR), --root $(DESTDIR)) 20 | .PHONY: install 21 | -------------------------------------------------------------------------------- /python/libploop/__init__.py: -------------------------------------------------------------------------------- 1 | from libploop import libploopapi 2 | import threading 3 | 4 | class ploopcopy(): 5 | def __init__(self, ddxml, fd, async_ = 0): 6 | self.di = libploopapi.open_dd(ddxml) 7 | self.h = libploopapi.copy_init(self.di, fd, async_) 8 | 9 | def __del__(self): 10 | if self.h: 11 | libploopapi.copy_deinit(self.h) 12 | if self.di: 13 | libploopapi.close_dd(self.di) 14 | 15 | def copy_start(self): 16 | return libploopapi.copy_start(self.h) 17 | 18 | def copy_next_iteration(self): 19 | return libploopapi.copy_next_iteration(self.h) 20 | 21 | def copy_stop(self): 22 | ret = libploopapi.copy_stop(self.h) 23 | libploopapi.copy_deinit(self.h) 24 | self.h = None 25 | return ret; 26 | 27 | class ploopcopy_receiver(): 28 | def __init__(self, fname, fd): 29 | libploopapi.start_receiver(fname, fd); 30 | 31 | class ploopcopy_thr_receiver(threading.Thread): 32 | def __init__(self, fname, fd): 33 | threading.Thread.__init__(self) 34 | self.__fname = fname 35 | self.__fd = fd 36 | 37 | def run(self): 38 | libploopapi.start_receiver(self.__fname, self.__fd); 39 | 40 | class snapshot(): 41 | def __init__(self, ddxml): 42 | self.ddxml = ddxml 43 | 44 | def create(self): 45 | return libploopapi.create_snapshot(self.ddxml); 46 | 47 | def create_offline(self): 48 | return libploopapi.create_snapshot_offline(self.ddxml); 49 | 50 | def delete(self, guid): 51 | return libploopapi.delete_snapshot(self.ddxml, guid); 52 | 53 | def get_top_delta_fname(self): 54 | return libploopapi.get_top_delta_fname(self.ddxml); 55 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | 3 | from distutils.core import setup, Extension 4 | 5 | extension_mod = Extension('libploop.libploopapi', 6 | sources=['libploop/libploopmodule.c'], 7 | include_dirs=['../include', '../lib'], 8 | library_dirs=['../lib'], 9 | libraries=['ploop']) 10 | 11 | setup(name = 'libploop', 12 | ext_modules=[extension_mod], 13 | packages=["libploop"], 14 | description = 'ploop API', 15 | ) 16 | -------------------------------------------------------------------------------- /scripts/Makefile: -------------------------------------------------------------------------------- 1 | PLOOPROOT = .. 2 | 3 | include $(PLOOPROOT)/Makefile.inc 4 | 5 | SCRIPTS=mount.ploop umount.ploop 6 | 7 | all: $(SCRIPTS) 8 | .PHONY: all 9 | 10 | clean distclean: 11 | .PHONY: clean distclean 12 | 13 | install: 14 | $(INSTALL) -d $(DESTDIR)$(SBINDIR) 15 | $(INSTALL) -d $(DESTDIR)$(LIBSCRIPTDIR) 16 | $(INSTALL) -d $(DESTDIR)$(LIBSCRIPTDIR)/crypt.d 17 | $(INSTALL) -m 755 $(SCRIPTS) $(DESTDIR)$(SBINDIR) 18 | $(INSTALL) -m 755 crypthelper $(DESTDIR)$(LIBSCRIPTDIR) 19 | .PHONY: install 20 | -------------------------------------------------------------------------------- /scripts/crypthelper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from __future__ import print_function 4 | import sys 5 | import os 6 | import subprocess 7 | from time import sleep 8 | 9 | def error(msg): 10 | print(msg, file = sys.stderr) 11 | 12 | def get_key(key_id): 13 | exe = '/usr/libexec/ploop/crypt.d/getkey' 14 | if not os.path.exists(exe): 15 | error('The key request plugin %r is not installed' % exe) 16 | sys.exit(2) 17 | try: 18 | return subprocess.check_output(('%s %s' % (exe, key_id)).split()) 19 | except subprocess.CalledProcessError as e: 20 | error('Failed to request key with %r: rc=%d' % (exe, e.returncode)) 21 | sys.exit(2) 22 | except: 23 | error("Failed to execute %r: Unexpected error" % (exe)) 24 | sys.exit(2) 25 | 26 | def run_cryptsetup(cmdline, data = None): 27 | proc = subprocess.Popen(['/usr/sbin/cryptsetup'] + cmdline, 28 | stdin = subprocess.PIPE) 29 | proc.communicate(data) 30 | return proc.returncode 31 | 32 | def crypt_init(key_id, device): 33 | rc = run_cryptsetup(('luksFormat %s -' % device).split(), get_key(key_id)) 34 | if rc != 0: 35 | error('Cannot format %r rc=%d' % (device, rc)) 36 | return 3 37 | return 0 38 | 39 | def crypt_open(key_id, device, name): 40 | rc = run_cryptsetup(('--key-file - --allow-discards luksOpen %s %s' 41 | % (device, name)).split(), get_key(key_id)) 42 | if rc != 0: 43 | error('Cannot open %r %r rc=%d' % (device, name, rc)) 44 | return 4 45 | return 0 46 | 47 | def crypt_close(name): 48 | for i in range(60): 49 | rc = run_cryptsetup(('luksClose %s' % name).split()) 50 | if rc == 0: 51 | return 0 52 | elif rc != 5: 53 | error('Cannot close %r rc=%d' % (name, rc)) 54 | return 5 55 | sleep(1) 56 | return 5 57 | 58 | def crypt_resize(name): 59 | rc = run_cryptsetup(('resize %s' % name).split()) 60 | if rc != 0: 61 | error('Cannot resize %r rc=%d' % (name, rc)) 62 | return 6 63 | return 0 64 | 65 | def crypt_change_key(old_key_id, new_key_id, device): 66 | old_key = get_key(old_key_id) 67 | # Here we pass the old key and the new key as a binary data in one stream. 68 | # Old key length is used to separate the keys on the cryptsetup side. 69 | rc = run_cryptsetup(('--key-file - --keyfile-size %d luksChangeKey %s -' 70 | % (len(old_key), device)).split(), old_key + get_key(new_key_id)) 71 | if rc != 0: 72 | error('Cannot change the key %r for %r rc=%d' 73 | % (old_key_id, device, rc)) 74 | return 7 75 | return 0 76 | 77 | if __name__ == "__main__": 78 | if len(sys.argv) != 2: 79 | sys.exit(1) 80 | action = sys.argv[1] 81 | 82 | rc = 1 83 | try: 84 | if action == 'init': 85 | rc = crypt_init(os.environ['KEYID'], os.environ['DEVICE']) 86 | elif action == 'open': 87 | rc = crypt_open(os.environ['KEYID'], os.environ['DEVICE'], 88 | os.environ['DEVICE_NAME']) 89 | elif action == 'close': 90 | rc = crypt_close(os.environ['DEVICE_NAME']) 91 | elif action == 'resize': 92 | rc = crypt_resize(os.environ['DEVICE_NAME']) 93 | elif action == 'changekey': 94 | rc = crypt_change_key(os.environ['KEYID'], os.environ['DEVICE_NAME'], 95 | os.environ['DEVICE']) 96 | except KeyError as e: 97 | error('Environment variable %s is not set' % e) 98 | sys.exit(rc) 99 | 100 | -------------------------------------------------------------------------------- /scripts/mount.ploop: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2008-2017 Parallels International GmbH. 4 | # Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | # Usage: 21 | # /sbin/mount.ploop spec dir [-h] [-o options] 22 | # 23 | 24 | PLOOP="ploop" 25 | DESCRIPTOR="DiskDescriptor.xml" 26 | DISK_DESCRIPTOR="" 27 | MOUNT_POINT="" 28 | OPTIONS="" 29 | OPTS_ORIG="" 30 | READLINK="readlink" 31 | MTAB="/etc/mtab" 32 | LOGFILE="/var/log/ploop_mount.log" 33 | MODULES="ploop pfmt_ploop1 pfmt_raw pio_direct pio_nfs pio_kaio" 34 | VERBOSE="" 35 | FAKE="" 36 | SELF=$(basename $0) 37 | 38 | log() { 39 | if [ "x$USER" = "x" ]; then 40 | echo $* >> $LOGFILE 41 | fi 42 | echo $* 43 | } 44 | 45 | verbose() { 46 | [ -n "$VERBOSE" ] && echo "$SELF: $*" 47 | } 48 | 49 | parse_opt() { 50 | local opts="$1" 51 | local nparam="$2" 52 | local string="" 53 | local value="" 54 | local param="" 55 | while [ "x$opts" != "x" ]; do 56 | string=`echo $opts | sed "s,\,.*,,g"` 57 | echo $string | grep "=" > /dev/null 2>&1 58 | if [ $? -eq 0 ]; then 59 | value=`echo $string | sed "s,.*=,,g"` 60 | param=`echo $string | sed "s,=.*,,g"` 61 | else 62 | param="$string" 63 | fi 64 | if [ "x$param" = "x$nparam" ]; then 65 | [ "x$value" != "x" ] && echo $value 66 | return 0 67 | fi 68 | opts=`echo $opts | sed -e "s,^$string,,g" -e "s,^\,,,g"` 69 | done 70 | return 1 71 | } 72 | 73 | canonicalize() { 74 | $READLINK -f "$1" 2>/dev/null 75 | } 76 | 77 | check_bin() { 78 | if ! which $1 >/dev/null; then 79 | log "$1 utility is not found" 80 | exit 2 81 | fi 82 | } 83 | 84 | load_modules() { 85 | local m 86 | 87 | for m in $MODULES; do 88 | modprobe $m 89 | done 90 | } 91 | 92 | mangle() { 93 | echo "$1" | 94 | sed -e 's/\\/\\134/g' \ 95 | -e 's/ /\\040/g' \ 96 | -e 's/\t/\\011/g' | 97 | sed ':a;N;$!ba;s/\n/\\012/g' 98 | } 99 | 100 | unmangle() { 101 | echo -n "$1" | sed \ 102 | -e 's/\\040/ /g' -e 's/\\011/\t/g' \ 103 | -e 's/\\012/\n/g' -e 's/\\134/\\/g' 104 | } 105 | 106 | # Place the timestamp into log 107 | if [ "x$USER" = "x" ]; then 108 | date >> $LOGFILE 109 | fi 110 | 111 | # Make sure we have sane PATH 112 | for P in /sbin /usr/sbin /usr/local/sbin; do 113 | if ! echo ":${PATH}:" | fgrep -q ":${P}:"; then 114 | PATH="$P:$PATH" 115 | fi 116 | done 117 | export PATH 118 | 119 | check_bin $PLOOP 120 | check_bin $READLINK 121 | 122 | case "$1" in 123 | -h|--help|-?) 124 | log "mount.ploop is a private mount(8) wrapper for ploop." 125 | log "Don't use it directly!" 126 | exit 1 127 | ;; 128 | esac 129 | 130 | # Parse the parameters 131 | if [ "x$1" = "x" ]; then 132 | log "ploop-related $DESCRIPTOR was not given" 133 | exit 32 134 | else 135 | DISK_DESCRIPTOR=`canonicalize "$1"` 136 | if [ ! -f "$DISK_DESCRIPTOR" ]; then 137 | log "$DISK_DESCRIPTOR does not exist" 138 | exit 32 139 | fi 140 | shift 141 | fi 142 | 143 | if [ "x$1" = "x" ]; then 144 | log "Mount point was not given" 145 | exit 32 146 | else 147 | MOUNT_POINT=`canonicalize "$1"` 148 | if [ ! -d "$MOUNT_POINT" ]; then 149 | log "$MOUNT_POINT does not exist" 150 | exit 32 151 | fi 152 | shift 153 | fi 154 | 155 | # Parse options 156 | while [ -n "$1" ]; do 157 | case "$1" in 158 | -o) 159 | shift 160 | OPTS_ORIG="$1" 161 | ro=`parse_opt $1 ro` 162 | if [ $? -eq 0 -a "x$ro" != "x" ]; then 163 | OPTIONS="$OPTIONS -r" 164 | fi 165 | ;; 166 | -v) 167 | VERBOSE=yes 168 | ;; 169 | -f) 170 | FAKE=yes 171 | ;; 172 | -n) 173 | # Deliberately ignore, since otherwise there will be 174 | # no record with "ploop" in /etc/mtab and umount won't 175 | # run umount.ploop helper. 176 | verbose "option -n deliberately ignored" 177 | ;; 178 | -s) 179 | # ignore as we don't have any subtypes 180 | ;; 181 | *) 182 | verbose "unexpected option $* ignored" 183 | ;; 184 | esac 185 | shift 186 | done 187 | 188 | # Check that it's already mounted 189 | for mpoint in `awk '{print $2}' /proc/mounts`; do 190 | if [ "$(unmangle $mpoint)" = "$MOUNT_POINT" ]; then 191 | log "$MOUNT_POINT already mounted" 192 | exit 32 193 | fi 194 | done 195 | 196 | load_modules 197 | 198 | # Call the ploop utility 199 | verbose "running $PLOOP mount $OPTIONS -m $MOUNT_POINT $DISK_DESCRIPTOR" 200 | if [ -z "$FAKE" ]; then 201 | if [ "x$USER" = "x" ]; then 202 | $PLOOP mount $OPTIONS -m "$MOUNT_POINT" "$DISK_DESCRIPTOR" >> $LOGFILE 2>&1 203 | else 204 | $PLOOP mount $OPTIONS -m "$MOUNT_POINT" "$DISK_DESCRIPTOR" 205 | fi 206 | fi 207 | 208 | if [ $? -ne 0 ]; then 209 | log "$PLOOP mount $OPTIONS -m $MOUNT_POINT $DISK_DESCRIPTOR command failed" 210 | exit 32 211 | fi 212 | 213 | # Fill /etc/mtab 214 | if [ -f $MTAB -a ! -L $MTAB ]; then 215 | verbose "adding mount to $MTAB" 216 | echo "$(mangle "$DISK_DESCRIPTOR") $(mangle "$MOUNT_POINT") ploop $OPTS_ORIG 0 0" >> $MTAB 217 | fi 218 | 219 | exit 0 220 | -------------------------------------------------------------------------------- /scripts/umount.ploop: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (c) 2008-2017 Parallels International GmbH. 3 | # Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | # 19 | # Usage: 20 | # /sbin/umount.ploop [-h] mount_point 21 | # 22 | 23 | PLOOP="ploop" 24 | MOUNT_POINT="" 25 | PLOOP_DEV="" 26 | MTAB="/etc/mtab" 27 | DESCRIPTOR="DiskDescriptor.xml" 28 | 29 | mangle() { 30 | echo "$1" | 31 | sed -e 's/\\/\\134/g' \ 32 | -e 's/ /\\040/g' \ 33 | -e 's/\t/\\011/g' | 34 | sed ':a;N;$!ba;s/\n/\\012/g' 35 | } 36 | 37 | # Make sure we have sane PATH 38 | for P in /sbin /usr/sbin /usr/local/sbin; do 39 | if ! echo ":${PATH}:" | fgrep -q ":${P}:"; then 40 | PATH="$P:$PATH" 41 | fi 42 | done 43 | export PATH 44 | 45 | if ! which $PLOOP >/dev/null; then 46 | echo "$PLOOP utility is not found" 47 | exit 2 48 | fi 49 | 50 | case $1 in 51 | -h|--help|-?) 52 | echo "umount.ploop is a private mount(8) wrapper for ploop." 53 | echo "Don't use it directly!" 54 | exit 1 55 | ;; 56 | esac 57 | 58 | # Parse the parameters. umount always call us with canonicalized mpoint 59 | if [ "x$1" = "x" ]; then 60 | echo "ploop-related mount point was not given" 61 | exit 32 62 | else 63 | MOUNT_POINT="$1" 64 | fi 65 | 66 | # Call the ploop utility 67 | $PLOOP umount -m "$MOUNT_POINT" 68 | 69 | if [ $? -ne 0 ]; then 70 | echo "$PLOOP umount -m $MOUNT_POINT failed" 71 | exit 32 72 | fi 73 | 74 | # Clear /etc/mtab 75 | if [ -f $MTAB ]; then 76 | MPNT=$(mangle "$MOUNT_POINT" | sed -e 's/\\/\\\\/g' -e 's/;/\\;/g') 77 | sed -i "\;^[^ ]*/$DESCRIPTOR $MPNT ploop ;d" $MTAB 78 | if [ $? -ne 0 ]; then 79 | echo "Failed to save $MTAB" 80 | exit 32 81 | fi 82 | fi 83 | 84 | exit 0 85 | -------------------------------------------------------------------------------- /setver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | OPTS=$(getopt -o biUFvo --long \ 4 | build,install,update,freshen,verbose,oldpackage \ 5 | -n 'setver.sh' -- "$@") 6 | 7 | eval set -- "$OPTS" 8 | 9 | while true; do 10 | case $1 in 11 | -b|--build) 12 | build=yes 13 | ;; 14 | -i|--install) 15 | build=yes 16 | install=i 17 | ;; 18 | -U|--update) 19 | build=yes 20 | install=U 21 | ;; 22 | -F|--freshen) 23 | build=yes 24 | install=F 25 | ;; 26 | -v|--verbose) 27 | verbose=yes 28 | ;; 29 | -o|--oldpackage) 30 | rpm_install_flags="${rpm_install_flags} --oldpackage" 31 | ;; 32 | --) 33 | break 34 | ;; 35 | *) 36 | echo "Invalid argument: $1" 1>&2 37 | exit 1 38 | esac 39 | shift 40 | done 41 | 42 | test "$build" = "yes" && clean="yes" 43 | 44 | NAME=ploop 45 | RPM_SPEC=${NAME}.spec 46 | MAKEFILE=Makefile.inc 47 | 48 | # Try to figure out version from git 49 | GIT_DESC=$(git describe --tags | sed s'/^[^0-9]*-\([0-9].*\)$/\1/') 50 | # 3.0.28-1-gf784152 51 | GIT_V=$(echo $GIT_DESC | awk -F - '{print $1}') 52 | GIT_R=$(echo $GIT_DESC | awk -F - '{print $2}') 53 | GIT_T=$(echo $GIT_DESC | awk -F - '{print $3}') 54 | test "x$GIT_R" = "x" && GIT_R="1" 55 | GIT_VR="${GIT_V}-${GIT_R}" 56 | 57 | BUILDID=$(cat .build-id 2>/dev/null || echo 0) 58 | if test "${GIT_VR}" = "$(cat .version-id 2>/dev/null)"; then 59 | BUILDID=$((BUILDID+1)) 60 | GIT_R="${GIT_R}.${BUILDID}" 61 | else 62 | echo "${GIT_VR}" > .version-id 63 | BUILDID=0 64 | fi 65 | echo "${BUILDID}" > .build-id 66 | 67 | GIT_RT="${GIT_R}.${GIT_T}" 68 | test "x$GIT_T" = "x" && GIT_RT="${GIT_R}" 69 | GIT_VRT="${GIT_V}-${GIT_RT}" 70 | 71 | read_spec() { 72 | SPEC_V=$(awk '($1 == "Version:") {print $2}' $RPM_SPEC) 73 | SPEC_R=$(awk '($1 " " $2 == "%define rel") {print $3}' $RPM_SPEC) 74 | SPEC_VR="${SPEC_V}-${SPEC_R}" 75 | } 76 | read_spec 77 | 78 | # Store original spec 79 | if test "$clean" = "yes"; then 80 | cp -a $RPM_SPEC .$RPM_SPEC.$$ 81 | ATEXIT="mv -f .$RPM_SPEC.$$ $RPM_SPEC" 82 | trap "$ATEXIT" EXIT 83 | fi 84 | 85 | # Set version/release in spec from git 86 | if test "$GIT_VR" != "$SPEC_VR"; then 87 | test -z "$verbose" || echo "Changing $RPM_SPEC:" 88 | # Version: 3.0.28 89 | # Release: 1%{?dist} 90 | sed -i -e "s/^\(Version:[[:space:]]*\).*\$/\1$GIT_V/" \ 91 | -e "s/^\(%define rel[[:space:]]*\).*\$/\1$GIT_RT/" \ 92 | $RPM_SPEC 93 | if test -n "$GIT_T"; then 94 | NVR='%{name}-%{version}-%{rel}' 95 | sed -i -e "s/^\(Source:[[:space:]]*\).*\$/\1${NVR}.tar.bz2/" \ 96 | -e "s/^%setup[[:space:]]*.*\$/%setup -n ${NVR}/" \ 97 | $RPM_SPEC 98 | fi 99 | fi 100 | test -z "$verbose" || \ 101 | grep -E -H '^Version:|^%define rel|^Source:|^%setup' $RPM_SPEC 102 | 103 | # Store original Makefile 104 | if test "$clean" = "yes"; then 105 | cp -a $MAKEFILE .$MAKEFILE.$$ 106 | ATEXIT="$ATEXIT; mv -f .$MAKEFILE.$$ $MAKEFILE" 107 | trap "$ATEXIT" EXIT 108 | fi 109 | 110 | # Set version in Makefile from git 111 | MF_V=$(awk -F = '($1 == "VERSION") { print $2; }' $MAKEFILE) 112 | MF_R=$(awk -F = '($1 == "RELEASE") { print $2; }' $MAKEFILE) 113 | GIT_R_MF="-$GIT_RT" 114 | test "$GIT_RT" = "1" && GIT_R_MF="" 115 | if test "$GIT_V" != "$MF_V" -o "$GIT_R_MF" != "MF_R"; then 116 | test -z "$verbose" || echo "Changing $MAKEFILE:" 117 | # VERSION=1.12 118 | # RELEASE= 119 | sed -i -e "s/^\(VERSION=\).*\$/\1$GIT_V/" \ 120 | -e "s/^\(RELEASE=\).*\$/\1$GIT_R_MF/" \ 121 | $MAKEFILE 122 | fi 123 | 124 | test -z "$verbose" || \ 125 | grep -E -H "^VERSION=|^RELEASE=" $MAKEFILE 126 | 127 | test "$build" = "yes" || exit 0 128 | make rpms || exit 1 129 | 130 | # Remove dist tarball 131 | test "$clean" = "yes" && rm -f ${NAME}-${GIT_VRT}.tar.bz2 132 | 133 | test -z "$install" && exit 0 134 | sudo rpm -${install}hv ${rpm_install_flags} \ 135 | $(rpm --eval %{_rpmdir}/%{_arch})/${NAME}-*${GIT_VRT}*.rpm || exit 1 136 | 137 | # Remove rpms 138 | if test "$clean" = "yes"; then 139 | rm -f $(rpm --eval %{_rpmdir}/%{_arch})/${NAME}-*${GIT_VRT}*.rpm 140 | rm -f $(rpm --eval %{_srcrpmdir})/${NAME}-*${GIT_VRT}*.src.rpm 141 | fi 142 | -------------------------------------------------------------------------------- /targets.list: -------------------------------------------------------------------------------- 1 | ploop.vzspecs dist-vz9-u00 2 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | PLOOPROOT = .. 2 | 3 | include $(PLOOPROOT)/Makefile.inc 4 | 5 | FILES=functions \ 6 | test-change-fmt_version \ 7 | test-device-grow \ 8 | test-device-snapshot \ 9 | test-fs-resize \ 10 | test-pcopy.py \ 11 | test-snapshot \ 12 | test-sparse \ 13 | 14 | all clean distclean: 15 | .PHONY: all clean distclean 16 | 17 | install: 18 | $(Q) $(INSTALL) -d $(DESTDIR)/$(TESTDIR) 19 | $(E) " INSTALL " $(FILES) 20 | $(Q) $(INSTALL) -m 755 $(FILES) $(DESTDIR)/$(TESTDIR) 21 | 22 | -------------------------------------------------------------------------------- /test/functions: -------------------------------------------------------------------------------- 1 | TEST_STORAGE=/vz/test 2 | TEST_IMAGE=$TEST_STORAGE/test-image.hdd 3 | TEST_DDXML=$TEST_STORAGE/DiskDescriptor.xml 4 | TEST_MNT=${TEST_STORAGE}_mnt 5 | 6 | [ -e /dev/ploop0 ] || mknod /dev/ploop0 b 182 0 7 | mkdir -p $TEST_STORAGE 8 | mkdir -p $TEST_MNT 9 | 10 | test_cleanup() 11 | { 12 | if [ -f $TEST_DDXML ]; then 13 | ploop umount $TEST_DDXML 2>/dev/null || true 14 | rm -f $TEST_IMAGE* $TEST_DDXML 15 | fi 16 | } 17 | 18 | progress() 19 | { 20 | printf "%s off: %d total: %d (%2d%%)\n" $1 $2 $3 $(($2*100/$3)) 21 | } 22 | -------------------------------------------------------------------------------- /test/ploop-volume.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -x 3 | 4 | TEST_DIR=$1 5 | IMAGES=$TEST_DIR/images/ 6 | PLOOP_VOLUME='unshare -m ./tools/ploop-volume' 7 | 8 | mkdir -p $IMAGES 9 | mkdir -p $TEST_DIR/vol1-mnt 10 | mkdir -p $TEST_DIR/vol2-mnt 11 | mkdir -p $TEST_DIR/snap1-mnt 12 | mkdir -p $TEST_DIR/snap2-mnt 13 | 14 | # Create 15 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1/ -s 10G 16 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 17 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap1/ 18 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1/ $TEST_DIR/snap2/ 19 | $PLOOP_VOLUME delete $TEST_DIR/snap2 20 | $PLOOP_VOLUME delete $TEST_DIR/snap1 21 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 22 | $PLOOP_VOLUME delete $TEST_DIR/vol1 23 | 24 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 11G 25 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap1 26 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap2 27 | $PLOOP_VOLUME delete $TEST_DIR/snap2 28 | $PLOOP_VOLUME delete $TEST_DIR/vol1 29 | $PLOOP_VOLUME delete $TEST_DIR/snap1 30 | 31 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 12G 32 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap1 33 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap2 34 | $PLOOP_VOLUME delete $TEST_DIR/snap1 35 | $PLOOP_VOLUME delete $TEST_DIR/snap2 36 | $PLOOP_VOLUME delete $TEST_DIR/vol1 37 | 38 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 13G 39 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap1 40 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap2 41 | $PLOOP_VOLUME delete $TEST_DIR/vol1 42 | $PLOOP_VOLUME delete $TEST_DIR/snap1 43 | $PLOOP_VOLUME delete $TEST_DIR/snap2 44 | 45 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 14G 46 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap1 47 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap2 48 | $PLOOP_VOLUME delete $TEST_DIR/vol1 49 | $PLOOP_VOLUME delete $TEST_DIR/snap2 50 | $PLOOP_VOLUME delete $TEST_DIR/snap1 51 | 52 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 15G 53 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 54 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1/ $TEST_DIR/snap1/ 55 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap2/ 56 | $PLOOP_VOLUME clone $TEST_DIR/snap1/ $TEST_DIR/vol2/ 57 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 58 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 59 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 60 | $PLOOP_VOLUME delete $TEST_DIR/vol1/ 61 | $PLOOP_VOLUME delete $TEST_DIR/snap2/ 62 | $PLOOP_VOLUME delete $TEST_DIR/vol2/ 63 | $PLOOP_VOLUME delete $TEST_DIR/snap1/ 64 | 65 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 16G 66 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap1 67 | $PLOOP_VOLUME clone $TEST_DIR/snap1 $TEST_DIR/vol2 68 | # A snapshot with child volumes can't be deleted 69 | $PLOOP_VOLUME delete $TEST_DIR/snap1 && exit 1 || true 70 | $PLOOP_VOLUME delete $TEST_DIR/vol2 71 | test -d $TEST_DIR/vol2 && exit 1 || true 72 | $PLOOP_VOLUME clone $TEST_DIR/snap1 $TEST_DIR/vol2 73 | $PLOOP_VOLUME delete $TEST_DIR/vol2 74 | $PLOOP_VOLUME delete $TEST_DIR/snap1 75 | $PLOOP_VOLUME delete $TEST_DIR/vol1 76 | test -d $TEST_DIR/vol1 && exit 1 || true 77 | test -f $IMAGES/vol1 && exit 1 || true 78 | 79 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 17G 80 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 81 | touch $TEST_DIR/vol1-mnt/test_file 82 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap1 83 | mkdir -p $TEST_DIR/snap1-mnt 84 | ./tools/ploop mount $TEST_DIR/snap1/DiskDescriptor.xml -m $TEST_DIR/snap1-mnt 85 | touch $TEST_DIR/snap1-mnt/test_file && exit 1 || true # snapshot mount is read-only 86 | $PLOOP_VOLUME clone $TEST_DIR/snap1 $TEST_DIR/vol2 87 | mkdir -p $TEST_DIR/vol2-mnt 88 | $PLOOP_VOLUME snapshot $TEST_DIR/vol2 $TEST_DIR/snap2 89 | ./tools/ploop mount $TEST_DIR/vol2/DiskDescriptor.xml -m $TEST_DIR/vol2-mnt 90 | test -f $TEST_DIR/vol2-mnt/test_file 91 | touch $TEST_DIR/vol1-mnt/test_file2 92 | test -f $TEST_DIR/vol1-mnt/test_file 93 | test -f $TEST_DIR/vol2-mnt/test_file 94 | 95 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 96 | $PLOOP_VOLUME delete $TEST_DIR/snap2 97 | ./tools/ploop umount $TEST_DIR/vol2/DiskDescriptor.xml 98 | $PLOOP_VOLUME delete $TEST_DIR/vol2 99 | ./tools/ploop umount $TEST_DIR/snap1/DiskDescriptor.xml 100 | $PLOOP_VOLUME delete $TEST_DIR/snap1 101 | $PLOOP_VOLUME delete $TEST_DIR/vol1 102 | 103 | # switch 104 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 10G 105 | $PLOOP_VOLUME clone $TEST_DIR/vol1 $TEST_DIR/vol2 106 | $PLOOP_VOLUME switch $TEST_DIR/vol1 $TEST_DIR/vol2 || true 107 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 108 | $PLOOP_VOLUME switch $TEST_DIR/vol2 $TEST_DIR/vol1 109 | ./tools/ploop mount $TEST_DIR/vol2/DiskDescriptor.xml -m $TEST_DIR/vol2-mnt 110 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 111 | ./tools/ploop umount $TEST_DIR/vol2/DiskDescriptor.xml 112 | $PLOOP_VOLUME delete $TEST_DIR/vol2 113 | $PLOOP_VOLUME delete $TEST_DIR/vol1 114 | 115 | $PLOOP_VOLUME create --image $IMAGES/vol1 $TEST_DIR/vol1 -s 11G 116 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 117 | touch $TEST_DIR/vol1-mnt/snap1 118 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap1 119 | touch $TEST_DIR/vol1-mnt/snap2 120 | ./tools/ploop mount $TEST_DIR/snap1/DiskDescriptor.xml -m $TEST_DIR/snap1-mnt 121 | test -f $TEST_DIR/snap1-mnt/snap1 122 | test -f $TEST_DIR/snap1-mnt/snap2 || true 123 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 124 | 125 | $PLOOP_VOLUME switch $TEST_DIR/vol1 $TEST_DIR/snap1 126 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 127 | test -f $TEST_DIR/vol1-mnt/snap1 128 | test -f $TEST_DIR/vol1-mnt/snap2 || true 129 | touch $TEST_DIR/vol1-mnt/snap2 130 | 131 | $PLOOP_VOLUME switch $TEST_DIR/vol1 $TEST_DIR/snap1 || true 132 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 133 | 134 | $PLOOP_VOLUME snapshot $TEST_DIR/vol1 $TEST_DIR/snap2 135 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 136 | test -f $TEST_DIR/vol1-mnt/snap1 137 | test -f $TEST_DIR/vol1-mnt/snap2 138 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 139 | ./tools/ploop umount $TEST_DIR/snap1/DiskDescriptor.xml 140 | 141 | $PLOOP_VOLUME switch $TEST_DIR/vol1 $TEST_DIR/snap1 142 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 143 | test -f $TEST_DIR/vol1-mnt/snap1 144 | test -f $TEST_DIR/vol1-mnt/snap2 || true 145 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 146 | 147 | $PLOOP_VOLUME switch $TEST_DIR/vol1 $TEST_DIR/snap2 148 | ./tools/ploop mount $TEST_DIR/vol1/DiskDescriptor.xml -m $TEST_DIR/vol1-mnt 149 | test -f $TEST_DIR/vol1-mnt/snap1 150 | test -f $TEST_DIR/vol1-mnt/snap2 151 | ./tools/ploop umount $TEST_DIR/vol1/DiskDescriptor.xml 152 | $PLOOP_VOLUME delete $TEST_DIR/snap2 153 | $PLOOP_VOLUME delete $TEST_DIR/snap1 154 | $PLOOP_VOLUME delete $TEST_DIR/vol1 155 | 156 | exit 0 157 | -------------------------------------------------------------------------------- /test/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | export PATH=$PATH:. 4 | 5 | test-device-grow --online 6 | test-device-grow --offline 7 | test-device-snapshot --online --merge 8 | test-device-snapshot --offline --merge 9 | test-fs-resize --online 10 | test-fs-resize --offline 11 | -------------------------------------------------------------------------------- /test/test-cbt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . ./functions 5 | 6 | V=2 7 | BLOCKSIZE=2048 8 | DELTA=100000 9 | SIZE=65536 10 | DEV=/dev/mapper/ploop0 11 | if [ -f /sys/module/ploop/parameters/large_disk_support ]; then 12 | SIZENEW=60485760000 13 | else 14 | SIZENEW=2147482624 15 | fi 16 | 17 | while [ "${#}" -gt 0 ]; do 18 | case "${1}" in 19 | "-v") 20 | V=${2} 21 | shift 22 | shift 23 | ;; 24 | *) 25 | shift 26 | ;; 27 | esac 28 | done 29 | 30 | test_cleanup 31 | UUID=`uuidgen` 32 | let bs=$BLOCKSIZE/2 33 | dd if=/dev/urandom bs=${bs}k count=1 of=$TEST_STORAGE/data 34 | 35 | ploop init -v $V -b $BLOCKSIZE -s ${SIZE}k -t none $TEST_IMAGE 36 | ploop snapshot -u $UUID -b $UUID $TEST_DDXML 37 | ploop mount -d $DEV $TEST_DDXML 38 | 39 | size=$SIZE 40 | for ((i = 0; i < size; i += bs*DELTA)); do 41 | progress Writing $i $size 42 | dd if=$TEST_STORAGE/data of=$DEV bs=1k seek=$i >/dev/null 2>&1 43 | done 44 | echo 45 | 46 | ploop-cbt show $TEST_DDXML > $TEST_STORAGE/data.cbt.1 47 | 48 | # OFFLINE 49 | echo Offline 50 | ploop umount $TEST_DDXML 51 | ploop-cbt show $TEST_DDXML > $TEST_STORAGE/data.cbt.2 52 | diff -u $TEST_STORAGE/data.cbt.1 $TEST_STORAGE/data.cbt.2 53 | ploop snapshot-delete -u $UUID $TEST_DDXML 54 | ploop-cbt show $TEST_DDXML > $TEST_STORAGE/data.cbt.3 55 | diff -u $TEST_STORAGE/data.cbt.2 $TEST_STORAGE/data.cbt.3 56 | 57 | # ONLINE 58 | echo Onine 59 | UUID=`uuidgen` 60 | ploop mount -d $DEV $TEST_DDXML 61 | ploop-cbt show $TEST_DDXML > $TEST_STORAGE/data.cbt.1 62 | ploop snapshot -u $UUID $TEST_DDXML 63 | ploop-cbt show $TEST_DDXML > $TEST_STORAGE/data.cbt.2 64 | ploop snapshot-delete -u $UUID $TEST_DDXML 65 | ploop-cbt show $TEST_DDXML > $TEST_STORAGE/data.cbt.3 66 | diff -u $TEST_STORAGE/data.cbt.2 $TEST_STORAGE/data.cbt.3 67 | ploop umount $TEST_DDXML 68 | ploop-cbt show $TEST_DDXML > $TEST_STORAGE/data.cbt.4 69 | diff -u $TEST_STORAGE/data.cbt.3 $TEST_STORAGE/data.cbt.4 70 | 71 | if [ "$OFFLINE" = yes ]; then 72 | echo "Offline resize" 73 | ploop umount $TEST_DDXML 74 | else 75 | echo "Online resize" 76 | fi 77 | test_cleanup 78 | 79 | rm -f $TEST_STORAGE/data_out 80 | rm -f $TEST_STORAGE/data 81 | 82 | echo "FINISHED" 83 | -------------------------------------------------------------------------------- /test/test-change-fmt_version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . ./functions 5 | 6 | BLOCKSIZE=2048 7 | SIZE=2048576000 8 | DELTA=1000 9 | FROM=1 10 | TO=2; 11 | NEXT_SNAP=0 12 | 13 | while [ "${#}" -gt 0 ]; do 14 | case "${1}" in 15 | "--from") 16 | FROM="${2}" 17 | shift 18 | shift 19 | ;; 20 | "--to") 21 | TO="${2}" 22 | shift 23 | shift 24 | ;; 25 | "--size") 26 | SIZE="${2}" 27 | shift 28 | shift 29 | ;; 30 | "--snapshot") 31 | SNAPSHOT="${2}" 32 | let SNAP_DELTA=$SIZE/$SNAPSHOT 33 | NEXT_SNAP=$SNAP_DELTA 34 | shift 35 | shift 36 | ;; 37 | *) 38 | shift 39 | ;; 40 | esac 41 | done 42 | 43 | test_cleanup 44 | 45 | ploop init -v ${FROM} -b $BLOCKSIZE -s ${SIZE}k -t ext4 $TEST_IMAGE 46 | ploop mount -d /dev/ploop0 -m $TEST_MNT $TEST_DDXML 47 | 48 | let bs=$BLOCKSIZE/2 49 | dd if=/dev/urandom bs=${bs}k count=1 of=$TEST_STORAGE/data 50 | 51 | for ((i = 0; i < SIZE; i += bs*DELTA)); do 52 | progress Writing $i $SIZE 53 | dd if=$TEST_STORAGE/data of=$TEST_MNT/$i bs=1k >/dev/null 2>&1 54 | if [ "$NEXT_SNAP" != 0 -a $i -gt $NEXT_SNAP ]; then 55 | echo 56 | ploop snapshot $TEST_DDXML 57 | let NEXT_SNAP+=$SNAP_DELTA 58 | fi 59 | done 60 | echo 61 | 62 | ploop umount $TEST_DDXML 63 | 64 | ploop convert -v ${TO} $TEST_DDXML 65 | ploop mount -d /dev/ploop0 -m $TEST_MNT $TEST_DDXML 66 | 67 | echo "Read" 68 | for ((i = 0; i < SIZE; i += bs*DELTA)); do 69 | progress Reading $i $SIZE 70 | cmp $TEST_STORAGE/data $TEST_MNT/$i 71 | done 72 | echo 73 | 74 | umount $TEST_MNT 75 | 76 | fsck -f -C /dev/ploop0p1 77 | 78 | ploop umount $TEST_DDXML 79 | 80 | ploop check -f $TEST_IMAGE 81 | 82 | rm -f $TEST_STORAGE/data 83 | test_cleanup 84 | -------------------------------------------------------------------------------- /test/test-device-grow: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . ./functions 5 | 6 | V=2 7 | BLOCKSIZE=2048 8 | DELTA=100000 9 | SIZE=104857600 10 | DEV=/dev/mapper/ploop1 11 | 12 | if [ -f /sys/module/ploop/parameters/large_disk_support ]; then 13 | SIZENEW=60485760000 14 | else 15 | SIZENEW=2147482624 16 | fi 17 | 18 | while [ "${#}" -gt 0 ]; do 19 | case "${1}" in 20 | "--online") 21 | OFFLINE="no" 22 | shift 23 | ;; 24 | "--offline") 25 | OFFLINE="yes" 26 | shift 27 | ;; 28 | "-v") 29 | V=${2} 30 | shift 31 | shift 32 | ;; 33 | *) 34 | shift 35 | ;; 36 | esac 37 | done 38 | 39 | test_cleanup 40 | 41 | ploop init -v $V -b $BLOCKSIZE -s ${SIZE}k $TEST_IMAGE 42 | ploop mount -d $DEV $TEST_DDXML 43 | 44 | let bs=$BLOCKSIZE/2 45 | dd if=/dev/urandom bs=${bs}k count=1 of=$TEST_STORAGE/data 46 | 47 | size=$SIZE 48 | for ((i = 0; i < size; i += bs*DELTA)); do 49 | progress Writing $i $size 50 | dd if=$TEST_STORAGE/data of=$DEV bs=1k seek=$i >/dev/null 2>&1 51 | done 52 | echo 53 | 54 | if [ "$OFFLINE" = yes ]; then 55 | echo "Offline resize" 56 | ploop umount $TEST_DDXML 57 | else 58 | echo "Online resize" 59 | fi 60 | ploop grow -s ${SIZENEW}k $TEST_DDXML 61 | 62 | if [ "$OFFLINE" != yes ]; then 63 | ploop umount $TEST_DDXML 64 | fi 65 | ploop mount $TEST_DDXML 66 | 67 | size=${SIZENEW} 68 | for ((; i < size; i += bs*DELTA)); do 69 | progress Writing $i $size 70 | dd if=$TEST_STORAGE/data of=$DEV bs=1k seek=$i >/dev/null 2>&1 71 | done 72 | echo 73 | 74 | echo "Read" 75 | for ((i = 0; i < size; i += bs*DELTA)); do 76 | progress Reading $i $size 77 | dd of=$TEST_STORAGE/data_out if=$DEV bs=1k count=$bs skip=$i >/dev/null 2>&1 78 | cmp $TEST_STORAGE/data $TEST_STORAGE/data_out 79 | rm -f $TEST_STORAGE/data_out 80 | done 81 | echo 82 | 83 | test_cleanup 84 | 85 | rm -f $TEST_STORAGE/data_out 86 | rm -f $TEST_STORAGE/data 87 | 88 | echo "FINISHED" 89 | -------------------------------------------------------------------------------- /test/test-device-snapshot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | . functions 5 | 6 | DEV=/dev/mapper/ploop1 7 | 8 | BLOCKSIZE=2048 9 | DELTA=100000 10 | SIZE=104857600 11 | SIZENEW=1048576000 12 | V=2 13 | OFFLINE=yes 14 | RESIZE=yes 15 | 16 | while [ "${#}" -gt 0 ]; do 17 | case "${1}" in 18 | "--online") 19 | OFFLINE="no" 20 | shift 21 | ;; 22 | "--noresize") 23 | RESIZE=no 24 | SIZE=$SIZENEW 25 | shift 26 | ;; 27 | "--size") 28 | SIZENEW="${2}" 29 | shift 30 | shift 31 | ;; 32 | *) 33 | shift 34 | ;; 35 | esac 36 | done 37 | 38 | test_cleanup 39 | 40 | ploop init -t none -v ${V} -b $BLOCKSIZE -s ${SIZE}k $TEST_IMAGE 41 | ploop mount -d $DEV $TEST_DDXML 42 | 43 | let bs=$BLOCKSIZE/2 44 | dd if=/dev/urandom bs=${bs}k count=1 of=$TEST_STORAGE/data 45 | 46 | size=$SIZE 47 | for ((i = 0; i < size; i += bs*DELTA)); do 48 | progress Writing $i $size 49 | dd if=$TEST_STORAGE/data of=$DEV bs=1k seek=$i >/dev/null 2>&1 50 | done 51 | echo 52 | 53 | if [ "$OFFLINE" = yes ]; then 54 | echo "Offline resize" 55 | ploop umount $TEST_DDXML 56 | else 57 | echo "Online resize" 58 | fi 59 | # snapshot + grow 60 | UUID=`uuidgen` 61 | UUID1=`uuidgen` 62 | ploop snapshot -u $UUID $TEST_DDXML 63 | if [ $RESIZE = yes ]; then 64 | ploop grow -s ${SIZENEW}k $TEST_DDXML 65 | fi 66 | 67 | ploop snapshot -u $UUID1 $TEST_DDXML 68 | 69 | if [ "$OFFLINE" != yes ]; then 70 | ploop umount $TEST_DDXML 71 | fi 72 | ploop mount -d $DEV $TEST_DDXML 73 | 74 | size=${SIZENEW} 75 | for ((; i < size; i += bs*DELTA)); do 76 | progress Writing $i $size 77 | dd if=$TEST_STORAGE/data of=$DEV bs=1k seek=$i >/dev/null 2>&1 78 | done 79 | echo 80 | 81 | 82 | if [ "$OFFLINE" = yes ]; then 83 | echo "Offline snapshot delete" 84 | ploop umount $TEST_DDXML 85 | else 86 | echo "Online snapshot delete" 87 | fi 88 | 89 | ploop snapshot-delete -u $UUID $TEST_DDXML 90 | 91 | if [ "$OFFLINE" = yes ]; then 92 | ploop mount -d $DEV $TEST_DDXML 93 | fi 94 | 95 | echo "Read" 96 | for ((i = 0; i < size; i += bs*DELTA)); do 97 | progress Reading $i $size 98 | dd of=$TEST_STORAGE/data_out if=$DEV bs=1k count=$bs skip=$i >/dev/null 2>&1 99 | cmp $TEST_STORAGE/data $TEST_STORAGE/data_out 100 | rm -f $TEST_STORAGE/data_out 101 | done 102 | echo 103 | 104 | test_cleanup 105 | 106 | rm -f $TEST_STORAGE/data_out 107 | rm -f $TEST_STORAGE/data 108 | 109 | echo "FINISHED" 110 | -------------------------------------------------------------------------------- /test/test-fs-resize: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . ./functions 5 | 6 | V=2 7 | BLOCKSIZE=2048 8 | DELTA=10000 9 | SIZE=1048576000 10 | if [ -f /sys/module/ploop/parameters/large_disk_support ]; then 11 | SIZENEW=10485760000 12 | else 13 | SIZENEW=2147482624 14 | fi 15 | 16 | while [ "${#}" -gt 0 ]; do 17 | case "${1}" in 18 | "--online") 19 | OFFLINE="no" 20 | shift 21 | ;; 22 | "--offline") 23 | OFFLINE="yes" 24 | shift 25 | ;; 26 | "-v") 27 | V=${2} 28 | shift 29 | shift 30 | ;; 31 | *) 32 | shift 33 | ;; 34 | esac 35 | done 36 | 37 | test_cleanup 38 | 39 | ploop init -v $V -b $BLOCKSIZE -s ${SIZE}k -t ext4 $TEST_IMAGE 40 | ploop mount -d /dev/ploop0 -m $TEST_MNT $TEST_DDXML 41 | 42 | let bs=$BLOCKSIZE/2 43 | dd if=/dev/urandom bs=${bs}k count=1 of=$TEST_STORAGE/data 44 | 45 | size=$SIZE 46 | for ((i = 0; i < size; i += bs*DELTA)); do 47 | progress Writing $i $size 48 | dd if=$TEST_STORAGE/data of=$TEST_MNT/$i bs=1k >/dev/null 2>&1 49 | done 50 | echo 51 | 52 | if [ "$OFFLINE" = yes ]; then 53 | echo "Offline resize" 54 | ploop umount $TEST_DDXML 55 | else 56 | echo "Online resize" 57 | fi 58 | ploop resize -s ${SIZENEW}k $TEST_DDXML 59 | 60 | if [ "$OFFLINE" != yes ]; then 61 | ploop umount $TEST_DDXML 62 | fi 63 | ploop mount -d /dev/ploop0 -m $TEST_MNT $TEST_DDXML 64 | 65 | size=${SIZENEW} 66 | for ((; i < size; i += bs*DELTA)); do 67 | progress Writing $i $size 68 | dd if=$TEST_STORAGE/data of=$TEST_MNT/$i bs=1k >/dev/null 2>&1 69 | done 70 | echo 71 | 72 | ploop umount $TEST_DDXML 73 | ploop mount -d /dev/ploop0 $TEST_DDXML 74 | 75 | fsck -f /dev/ploop0p1 76 | 77 | ploop umount $TEST_DDXML 78 | ploop mount -d /dev/ploop0 -m $TEST_MNT $TEST_DDXML 79 | 80 | rm -f $TEST_MNT/* || true 81 | 82 | ploop resize -s ${SIZE}k $TEST_DDXML 83 | 84 | test_cleanup 85 | 86 | rm -f $TEST_STORAGE/data_out 87 | rm -f $TEST_STORAGE/data 88 | 89 | echo "FINISHED" 90 | -------------------------------------------------------------------------------- /test/test-pcopy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import libploop 3 | import shutil 4 | import io 5 | import os 6 | import socket 7 | import time 8 | import subprocess as sp 9 | import unittest 10 | import hashlib 11 | import sys 12 | 13 | sleep_sec = 3 14 | bitmap_name = "262178fe-49d7-4c8b-b47c-4c0799dbf02a" 15 | image_fmt = "qcow2" 16 | #image_fmt = "ploop" 17 | 18 | def hashfile(afile, hasher, blocksize=65536): 19 | buf = afile.read(blocksize) 20 | while len(buf) > 0: 21 | hasher.update(buf) 22 | buf = afile.read(blocksize) 23 | print (hasher.hexdigest()) 24 | return hasher.hexdigest() 25 | 26 | def get_devname(): 27 | return '/dev/mapper/ploop1234' 28 | 29 | def create_data(): 30 | print("Fill data...") 31 | ret = sp.call(['/bin/dd', 'if=/dev/urandom', "of="+get_storage()+get_data(), 'bs=1M', 'count=512']) 32 | if ret != 0: 33 | raise Exception("Cannot create data file") 34 | print("Fill done") 35 | 36 | def start_image_filller(): 37 | pid = os.fork() 38 | if pid == 0: 39 | os.execl('/bin/dd', 'dd', "if="+get_storage()+get_data(), "of="+get_mnt()+get_data(), 'bs=1M', 'oflag=direct') 40 | os._exit(1) 41 | else: 42 | print("Start filler pid=%d" % pid) 43 | time.sleep(sleep_sec) 44 | return pid 45 | 46 | def start_pcopy_receiver(fname, fd): 47 | print("Start receiver") 48 | t = libploop.ploopcopy_thr_receiver(fname, fd) 49 | t.start() 50 | return t 51 | 52 | def get_storage(): 53 | return '/vz/test/' 54 | 55 | def get_data(): 56 | return 'data.dat' 57 | 58 | def get_image(): 59 | return os.path.join(get_storage(), "test.hds") 60 | 61 | def get_out(): 62 | return os.path.join(get_storage(), "out.hds"); 63 | 64 | def get_ddxml(): 65 | if image_fmt == "ploop": 66 | return os.path.join(get_storage(), 'DiskDescriptor.xml') 67 | return get_image() 68 | 69 | def get_mnt(): 70 | return get_storage() + "mnt/" 71 | 72 | def ploop_create(img): 73 | ret = sp.call(["ploop", "init", "-s10g", "-T", image_fmt, img]) 74 | if ret != 0: 75 | raise Exception("failed to create image") 76 | 77 | def ploop_mount(ddxml): 78 | ret = sp.call(["ploop", "mount", "-m", get_mnt(), ddxml]) 79 | if ret != 0: 80 | raise Exception("failed to mount image") 81 | 82 | def ploop_umount(ddxml): 83 | ret = sp.run(["ploop", "list"], text=True, stdout=sp.PIPE) 84 | if ddxml.find("DiskDescriptor.xml") == -1: 85 | if ret.returncode == 0 and ret.stdout.find(ddxml) == -1: 86 | return 0 87 | else: 88 | if ret.returncode == 0 and len(ret.stdout) == 0: 89 | return 0 90 | return sp.call(["ploop", "umount", ddxml]) 91 | 92 | def dump_cbt(img): 93 | fout = img + ".cbt" 94 | 95 | with open(fout, "w") as f: 96 | p = sp.Popen(["ploop-cbt", "show", img], stdout=f) 97 | p.wait() 98 | return fout; 99 | 100 | def do_ploop_copy(ddxml, fd): 101 | 102 | print("do_ploop_copy") 103 | ploop_mount(ddxml) 104 | pc = libploop.ploopcopy(ddxml, fd); 105 | 106 | pid = start_image_filller() 107 | 108 | print("Start copy") 109 | pc.copy_start() 110 | n = 0 111 | 112 | while True: 113 | print("Iter:", n) 114 | transferred = pc.copy_next_iteration() 115 | print("transferred:", transferred) 116 | try: 117 | p, s = os.waitpid(pid, os.WNOHANG) 118 | if p == pid: 119 | break 120 | except: 121 | break; 122 | time.sleep(sleep_sec) 123 | n = n + 1 124 | try: 125 | os.waitpid(pid, 0) 126 | except: 127 | print("waitpid"); 128 | 129 | print("Stop copy") 130 | pc.copy_stop() 131 | print("Umount") 132 | ploop_umount(ddxml) 133 | 134 | def get_qcow_info(img): 135 | ret = sp.run(["/usr/bin/qemu-img", "info", img], text=True, stdout=sp.PIPE) 136 | if ret.returncode != 0: 137 | raise Exception("failed to get info from", img) 138 | print(ret.stdout) 139 | return ret.stdout 140 | 141 | def check(t): 142 | print("Check MD5"); 143 | s = open(get_storage()+get_data(), 'rb') 144 | src = hashfile(s, hashlib.md5()) 145 | 146 | ploop_mount(t.ddxml) 147 | d = open(get_mnt()+get_data(), 'rb') 148 | dst = hashfile(d, hashlib.md5()) 149 | s.close() 150 | d.close() 151 | ploop_umount(t.ddxml) 152 | t.assertEqual(src, dst) 153 | 154 | if image_fmt == "qcow2": 155 | ploop_mount(t.out) 156 | o = open(get_mnt()+get_data(), 'rb') 157 | out = hashfile(o, hashlib.md5()) 158 | o.close() 159 | ploop_umount(t.out) 160 | t.assertEqual(src, out) 161 | print("Check MD5 [Ok]"); 162 | 163 | def check_qcow_cbt(t): 164 | print("Check bitmap in images", t.ddxml, "and", t.out) 165 | x = get_qcow_info(t.ddxml) 166 | if x.find(bitmap_name) == -1: 167 | raise Exception("Not found bitmap in", t.ddxml) 168 | x = get_qcow_info(t.out) 169 | if x.find(bitmap_name) == -1: 170 | raise Exception("Not found bitmap in", t.out) 171 | 172 | def clean_all(): 173 | if os.path.exists(get_image()): 174 | ploop_umount(get_image()) 175 | if os.path.exists(get_out()): 176 | ploop_umount(get_out()) 177 | if os.path.exists(get_storage()): 178 | shutil.rmtree(get_storage()) 179 | 180 | class testPcopy(unittest.TestCase): 181 | def setUp(self): 182 | clean_all() 183 | 184 | if not os.path.exists(get_storage()): 185 | os.mkdir(get_storage()) 186 | if not os.path.exists(get_mnt()): 187 | os.mkdir(get_mnt()) 188 | 189 | create_data() 190 | ploop_create(get_image()) 191 | self.out = get_out() 192 | self.ddxml = get_ddxml() 193 | 194 | def tearDown(self): 195 | print("tearDown") 196 | clean_all() 197 | 198 | def test_remote(self): 199 | print("Start remote") 200 | 201 | parent, child = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) 202 | self.rcv_thr = start_pcopy_receiver(self.out, child.fileno()) 203 | 204 | if do_ploop_copy(self.ddxml, parent.fileno()): 205 | return 206 | parent.close() 207 | child.close() 208 | check(self) 209 | 210 | def test_cbt(self): 211 | print("Start local CBT dst=%s" % self.out) 212 | 213 | if image_fmt == "qcow2": 214 | ret = sp.call(["/usr/bin/qemu-img", "bitmap", "--add", self.ddxml, bitmap_name]) 215 | if ret != 0: 216 | raise Exception("Failed to add bitmap") 217 | else: 218 | ret = sp.call(["ploop", "snapshot", "-u"+bitmap_name, "-b"+bitmap_name, self.ddxml]) 219 | if ret != 0: 220 | raise Exception("Cannot create snapshot") 221 | sp.call(["ploop", "snapshot-delete", "-u"+bitmap_name, self.ddxml]) 222 | 223 | parent, child = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) 224 | self.rcv_thr = start_pcopy_receiver(self.out, child.fileno()) 225 | 226 | if do_ploop_copy(self.ddxml, parent.fileno()): 227 | return 228 | parent.close(); 229 | child.close() 230 | 231 | print("Check CBT"); 232 | if image_fmt == "qcow2": 233 | check_qcow_cbt(self) 234 | else: 235 | f1 = dump_cbt(get_image()) 236 | f2 = dump_cbt(self.out) 237 | ret = sp.call(["diff", "-u", f1, f2]) 238 | if ret != 0: 239 | raise Exception("Check CBT failed") 240 | print("Check CBT [Ok]"); 241 | check(self) 242 | 243 | """ 244 | def test_local(self): 245 | print("Start local") 246 | 247 | f = open(self.out, 'wb') 248 | if do_ploop_copy(self.ddxml, f.fileno()): 249 | return 250 | check(self) 251 | f.close() 252 | 253 | """ 254 | if __name__ == '__main__': 255 | unittest.main() 256 | -------------------------------------------------------------------------------- /test/test-snapshot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | . functions 5 | 6 | SID1=77167520-004e-4423-a6f8-026bbbce0a00 7 | SID2=77167520-004e-4423-a6f8-026bbbce0a01 8 | BLOCKSIZE=2048 9 | SIZE=819200 10 | SIZE_MAX=62914560 11 | V=2 12 | OFFLINE=yes 13 | while [ "${#}" -gt 0 ]; do 14 | case "${1}" in 15 | "--online") 16 | OFFLINE="no" 17 | shift 18 | ;; 19 | "--offline") 20 | OFFLINE="yes" 21 | shift 22 | ;; 23 | "--merge") 24 | MERGE="yes" 25 | shift 26 | ;; 27 | "--size") 28 | SIZE="${2}" 29 | shift 30 | shift 31 | ;; 32 | "-v1") 33 | V=1 34 | SIZE_MAX=2097152 35 | shift 36 | ;; 37 | "-v2") 38 | V=2 39 | shift 40 | ;; 41 | 42 | *) 43 | shift 44 | ;; 45 | esac 46 | done 47 | 48 | test_cleanup 49 | 50 | 51 | B=256 52 | check_md5() 53 | { 54 | M1_n=$(md5sum $TEST_MNT/file.replace) 55 | if [ "$M1" != "$M1_n" ]; then 56 | echo "ERROR md5 $TEST_MNT/file.replace mismatch" 57 | echo $M1 58 | echo $M1_n 59 | exit 1 60 | fi 61 | M2_n=$(md5sum $TEST_MNT/file.new) 62 | if [ "$M2" != "$M2_n" ]; then 63 | echo "md5 $TEST_MNT/file.new mismatch" 64 | echo $M2 65 | echo $M2_n 66 | 67 | exit 1 68 | fi 69 | } 70 | 71 | check_size() 72 | { 73 | local total=0 bs=0 alloc=0 delta=0 74 | 75 | total=`stat -c "%s" $1` 76 | bs=`stat -c "%B" $1` 77 | alloc=`stat -c "%b" $1` 78 | 79 | let alloc*=$bs 80 | 81 | delta=$total 82 | let delta=$total-$alloc 83 | echo "Delta $1 delta: $delta total: $total allocated: $alloc" 84 | if [ $delta -gt 134217728 ]; then 85 | echo "ERROR check image $1 size failed: total $total allocated $alloc delta $delta > 134217728" 86 | exit 1 87 | fi 88 | } 89 | 90 | reuse_holes() 91 | { 92 | echo "reuse_holes_offline" 93 | ploop init -v ${V} -b $BLOCKSIZE -s ${SIZE}k $TEST_IMAGE >/dev/null 2>&1 94 | for ((i=$SIZE; i<$SIZE_MAX;)) do 95 | echo Size $i 96 | ploop mount -d /dev/ploop0 -m $TEST_MNT $TEST_DDXML >/dev/null 97 | rm -f $TEST_MNT/file.* 98 | 99 | dd if=/dev/urandom bs=1M count=$B of=$TEST_MNT/file.replace >/dev/null 2>&1 100 | 101 | rm -f $TEST_MNT/file.rm 102 | ploop balloon discard --defrag $TEST_DDXML >/dev/null 103 | 104 | ploop snapshot -u $SID1 $TEST_DDXML >/dev/null 105 | dd if=/dev/urandom bs=1M count=$B of=$TEST_MNT/file.replace >/dev/null 2>&1 106 | M1=$(md5sum $TEST_MNT/file.replace) 107 | dd if=/dev/urandom bs=1M count=$B of=$TEST_MNT/file.new >/dev/null 2>&1 108 | M2=$(md5sum $TEST_MNT/file.new) 109 | 110 | if [ "$OFFLINE" = "yes" ]; then 111 | ploop umount $TEST_DDXML >/dev/null 112 | else 113 | ploop snapshot -u $SID2 $TEST_DDXML >/dev/null 114 | fi 115 | 116 | let s=$i*2 117 | ploop grow -s ${s}k $TEST_DDXML >/dev/null 2>&1 118 | ploop snapshot-delete -u $SID1 $TEST_DDXML >/dev/null 119 | if [ "$OFFLINE" = "yes" ]; then 120 | ploop mount -d /dev/ploop0 -m $TEST_MNT $TEST_DDXML >/dev/null 121 | check_md5 122 | else 123 | check_md5 124 | dd if=/dev/urandom bs=1M count=$B of=$TEST_MNT/file.new >/dev/null 2>&1 125 | M2=$(md5sum $TEST_MNT/file.new) 126 | ploop snapshot-delete -u $SID2 $TEST_DDXML >/dev/null 127 | check_md5 128 | fi 129 | let "i <<= 2" 130 | ploop grow -s ${i}k $TEST_DDXML >/dev/null 2>&1 131 | check_md5 132 | ploop umount $TEST_DDXML >/dev/null 133 | 134 | check_size "$TEST_IMAGE" 135 | done 136 | } 137 | 138 | reuse_holes 139 | 140 | test_cleanup 141 | echo 142 | echo "FINISHED [OK]" 143 | -------------------------------------------------------------------------------- /test/test-sparse: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . ./functions 5 | 6 | V=2 7 | BLOCKSIZE=2048 8 | DELTA=100000 9 | SIZE=65536 10 | if [ -f /sys/module/ploop/parameters/large_disk_support ]; then 11 | SIZENEW=60485760000 12 | else 13 | SIZENEW=2147482624 14 | fi 15 | 16 | while [ "${#}" -gt 0 ]; do 17 | case "${1}" in 18 | "-v") 19 | V=${2} 20 | shift 21 | shift 22 | ;; 23 | *) 24 | shift 25 | ;; 26 | esac 27 | done 28 | 29 | test_cleanup 30 | 31 | ploop init -s 1T -t none $TEST_IMAGE 32 | ploop mount -d /dev/ploop0 $TEST_DDXML 33 | dd if=/dev/urandom of=/dev/ploop0 bs=1M count=4 >/dev/null 34 | dd if=/dev/urandom of=/dev/ploop0 bs=1M count=1 seek=1022976 >/dev/null 35 | ploop umount $TEST_DDXML 36 | fallocate -p -o 1458176 -l 2097152 $TEST_IMAGE 37 | fallocate -p -o 5455872 -l 4096 $TEST_IMAGE 38 | fallocate -p -o 6291456 -l 4096 $TEST_IMAGE 39 | exit 0 40 | m1=`md5sum $TEST_IMAGE` 41 | ploop mount $TEST_DDXML 42 | ploop umount $TEST_DDXML 43 | m2=`md5sum $TEST_IMAGE` 44 | 45 | if [ "$m1" != "$m2" ]; then 46 | echo "FAILED md5 mismatch $m1 != $m2" 47 | exit 1 48 | fi 49 | 50 | ploop mount $TEST_DDXML 51 | ploop umount $TEST_DDXML 52 | m2=`md5sum $TEST_IMAGE` 53 | if [ "$m1" != "$m2" ]; then 54 | echo "FAILED md5 mismatch $m1 != $m2" 55 | exit 1 56 | fi 57 | exit 0 58 | test_cleanup 59 | echo "FINISHED" 60 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | .depend 2 | ploop.8 3 | common.o 4 | ploop.o 5 | ploop-stat.o 6 | ploop-check.o 7 | ploop-merge.o 8 | ploop-copy.o 9 | ploop-grow.o 10 | ploop-balloon.o 11 | ploop-snapshot.o 12 | ploop 13 | ploop-balloon 14 | -------------------------------------------------------------------------------- /tools/Makefile: -------------------------------------------------------------------------------- 1 | PLOOPROOT = .. 2 | 3 | include $(PLOOPROOT)/Makefile.inc 4 | 5 | PROGS = ploop \ 6 | ploop-e4defrag \ 7 | ploop-balloon \ 8 | ploop-cbt \ 9 | ploop-test \ 10 | ploop-volume 11 | 12 | BINS = $(PROGS) 13 | 14 | MAN8 = ploop.8 15 | MANS = $(MAN8) 16 | 17 | PLOOP_OBJS = \ 18 | common.o \ 19 | ploop-check.o \ 20 | ploop-grow.o \ 21 | ploop-merge.o \ 22 | ploop-stat.o \ 23 | ploop-copy.o \ 24 | ploop-snapshot.o 25 | 26 | OBJS = $(addsuffix .o,$(PROGS)) $(PLOOP_OBJS) 27 | SOURCES = $(OBJS:.o=.c) 28 | CFLAGS += -I../lib -I ../include 29 | LDFLAGS += -L../lib 30 | LDLIBS = -lploop -ljson-c 31 | 32 | define do_rebrand 33 | sed -e "s,@PRODUCT_NAME_SHORT@,$(PRODUCT_NAME_SHORT),g" -i $(1) || exit 1; 34 | sed -e "s,@PRODUCT_URL@,$(PRODUCT_URL),g" -i $(1) || exit 1; 35 | endef 36 | 37 | all: $(PROGS) $(MANS) 38 | .PHONY: all 39 | 40 | $(PROGS): $(LIBPLOOP) 41 | 42 | %: %.o common.o 43 | $(E) " LD " $@ 44 | $(Q) $(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ 45 | 46 | ploop: $(PLOOP_OBJS) 47 | 48 | .depend: $(SOURCES) 49 | -include .depend 50 | 51 | %.8: %.8.in macros.tmac 52 | $(E) " GEN " $@ 53 | $(Q) cat macros.tmac $< > $@ 54 | 55 | install-man: $(MANS) 56 | $(E) " INSTALL " $(MAN8) 57 | $(Q) $(INSTALL) -d $(DESTDIR)$(MAN8DIR) 58 | $(Q) $(INSTALL) -m 644 $(MAN8) $(DESTDIR)$(MAN8DIR)/ 59 | $(Q) $(call do_rebrand,$(DESTDIR)$(MAN8DIR)/$(MAN8)) 60 | 61 | install-bin: $(BINS) 62 | $(Q) $(INSTALL) -d $(DESTDIR)$(USRSBINDIR) 63 | $(E) " INSTALL " $(BINS) 64 | $(Q) $(INSTALL) -m 755 $(BINS) $(DESTDIR)$(USRSBINDIR)/ 65 | 66 | install: install-bin install-man 67 | .PHONY: install install-bin install-man 68 | 69 | clean: 70 | $(E) " CLEAN " 71 | $(Q) rm -f $(PROGS) $(OBJS) $(MANS) .depend 72 | .PHONY: clean 73 | 74 | distclean: clean 75 | $(Q) rm -f .depend 76 | .PHONY: distclean 77 | -------------------------------------------------------------------------------- /tools/common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "ploop.h" 27 | 28 | int parse_size(const char *opt, off_t *sz, const char *name) 29 | { 30 | __u64 val; 31 | char * endptr; 32 | 33 | val = strtoul(opt, &endptr, 0); 34 | 35 | if (opt == endptr) 36 | goto err; 37 | 38 | if (strlen(endptr) > 1) 39 | goto err; 40 | 41 | switch (*endptr) { 42 | case 'T': case 't': 43 | if (val >= ~0ULL/(1024ULL*1024*1024*1024/SECTOR_SIZE)) 44 | goto large; 45 | val *= 1024ULL*1024*1024*1024/SECTOR_SIZE; 46 | *sz = val; 47 | break; 48 | case 'G': case 'g': 49 | if (val >= ~0ULL/(1024*1024*1024/SECTOR_SIZE)) 50 | goto large; 51 | val *= 1024*1024*1024/SECTOR_SIZE; 52 | *sz = val; 53 | break; 54 | case 'M': case 'm': 55 | if (val >= ~0ULL/(1024*1024/SECTOR_SIZE)) 56 | goto large; 57 | val *= 1024*1024/SECTOR_SIZE; 58 | *sz = val; 59 | break; 60 | case 'K': case 'k': 61 | if (val >= ~0ULL/(1024/SECTOR_SIZE)) 62 | goto large; 63 | val *= 1024/SECTOR_SIZE; 64 | *sz = val; 65 | break; 66 | case 0: 67 | *sz = (off_t)val; 68 | break; 69 | default: 70 | goto err; 71 | } 72 | 73 | if (val >= (0xffffffffULL << PLOOP1_SECTOR_LOG)) 74 | goto large; 75 | 76 | return 0; 77 | 78 | err: 79 | fprintf(stderr, "ERROR: Invalid argument for option %s: %s\n", name, opt); 80 | return -1; 81 | 82 | large: 83 | fprintf(stderr, "ERROR: Too large value for option %s: %s\n", name, opt); 84 | return -1; 85 | } 86 | 87 | int parse_format_opt(const char *opt) 88 | { 89 | if (strcmp(opt, "raw") == 0) 90 | return PLOOP_RAW_MODE; 91 | else if ((strcmp(opt, "ploop1") == 0) || 92 | (strcmp(opt, "expanded") == 0)) 93 | return PLOOP_EXPANDED_MODE; 94 | else if (strcmp(opt, "preallocated") == 0) 95 | return PLOOP_EXPANDED_PREALLOCATED_MODE; 96 | 97 | fprintf(stderr, "Bad -f argument: %s\n", opt); 98 | return -1; 99 | } 100 | 101 | char *parse_uuid(const char *opt) 102 | { 103 | char buf[] = "{fbcdf284-5345-416b-a589-7b5fcaa87673}"; 104 | const char *id = opt; 105 | 106 | if (!id) 107 | goto err; 108 | if (id[0] != '{' && strlen(id) == 36) { 109 | /* as a courtesy, add missing brackets */ 110 | memcpy(buf+1, id, 36); 111 | id = buf; 112 | } 113 | if (!is_valid_guid(id)) 114 | goto err; 115 | 116 | return strdup(id); 117 | 118 | err: 119 | fprintf(stderr, "Incorrect uuid specified: %s\n", opt); 120 | return NULL; 121 | 122 | } 123 | 124 | int is_xml_fname(const char *fname) 125 | { 126 | const char *p; 127 | if (fname == NULL) 128 | return 0; 129 | 130 | p = strrchr(fname, '.'); 131 | return (p != NULL && (!strcmp(p, ".xml") || !strcmp(p, ".qcow2"))); 132 | } 133 | 134 | static void cancel_callback(int sig) 135 | { 136 | ploop_cancel_operation(); 137 | } 138 | 139 | void init_signals(void) 140 | { 141 | struct sigaction act = {}; 142 | 143 | sigemptyset(&act.sa_mask); 144 | act.sa_handler = cancel_callback; 145 | sigaction(SIGTERM, &act, NULL); 146 | sigaction(SIGINT, &act, NULL); 147 | sigaction(SIGHUP, &act, NULL); 148 | } 149 | -------------------------------------------------------------------------------- /tools/common.h: -------------------------------------------------------------------------------- 1 | #ifndef __COMMON_H_ 2 | #define __COMMON_H_ 3 | 4 | int parse_size(const char *opt, off_t *sz, const char *name); 5 | int parse_format_opt(const char *opt); 6 | char *parse_uuid(const char *opt); 7 | int is_xml_fname(const char *fname); 8 | void init_signals(void); 9 | 10 | #define USAGE_FORMATS "{ raw | ploop1 | expanded | preallocated }" 11 | #define USAGE_VERSIONS "{ 1 | 2 } (default 2, if supported)" 12 | 13 | #endif // __COMMON_H_ 14 | -------------------------------------------------------------------------------- /tools/macros.tmac: -------------------------------------------------------------------------------- 1 | .\" Stolen from groff's an-ext.tmac as of 2012-Mar-05 2 | .nr mS 0 3 | . 4 | . 5 | .\" Declare start of command synopsis. Sets up hanging indentation. 6 | .de SY 7 | . ie !\\n(mS \{\ 8 | . nh 9 | . nr mS 1 10 | . nr mA \\n(.j 11 | . ad l 12 | . nr mI \\n(.i 13 | . \} 14 | . el \{\ 15 | . br 16 | . ns 17 | . \} 18 | . 19 | . nr mT \w'\fB\\$1\fP\ ' 20 | . HP \\n(mTu 21 | . B "\\$1" 22 | .. 23 | . 24 | . 25 | .\" End of command synopsis. Restores adjustment. 26 | .de YS 27 | . in \\n(mIu 28 | . ad \\n(mA 29 | . hy \\n(HY 30 | . nr mS 0 31 | .. 32 | . 33 | . 34 | .\" Declare optional option. 35 | .de OP 36 | . ie \\n(.$-1 \ 37 | . RI "[\fB\\$1\fP" "\ \\$2" "]" 38 | . el \ 39 | . RB "[" "\\$1" "]" 40 | .. 41 | . 42 | . 43 | .\" Start example. 44 | .de EX 45 | . nr mE \\n(.f 46 | . nf 47 | . nh 48 | . ft CW 49 | .. 50 | . 51 | . 52 | .\" End example. 53 | .de EE 54 | . ft \\n(mE 55 | . fi 56 | . hy \\n(HY 57 | .. 58 | -------------------------------------------------------------------------------- /tools/ploop-balloon.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "ploop.h" 35 | #include "common.h" 36 | 37 | static char *device; /* ploop device name (e.g. /dev/ploop0) */ 38 | static char *mount_point; 39 | static char *disk_descriptor; 40 | static int force; /* do not flock hidden balloon */ 41 | 42 | #define GET_DD(argc, argv) \ 43 | do { \ 44 | if (argc == 1 && is_xml_fname(argv[0])) { \ 45 | disk_descriptor = argv[0]; \ 46 | argc--; argv++; \ 47 | } \ 48 | } while (0) 49 | 50 | static int fill_opts(void) 51 | { 52 | static char _device[PATH_MAX]; 53 | static char _mount_point[PATH_MAX]; 54 | 55 | if (device && mount_point) 56 | return 0; 57 | 58 | if (disk_descriptor) { 59 | int ret; 60 | struct ploop_disk_images_data *di; 61 | 62 | ret = ploop_open_dd(&di, disk_descriptor); 63 | if (ret) 64 | return ret; 65 | 66 | ret = ploop_get_dev(di, _device, sizeof(_device)); 67 | ploop_close_dd(di); 68 | if (ret < 0) 69 | return SYSEXIT_PARAM; 70 | 71 | if (ret == 1) { 72 | fprintf(stderr, "The image is not mounted\n"); 73 | return SYSEXIT_PARAM; 74 | } 75 | 76 | device = _device; 77 | } 78 | 79 | if (!device && mount_point) { 80 | if (ploop_get_dev_by_mnt(mount_point, _device, sizeof(_device))) { 81 | fprintf(stderr, "Unable to find ploop device by %s\n", mount_point); 82 | return SYSEXIT_PARAM; 83 | } 84 | device = _device; 85 | return 0; 86 | } 87 | 88 | if (!mount_point && device) { 89 | if (ploop_get_mnt_by_dev(device, _mount_point, sizeof(_mount_point))) { 90 | fprintf(stderr, "Unable to find mount point for %s\n", device); 91 | return SYSEXIT_PARAM; 92 | } 93 | mount_point = _mount_point; 94 | return 0; 95 | } 96 | 97 | fprintf(stderr, "Error: one of -d, -m or DiskDescriptor.xml argument is required\n"); 98 | return 1; 99 | } 100 | 101 | static void usage_summary(void) 102 | { 103 | fprintf(stderr, "Usage: ploop-balloon { change discard | show } ...\n" 104 | "Use \"ploop-balloon cmd\" to get more info about cmd\n" 105 | ); 106 | } 107 | 108 | static void usage_show(void) 109 | { 110 | fprintf(stderr, "Usage: ploop-balloon show [-f] {-d DEVICE | -m MOUNT_POINT | DiskDescriptor.xml}\n" 111 | " DEVICE := ploop device, e.g. /dev/ploop0\n" 112 | " MOUNT_POINT := path where fs living on ploop device mounted to\n" 113 | " -f - do not flock hidden balloon\n" 114 | "Action: show current ploop balloon size\n" 115 | ); 116 | } 117 | 118 | static int pb_show(int argc, char **argv) 119 | { 120 | int i, ret; 121 | struct stat st; 122 | 123 | while ((i = getopt(argc, argv, "fd:m:")) != EOF) { 124 | switch (i) { 125 | case 'f': 126 | force = 1; 127 | break; 128 | case 'd': 129 | device = optarg; 130 | break; 131 | case 'm': 132 | mount_point = optarg; 133 | break; 134 | default: 135 | usage_show(); 136 | return SYSEXIT_PARAM; 137 | } 138 | } 139 | 140 | argc -= optind; 141 | argv += optind; 142 | 143 | GET_DD(argc, argv); 144 | if (argc != 0 || fill_opts()) { 145 | usage_show(); 146 | return SYSEXIT_PARAM; 147 | } 148 | 149 | ret = get_balloon(mount_point, &st, NULL); 150 | if (ret) 151 | return ret; 152 | fprintf(stdout, "Current size of hidden balloon is %llu bytes\n", 153 | (unsigned long long) st.st_size); 154 | return 0; 155 | } 156 | 157 | static void usage_change(void) 158 | { 159 | fprintf(stderr, "Usage: ploop-balloon change -s SIZE {-d DEVICE | -m MOUNT_POINT | DiskDescriptor.xml}\n" 160 | " SIZE := NUMBER[kmg] (new size of balloon)\n" 161 | " DEVICE := ploop device, e.g. /dev/ploop0\n" 162 | " MOUNT_POINT := path where fs living on ploop device mounted to\n" 163 | "Action: inflate or truncate hidden balloon (dependently on new_size vs. old_size)\n" 164 | ); 165 | } 166 | 167 | static int pb_change(int argc, char **argv) 168 | { 169 | int fd; 170 | int i, ret; 171 | off_t new_size = 0; 172 | int new_size_set = 0; 173 | 174 | while ((i = getopt(argc, argv, "s:d:m:")) != EOF) { 175 | switch (i) { 176 | case 's': 177 | /* NB: currently, new_size is in 'sector' units */ 178 | if (parse_size(optarg, &new_size, "-s")) { 179 | usage_change(); 180 | return SYSEXIT_PARAM; 181 | } 182 | new_size_set++; 183 | break; 184 | case 'd': 185 | device = optarg; 186 | break; 187 | case 'm': 188 | mount_point = optarg; 189 | break; 190 | default: 191 | usage_change(); 192 | return SYSEXIT_PARAM; 193 | } 194 | } 195 | 196 | argc -= optind; 197 | argv += optind; 198 | 199 | GET_DD(argc, argv); 200 | if (argc != 0 || !new_size_set || fill_opts()) { 201 | usage_change(); 202 | return SYSEXIT_PARAM; 203 | } 204 | ret = get_balloon(mount_point, NULL, &fd); 205 | if (ret) 206 | return ret; 207 | 208 | return ploop_balloon_change_size(device, fd, new_size); 209 | } 210 | 211 | static void usage_discard(void) 212 | { 213 | fprintf(stderr, "Usage: ploop-balloon discard {-d DEVICE | -m MOUNT_POINT | DiskDescriptor.xml}\n" 214 | " { [--automount] [--to-free SIZE] [--min-block MIN_SIZE]\n" 215 | " [--defrag] | [--stat]}\n" 216 | " DEVICE := ploop device, e.g. /dev/ploop0\n" 217 | " MOUNT_POINT := path where fs living on ploop device mounted to\n" 218 | " SIZE := NUMBER[KMGT] (maximum space to free)\n" 219 | " MIN_SIZE := NUMBER[KMGT] (minimum size of a linear slice to be freed)\n" 220 | "Action: discard unused blocks from the image.\n" 221 | ); 222 | } 223 | 224 | static void print_discard_stat(struct ploop_discard_stat *stat) 225 | { 226 | fprintf(stdout, "Balloon size: %8lluMB\n", 227 | (unsigned long long)stat->balloon_size >> 20); 228 | fprintf(stdout, "Data size: %8lluMB\n", 229 | (unsigned long long)stat->data_size >> 20); 230 | fprintf(stdout, "Ploop size: %8lluMB\n", 231 | (unsigned long long)stat->ploop_size >> 20); 232 | fprintf(stdout, "Image size: %8lluMB\n", 233 | (unsigned long long)stat->image_size >> 20); 234 | } 235 | 236 | static int pb_discard(int argc, char **argv) 237 | { 238 | int ret, i; 239 | off_t val; 240 | int stat = 0; 241 | static struct option long_opts[] = { 242 | { "to-free", required_argument, 0, 666 }, 243 | { "min-block", required_argument, 0, 667 }, 244 | { "stat", no_argument, 0, 668 }, 245 | { "automount", no_argument, 0, 669 }, 246 | { "defrag", no_argument, 0, 670 }, 247 | { "defrag-only", no_argument, 0, 'D' }, 248 | { "image-defrag-threshold", required_argument, 0, 671 }, 249 | {}, 250 | }; 251 | struct ploop_disk_images_data *di = NULL; 252 | struct ploop_discard_param param = {}; 253 | 254 | while ((i = getopt_long(argc, argv, "d:m:", long_opts, NULL)) != EOF) { 255 | switch (i) { 256 | case 'd': 257 | device = optarg; 258 | break; 259 | case 'm': 260 | mount_point = optarg; 261 | break; 262 | case 666: 263 | if (parse_size(optarg, &val, "--to-free")) { 264 | usage_discard(); 265 | return SYSEXIT_PARAM; 266 | } 267 | param.to_free = S2B(val); 268 | break; 269 | case 667: 270 | if (parse_size(optarg, &val, "--min-block")) { 271 | usage_discard(); 272 | return SYSEXIT_PARAM; 273 | } 274 | param.minlen_b = S2B(val); 275 | break; 276 | case 668: 277 | stat = 1; 278 | break; 279 | case 669: 280 | param.automount = 1; 281 | break; 282 | case 670: 283 | param.defrag = 1; 284 | break; 285 | case 671: 286 | param.image_defrag_threshold = atoi(optarg); 287 | break; 288 | case 'D': 289 | param.defrag = 2; 290 | break; 291 | default: 292 | usage_discard(); 293 | return SYSEXIT_PARAM; 294 | } 295 | } 296 | 297 | argc -= optind; 298 | argv += optind; 299 | 300 | if (argc == 1 && is_xml_fname(argv[0])) { 301 | ret = ploop_open_dd(&di, argv[0]); 302 | if (ret) 303 | return ret; 304 | 305 | argc--; 306 | argv++; 307 | } 308 | 309 | if (argc != 0 || (di != NULL && (device || mount_point)) || 310 | (di == NULL && fill_opts())) { 311 | usage_discard(); 312 | 313 | if (di) 314 | ploop_close_dd(di); 315 | 316 | return 1; 317 | } 318 | 319 | if (stat) { 320 | struct ploop_discard_stat d_stat; 321 | 322 | ploop_set_verbose_level(PLOOP_LOG_NOCONSOLE); 323 | if (di) 324 | ret = ploop_discard_get_stat(di, &d_stat); 325 | else 326 | ret = ploop_discard_get_stat_by_dev(device, mount_point, &d_stat); 327 | 328 | if (ret == 0) 329 | print_discard_stat(&d_stat); 330 | } else { 331 | if (di) 332 | ret = ploop_discard(di, ¶m); 333 | else 334 | ret = ploop_discard_by_dev(device, mount_point, 335 | param.minlen_b, param.to_free, NULL); 336 | } 337 | 338 | if (di) 339 | ploop_close_dd(di); 340 | 341 | return ret; 342 | } 343 | 344 | int main(int argc, char **argv) 345 | { 346 | char *cmd; 347 | 348 | if (argc < 2) { 349 | usage_summary(); 350 | return SYSEXIT_PARAM; 351 | } 352 | 353 | cmd = argv[1]; 354 | argc--; 355 | argv++; 356 | 357 | ploop_set_verbose_level(3); 358 | init_signals(); 359 | 360 | if (strcmp(cmd, "show") == 0) 361 | return pb_show(argc, argv); 362 | if (strcmp(cmd, "change") == 0) 363 | return pb_change(argc, argv); 364 | if (strcmp(cmd, "discard") == 0) 365 | return pb_discard(argc, argv); 366 | 367 | usage_summary(); 368 | return SYSEXIT_PARAM; 369 | } 370 | -------------------------------------------------------------------------------- /tools/ploop-cbt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "ploop.h" 35 | #include "cbt.h" 36 | #include "common.h" 37 | #include "bit_ops.h" 38 | 39 | static void usage_summary(void) 40 | { 41 | fprintf(stderr, "Usage: ploop-cbt { dump | drop | show } DiskDescriptor.xml\n"); 42 | 43 | } 44 | 45 | static void usage_dump(void) 46 | { 47 | fprintf(stderr, "Usage: ploop-cbt dump --dst DiskDescriptor.xml\n" 48 | "\t\tdump --src --dst \n"); 49 | } 50 | 51 | static int dump(int argc, char **argv) 52 | { 53 | int ret, i; 54 | struct ploop_disk_images_data *di = NULL; 55 | const char *src = NULL, *dst = NULL; 56 | static struct option long_opts[] = { 57 | { "src", required_argument, 0, 1 }, 58 | { "dst", required_argument, 0, 2 }, 59 | {}, 60 | }; 61 | 62 | while ((i = getopt_long(argc, argv, "", long_opts, NULL)) != EOF) { 63 | switch (i) { 64 | case 1: 65 | src = optarg; 66 | break; 67 | case 2: 68 | dst = optarg; 69 | break; 70 | default: 71 | return SYSEXIT_PARAM; 72 | } 73 | } 74 | 75 | argc -= optind; 76 | argv += optind; 77 | 78 | if (argc == 0 && src != NULL && dst != NULL) 79 | return ploop_move_cbt(dst, src); 80 | 81 | if (argc != 1 || !is_xml_fname(argv[0]) || dst == NULL) { 82 | usage_dump(); 83 | return SYSEXIT_PARAM; 84 | } 85 | 86 | ret = ploop_open_dd(&di, argv[0]); 87 | if (ret) 88 | return ret; 89 | 90 | ret = ploop_dump_cbt(di, dst); 91 | 92 | ploop_close_dd(di); 93 | 94 | return ret; 95 | } 96 | 97 | static int drop(int argc, char **argv) 98 | { 99 | int ret; 100 | struct ploop_disk_images_data *di = NULL; 101 | 102 | argc--; 103 | argv++; 104 | if (argc != 1 || !is_xml_fname(argv[0])) { 105 | return SYSEXIT_PARAM; 106 | } 107 | 108 | ret = ploop_open_dd(&di, argv[0]); 109 | if (ret) 110 | return ret; 111 | 112 | ret = ploop_drop_cbt(di); 113 | 114 | ploop_close_dd(di); 115 | 116 | return ret; 117 | } 118 | 119 | static int show(int argc, char **argv) 120 | { 121 | int ret, i; 122 | struct ploop_disk_images_data *di = NULL; 123 | const char *fname = NULL; 124 | static struct option long_opts[] = { 125 | { "image", required_argument, 0, 1 }, 126 | {}, 127 | }; 128 | 129 | while ((i = getopt_long(argc, argv, "", long_opts, NULL)) != EOF) { 130 | switch (i) { 131 | case 1: 132 | fname = optarg; 133 | break; 134 | default: 135 | return SYSEXIT_PARAM; 136 | } 137 | } 138 | 139 | argc -= optind; 140 | argv += optind; 141 | ploop_set_verbose_level(PLOOP_LOG_NOCONSOLE); 142 | if (argc == 1 && is_xml_fname(argv[0])) { 143 | ret = ploop_open_dd(&di, argv[0]); 144 | if (ret) 145 | return ret; 146 | 147 | ret = ploop_cbt_dump_info(di); 148 | 149 | ploop_close_dd(di); 150 | } else 151 | ret = ploop_cbt_dump_info_from_image(fname ?:argv[0]); 152 | 153 | return ret; 154 | } 155 | 156 | static void usage_diff(void) 157 | { 158 | fprintf(stderr, "Usage: ploop-cbt diff [-b BLOCKSIZE] [-o OUT] file1 file2\n"); 159 | } 160 | 161 | static int diff(int argc, char **argv) 162 | { 163 | int ret = 0, i, f1, f2; 164 | unsigned long n, r, off, bits, bytes; 165 | unsigned long *map; 166 | void *b1, *b2; 167 | struct stat st; 168 | int bsize = 64 * 1024; 169 | const char *out = NULL; 170 | 171 | while ((i = getopt(argc, argv, "b:o:")) != EOF) { 172 | switch (i) { 173 | case 'b': 174 | bsize = atoi(optarg); 175 | break; 176 | case 'o': 177 | out = optarg; 178 | break; 179 | default: 180 | usage_diff(); 181 | return SYSEXIT_PARAM; 182 | } 183 | } 184 | 185 | argc -= optind; 186 | argv += optind; 187 | 188 | if (argc != 2) { 189 | usage_diff(); 190 | return SYSEXIT_PARAM; 191 | } 192 | 193 | f1 = open(argv[0], O_RDONLY); 194 | if (f1 == -1) { 195 | fprintf(stderr, "Error: Cannot open %s: %m\n", argv[0]); 196 | return 1; 197 | } 198 | if (fstat(f1, &st)) { 199 | fprintf(stderr, "Error: Cannot fstat %s: %m", argv[0]); 200 | close(f1); 201 | return 1; 202 | } 203 | 204 | f2 = open(argv[1], O_RDONLY); 205 | if (f2 == -1) { 206 | fprintf(stderr, "Error: Cannot open %s: %m\n", argv[1]); 207 | close(f1); 208 | return 1; 209 | } 210 | 211 | printf("Create diff %s %s\n", argv[0], argv[1]); 212 | b1 = malloc(bsize); 213 | b2 = malloc(bsize); 214 | 215 | bits = st.st_size / bsize; 216 | bytes = (bits + 7) / 8; 217 | map = (unsigned long *)calloc(1, bytes); 218 | for (off = 0, n = 0; n < bits; n++, off += bsize) { 219 | if (read_safe(f1, b1, bsize, off, "")) { 220 | fprintf(stderr, "Error: failed read %s off %lu: %m\n", argv[0], off); 221 | ret = 1; 222 | break; 223 | } 224 | if (read_safe(f2, b2, bsize, off, "")) { 225 | fprintf(stderr, "Error: failed read %s off %lu: %m\n", argv[1], off); 226 | ret = 1; 227 | break; 228 | } 229 | if (memcmp(b1, b2, bsize)) { 230 | BMAP_SET(map, n); 231 | } 232 | } 233 | 234 | if (ret == 0 && out) { 235 | int o; 236 | 237 | printf("Store CBT %s\n", out); 238 | o = open(out, O_WRONLY|O_TRUNC|O_CREAT, 0600); 239 | if (o == -1) { 240 | fprintf(stderr, "Error: Cannot open %s: %m\n", out); 241 | return 1; 242 | } 243 | 244 | r = write(o, map, bytes); 245 | if (r != bytes) { 246 | fprintf(stderr, "Error: failed read %s off %lu: %m\n", argv[1], off); 247 | ret = 1; 248 | } 249 | close(o); 250 | } 251 | close(f1); 252 | close(f2); 253 | free(map); 254 | free(b1); 255 | free(b2); 256 | 257 | return ret; 258 | } 259 | 260 | static void usage_cmp(void) 261 | { 262 | fprintf(stderr, "Usage: ploop-cbt cmp file1 file2\n"); 263 | } 264 | 265 | static int cmp(int argc, char **argv) 266 | { 267 | int ret = 0, f1, f2; 268 | unsigned long n, r, i; 269 | struct stat st; 270 | 271 | argc -= optind; 272 | argv += optind; 273 | 274 | if (argc != 2) { 275 | usage_cmp(); 276 | return SYSEXIT_PARAM; 277 | } 278 | 279 | f1 = open(argv[0], O_RDONLY); 280 | if (f1 == -1) { 281 | fprintf(stderr, "Error: Cannot open %s: %m\n", argv[0]); 282 | return 1; 283 | } 284 | if (fstat(f1, &st)) { 285 | fprintf(stderr, "Error: Cannot fstat %s: %m", argv[0]); 286 | close(f1); 287 | return 1; 288 | } 289 | 290 | f2 = open(argv[1], O_RDONLY); 291 | if (f2 == -1) { 292 | fprintf(stderr, "Error: Cannot open %s: %m\n", argv[1]); 293 | close(f1); 294 | return 1; 295 | } 296 | 297 | n = st.st_size / sizeof(unsigned long); 298 | for (i = 0; i < n; i++) { 299 | unsigned long b1, b2; 300 | r = read(f1, &b1, sizeof(b1)); 301 | if (r != sizeof(b1)) { 302 | fprintf(stderr, "Error: failed read %s off %lu: %m\n", argv[0], i * sizeof(unsigned long)); 303 | ret = 1; 304 | break; 305 | } 306 | r = read(f2, &b2, sizeof(b2)); 307 | if (r != sizeof(b2)) { 308 | fprintf(stderr, "Error: failed read %s off %lu: %m\n", argv[1], i * sizeof(unsigned long)); 309 | ret = 1; 310 | break; 311 | } 312 | if (b1 != b2) { 313 | printf("differ off %lu %lx %lx\n", i * sizeof(unsigned long), b1, b2); 314 | if (~b1 & b2) { 315 | fprintf(stderr, "Failed"); 316 | ret = 1; 317 | break; 318 | } 319 | } 320 | } 321 | 322 | close(f1); 323 | close(f2); 324 | 325 | return ret; 326 | } 327 | 328 | int main(int argc, char **argv) 329 | { 330 | char *cmd; 331 | 332 | if (argc < 2) { 333 | usage_summary(); 334 | return SYSEXIT_PARAM; 335 | } 336 | 337 | cmd = argv[1]; 338 | argc--; 339 | argv++; 340 | 341 | init_signals(); 342 | ploop_set_verbose_level(3); 343 | 344 | if (strcmp(cmd, "dump") == 0) 345 | return dump(argc, argv); 346 | if (strcmp(cmd, "drop") == 0) 347 | return drop(argc, argv); 348 | if (strcmp(cmd, "show") == 0) 349 | return show(argc, argv); 350 | if (strcmp(cmd, "diff") == 0) 351 | return diff(argc, argv); 352 | if (strcmp(cmd, "cmp") == 0) 353 | return cmp(argc, argv); 354 | 355 | usage_summary(); 356 | 357 | return SYSEXIT_PARAM; 358 | } 359 | -------------------------------------------------------------------------------- /tools/ploop-check.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "ploop.h" 32 | #include "common.h" 33 | 34 | static void usage(void) 35 | { 36 | fprintf(stderr, "Usage: ploop check [-u UUID] DiskDescriptor.xml\n" 37 | " UUID := check all deltas up to top image with this UUID\n" 38 | " ploop check [options] DELTA\n" 39 | " DELTA := path to image file\n" 40 | " -f, --force - force check even if dirty flag is clear\n" 41 | " -F, --hard-force - -f and try to fix even fatal errors (dangerous)\n" 42 | " -c, --check - check for duplicated blocks and holes\n" 43 | " -r, --ro - do not modify DELTA (read-only access)\n" 44 | " -s, --silent - be silent, report only errors\n" 45 | " -d, --drop-inuse - drop image \"in use\" flag\n" 46 | " -R, --raw - DELTA is a raw ploop image\n" 47 | " -b, --blocksize SIZE - cluster block size in sectors (for raw images)\n" 48 | " -S, --repair-sparse - repair sparse image\n" 49 | " -D, --defrag - cluster block defragmentation\n" 50 | ); 51 | } 52 | 53 | int plooptool_check(int argc, char ** argv) 54 | { 55 | int i, idx; 56 | int flags = CHECK_TALKATIVE; 57 | unsigned int blocksize = 0; 58 | char *endptr; 59 | const char *uuid = NULL; 60 | static struct option options[] = { 61 | {"force", no_argument, NULL, 'f'}, 62 | {"hard-force", no_argument, NULL, 'F'}, 63 | {"check", no_argument, NULL, 'c'}, 64 | {"drop-inuse", no_argument, NULL, 'd'}, 65 | {"ro", no_argument, NULL, 'r'}, 66 | {"silent", no_argument, NULL, 's'}, 67 | {"raw", no_argument, NULL, 'R'}, 68 | {"blocksize", required_argument, NULL, 'b'}, 69 | {"repair-sparse", no_argument, NULL, 'S'}, 70 | {"uuid", required_argument, NULL, 'u'}, 71 | {"defrag", no_argument, NULL, 'D'}, 72 | { NULL, 0, NULL, 0 } 73 | }; 74 | 75 | while ((i = getopt_long(argc, argv, "fFcrsdRb:Su:D", options, &idx)) != EOF) { 76 | switch (i) { 77 | case 'f': 78 | /* try to repair non-fatal conditions */ 79 | flags |= CHECK_FORCE; 80 | break; 81 | case 'F': 82 | /* try to repair even fatal conditions */ 83 | flags |= (CHECK_FORCE | CHECK_HARDFORCE); 84 | break; 85 | case 'c': 86 | /* build bitmap and check for duplicate blocks */ 87 | flags |= CHECK_DETAILED; 88 | break; 89 | case 'd': 90 | flags |= CHECK_DROPINUSE; 91 | break; 92 | case 'r': 93 | flags |= CHECK_READONLY; 94 | break; 95 | case 'R': 96 | flags |= CHECK_RAW; 97 | break; 98 | case 'b': 99 | blocksize = strtoul(optarg, &endptr, 0); 100 | if (*endptr != '\0') { 101 | usage(); 102 | return SYSEXIT_PARAM; 103 | } 104 | break; 105 | case 's': 106 | flags &= ~CHECK_TALKATIVE; 107 | break; 108 | case 'S': 109 | flags |= CHECK_REPAIR_SPARSE; 110 | break; 111 | case 'u': 112 | uuid = parse_uuid(optarg); 113 | if (!uuid) 114 | return SYSEXIT_PARAM; 115 | break; 116 | case 'D': 117 | flags |= CHECK_DEFRAG; 118 | break; 119 | default: 120 | usage(); 121 | return SYSEXIT_PARAM; 122 | } 123 | } 124 | 125 | argc -= optind; 126 | argv += optind; 127 | 128 | if (argc != 1) { 129 | usage(); 130 | return SYSEXIT_PARAM; 131 | } 132 | 133 | if (is_xml_fname(argv[0])) { 134 | struct ploop_disk_images_data *di; 135 | int ret; 136 | 137 | if (blocksize) 138 | fprintf(stderr, "WARNING: blocksize options is ignored " 139 | "for DiskDescriptor.xml form\n"); 140 | 141 | ret = ploop_open_dd(&di, argv[0]); 142 | if (ret) 143 | return ret; 144 | 145 | ret = check_dd(di, uuid, flags); 146 | 147 | ploop_close_dd(di); 148 | 149 | return ret; 150 | } 151 | 152 | /* non-ddxml form */ 153 | if (uuid) { 154 | fprintf(stderr, "Option -u is only applicable to " 155 | "DiskDescriptor.xml syntax\n"); 156 | return SYSEXIT_PARAM; 157 | } 158 | 159 | return ploop_check(argv[0], flags, &blocksize, NULL); 160 | } 161 | -------------------------------------------------------------------------------- /tools/ploop-copy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "libploop.h" 28 | #include "ploop.h" 29 | #include "common.h" 30 | 31 | static void usage(void) 32 | { 33 | fprintf(stderr, "Usage: ploop copy -s DEVICE -t FORMAT { [-d FILE] | [-o OFD] [-f FFD]}\n" 34 | " ploop copy -d FILE [-i IFD]\n" 35 | " DEVICE := source ploop device, e.g. /dev/ploop0\n" 36 | " FORMAT := " USAGE_FORMATS "\n" 37 | " FILE := destination file name\n" 38 | " OFD := output file descriptor\n" 39 | " IFD := input file descriptor\n" 40 | "Action: effectively copy top ploop delta with write tracker\n" 41 | ); 42 | } 43 | 44 | int plooptool_copy(int argc, char **argv) 45 | { 46 | int i, ret; 47 | int niter = 3; 48 | struct ploop_copy_handle *h = NULL; 49 | struct ploop_copy_stat stat; 50 | struct ploop_copy_param s = { 51 | .ofd = 1, /* write to stdout by default */ 52 | }; 53 | struct ploop_copy_receive_param r = { 54 | .ifd = 0, /* read from stdin by default */ 55 | .feedback_fd = -1, /* no feedback */ 56 | }; 57 | 58 | while ((i = getopt(argc, argv, "s:t:d:o:i:")) != EOF) { 59 | switch (i) { 60 | case 'd': 61 | r.file = optarg; 62 | break; 63 | case 's': 64 | s.device = optarg; 65 | break; 66 | case 'o': 67 | s.ofd = atoi(optarg); 68 | break; 69 | case 'i': 70 | r.ifd = atoi(optarg); 71 | break; 72 | case 't': 73 | s.image_fmt = parse_format_opt(optarg); 74 | break; 75 | default: 76 | usage(); 77 | return SYSEXIT_PARAM; 78 | } 79 | } 80 | 81 | argc -= optind; 82 | argv += optind; 83 | 84 | if (argc) { 85 | usage(); 86 | return SYSEXIT_PARAM; 87 | } 88 | 89 | if (!s.device && !r.file) { 90 | fprintf(stderr, "Either -s or -d is required\n"); 91 | usage(); 92 | return SYSEXIT_PARAM; 93 | } 94 | 95 | signal(SIGPIPE, SIG_IGN); 96 | 97 | if (!s.device) 98 | return ploop_copy_receiver(&r); 99 | 100 | if (r.file) { 101 | /* Write to a file, not pipe */ 102 | s.ofd = open(r.file, O_WRONLY|O_CREAT|O_EXCL, 0600); 103 | if (s.ofd < 0) { 104 | fprintf(stderr, "Can't open %s: %m", r.file); 105 | return SYSEXIT_CREAT; 106 | } 107 | } 108 | 109 | if (s.ofd == 1) 110 | ploop_set_verbose_level(PLOOP_LOG_NOCONSOLE); 111 | 112 | ret = ploop_copy_init(NULL, &s, &h); 113 | if (ret) 114 | return ret; 115 | ret = ploop_copy_start(h, &stat); 116 | if (ret) 117 | goto err; 118 | 119 | for (i = 0; i < niter; i++) { 120 | ret = ploop_copy_next_iteration(h, &stat); 121 | if (ret) 122 | goto err; 123 | } 124 | 125 | ret = ploop_copy_stop(h, &stat); 126 | if (ret) 127 | goto err; 128 | 129 | err: 130 | ploop_copy_deinit(h); 131 | if (r.file) { 132 | close(s.ofd); 133 | if (ret) 134 | unlink(r.file); 135 | } 136 | 137 | return ret; 138 | } 139 | -------------------------------------------------------------------------------- /tools/ploop-grow.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "ploop.h" 33 | #include "common.h" 34 | 35 | 36 | static void usage(void) 37 | { 38 | fprintf(stderr, "Usage: ploop grow -s NEW_SIZE -d DEVICE\n" 39 | " ploop grow -s NEW_SIZE [-f raw] DELTA\n" 40 | " ploop grow -s NEW_SIZE [--sparse] DiskDescriptor.xml\n" 41 | ); 42 | } 43 | 44 | int plooptool_grow(int argc, char **argv) 45 | { 46 | int i, f; 47 | off_t new_size = 0; /* in sectors */ 48 | int raw = 0, sparse = 0; 49 | char *device = NULL; 50 | static struct option long_opts[] = { 51 | { "sparse", no_argument, 0, 'S' }, 52 | {}, 53 | }; 54 | 55 | while ((i = getopt_long(argc, argv, "f:d:s:S", long_opts, NULL)) != EOF) { 56 | switch (i) { 57 | case 'f': 58 | f = parse_format_opt(optarg); 59 | if (f < 0) { 60 | usage(); 61 | return SYSEXIT_PARAM; 62 | } 63 | raw = (f == PLOOP_RAW_MODE); 64 | break; 65 | case 'd': 66 | device = optarg; 67 | break; 68 | case 'S': 69 | sparse = 1; 70 | break; 71 | case 's': 72 | if (parse_size(optarg, &new_size, "-s")) { 73 | usage(); 74 | return SYSEXIT_PARAM; 75 | } 76 | break; 77 | default: 78 | usage(); 79 | return SYSEXIT_PARAM; 80 | } 81 | } 82 | 83 | argc -= optind; 84 | argv += optind; 85 | 86 | if (((argc != 0 || !device) && (argc != 1 || device)) || 87 | (raw && device) || (new_size == 0)) { 88 | usage(); 89 | return SYSEXIT_PARAM; 90 | } 91 | 92 | if (argc == 1 && is_xml_fname(argv[0])) 93 | { 94 | int ret; 95 | 96 | struct ploop_disk_images_data *di; 97 | ret = ploop_open_dd(&di, argv[0]); 98 | if (ret) 99 | return ret; 100 | 101 | ret = ploop_grow_image(di, new_size, sparse); 102 | ploop_close_dd(di); 103 | 104 | return ret; 105 | } 106 | else if (device) 107 | return ploop_grow_device(NULL, device, new_size); 108 | else if (raw) 109 | return ploop_grow_raw_delta_offline(argv[0], new_size, sparse); 110 | else 111 | return ploop_grow_delta_offline(argv[0], new_size); 112 | } 113 | -------------------------------------------------------------------------------- /tools/ploop-merge.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "ploop.h" 34 | #include "common.h" 35 | 36 | static void usage(void) 37 | { 38 | fprintf(stderr, "Usage: ploop merge -d DEVICE [-l LEVEL[..TOP_LEVEL]] [-n NEW_DELTA]\n" 39 | " ploop merge [-f raw] [-n NEW_DELTA] DELTAS_TO_MERGE BASE_DELTA\n" 40 | ); 41 | } 42 | 43 | int plooptool_merge(int argc, char ** argv) 44 | { 45 | int raw = 0; 46 | int start_level = 0; 47 | int end_level = 0; 48 | int merge_top = 0; 49 | char *device = NULL; 50 | char **names = NULL; 51 | const char *new_delta = NULL; 52 | int i, f, ret; 53 | int is_dm = ploop_is_devicemapper(); 54 | 55 | while ((i = getopt(argc, argv, "f:d:l:n:u:A")) != EOF) { 56 | switch (i) { 57 | case 'f': 58 | f = parse_format_opt(optarg); 59 | if (f < 0) { 60 | usage(); 61 | return SYSEXIT_PARAM; 62 | } 63 | raw = (f == PLOOP_RAW_MODE); 64 | break; 65 | case 'd': 66 | device = optarg; 67 | break; 68 | case 'l': 69 | if (sscanf(optarg, "%d..%d", &start_level, &end_level) != 2) { 70 | if (sscanf(optarg, "%d", &start_level) != 1) { 71 | usage(); 72 | return SYSEXIT_PARAM; 73 | } 74 | end_level = start_level + 1; 75 | } 76 | if (start_level >= end_level || start_level < 0) { 77 | usage(); 78 | return SYSEXIT_PARAM; 79 | } 80 | break; 81 | case 'n': 82 | new_delta = optarg; 83 | break; 84 | case 'u': 85 | case 'A': 86 | /* ignore */ 87 | break; 88 | default: 89 | usage(); 90 | return SYSEXIT_PARAM; 91 | } 92 | } 93 | 94 | argc -= optind; 95 | argv += optind; 96 | 97 | if (argc == 1 && is_xml_fname(argv[0])) { 98 | fprintf(stderr, "Please use ploop snapshot-merge command\n"); 99 | return SYSEXIT_PARAM; 100 | } else { 101 | if (device == NULL) { 102 | if (argc < 2) { 103 | usage(); 104 | return SYSEXIT_PARAM; 105 | } 106 | end_level = argc; 107 | names = argv; 108 | } else { 109 | #if 0 110 | char *f; 111 | int blocksize; 112 | if (argc || raw) { 113 | usage(); 114 | return SYSEXIT_PARAM; 115 | } 116 | 117 | if ((ret = get_delta_names(device, &names, &f, &blocksize))) 118 | return ret; 119 | merge_top = get_list_size(names) == start_level; 120 | #endif 121 | } 122 | 123 | if (is_dm && !qcow_check_valid_images(argv, argc)) { 124 | /* we only support backward merge all */ 125 | // need to reload with last two deltas writeable otherwise we get EACCESS 126 | int ret; 127 | struct ploop_disk_images_data *di; 128 | ret = ploop_make_dd_from_imgs(&di, argv); 129 | if (ret) 130 | return ret; 131 | 132 | ret = ploop_dmreplace_qcow(di, NULL, device, RELOAD_RW2); 133 | if (!ret) 134 | ret = merge_qcow2_backward(device); 135 | 136 | ploop_close_dd(di); 137 | 138 | return ret; 139 | 140 | } else { 141 | ret = merge_image(device, start_level, end_level, raw, merge_top, names, new_delta); 142 | } 143 | } 144 | 145 | return ret; 146 | } 147 | -------------------------------------------------------------------------------- /tools/ploop-snapshot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "ploop.h" 29 | #include "list.h" 30 | #include "common.h" 31 | 32 | struct id_entry { 33 | list_elem_t list; 34 | int id; 35 | }; 36 | 37 | static const char *default_field_order = "parent_uuid,current,uuid,fname"; 38 | static int g_last_field; 39 | static LIST_HEAD(g_field_order_head); 40 | static LIST_HEAD(g_uuid_list_head); 41 | static struct ploop_disk_images_data *g_di; 42 | static int g_snapshot_mode; 43 | static char *g_current_snap_guid; 44 | 45 | static const char *FMT(const char *fmt) 46 | { 47 | return g_last_field ? "%-s\n" : fmt; 48 | } 49 | 50 | static void print_uuid(struct ploop_snapshot_data *p) 51 | { 52 | printf(FMT("%-38s "), p->guid); 53 | } 54 | 55 | static void print_parent_uuid(struct ploop_snapshot_data *p) 56 | { 57 | printf(FMT("%-38s "), p->parent_guid); 58 | } 59 | 60 | static void print_current(struct ploop_snapshot_data *p) 61 | { 62 | if (g_snapshot_mode) { 63 | if (g_current_snap_guid == NULL) 64 | g_current_snap_guid = ploop_find_parent_by_guid(g_di, g_di->top_guid); 65 | printf(FMT("%1s "), !guidcmp(p->guid, g_current_snap_guid ?: "") ? "*" : ""); 66 | } else 67 | printf(FMT("%1s "), !guidcmp(p->guid, g_di->top_guid) ? "*" : ""); 68 | } 69 | 70 | static void print_fname(struct ploop_snapshot_data *p) 71 | { 72 | // FIXME: error if not found? 73 | char *fname = find_image_by_guid(g_di, p->guid); 74 | 75 | printf(FMT("%-32s "), fname ? fname : ""); 76 | } 77 | 78 | struct snapshot_field { 79 | char *name; 80 | char *hdr; 81 | char *fmt; 82 | void (* print_fn)(struct ploop_snapshot_data *p); 83 | } field_tbl[] = 84 | { 85 | {"uuid", "UUID", "%-38s", print_uuid}, 86 | {"parent_uuid", "PARENT_UUID", "%-38s", print_parent_uuid}, 87 | {"current", "C", "%1s", print_current}, 88 | {"fname", "FNAME", "%-32s", print_fname}, 89 | }; 90 | 91 | static int get_field_tbl_id(const char *name) 92 | { 93 | int i; 94 | 95 | for (i = 0; i < sizeof(field_tbl) / sizeof(field_tbl[0]); i++) 96 | if (!strcasecmp(name, field_tbl[i].name)) 97 | return i; 98 | return -1; 99 | } 100 | 101 | static int add_entry(list_head_t *head, int id) 102 | { 103 | struct id_entry *p; 104 | 105 | p = malloc(sizeof( struct id_entry)); 106 | if (p == NULL) { 107 | fprintf(stderr, "ENOMEM\n"); 108 | return 1; 109 | } 110 | p->id = id; 111 | list_add_tail(&p->list, head); 112 | return 0; 113 | } 114 | 115 | static int build_field_order_list(const char *fields) 116 | { 117 | int len, id; 118 | const char *sp, *ep, *p; 119 | char name[256]; 120 | 121 | sp = fields != NULL ? fields : default_field_order; 122 | 123 | ep = sp + strlen(sp); 124 | do { 125 | if ((p = strchr(sp, ',')) == NULL) 126 | p = ep; 127 | len = p - sp + 1; 128 | if (len > sizeof(name) - 1) { 129 | fprintf(stderr, "Field name %s is unknown.\n", sp); 130 | return 1; 131 | } 132 | snprintf(name, len, "%s", sp); 133 | sp = p + 1; 134 | id = get_field_tbl_id(name); 135 | if (id == -1) { 136 | fprintf(stderr, "Unknown field: %s\n", name); 137 | return 1; 138 | } 139 | if (add_entry(&g_field_order_head, id)) 140 | return 1; 141 | } while (sp < ep); 142 | 143 | return 0; 144 | } 145 | 146 | static int build_uuid_list(const char *guid) 147 | { 148 | int done = 0; 149 | int id, n; 150 | 151 | // List all snapshots 152 | if (guid == NULL) { 153 | for (n = 0; n < g_di->nsnapshots; n++) { 154 | if (add_entry(&g_uuid_list_head, n)) 155 | return 1; 156 | } 157 | return 0; 158 | } 159 | for (n = 0; n < g_di->nsnapshots; n++) { 160 | id = find_snapshot_by_guid(g_di, guid); 161 | if (id == -1) { 162 | fprintf(stderr, "Can't find snapshot by uuid %s\n", guid); 163 | return 1; 164 | } 165 | 166 | if (find_image_by_guid(g_di, guid) == NULL) { 167 | fprintf(stderr, "Can't find image by guid %s\n", guid); 168 | return 1; 169 | } 170 | 171 | if (n == g_di->nimages) { 172 | fprintf(stderr, "Inconsistency detected: snapshots > images\n"); 173 | return 1; 174 | } 175 | 176 | if (add_entry(&g_uuid_list_head, id)) 177 | return 1; 178 | 179 | guid = g_di->snapshots[id]->parent_guid; 180 | if (!strcmp(guid, NONE_UUID)) { 181 | done = 1; 182 | break; 183 | } 184 | } 185 | if (!done) { 186 | fprintf(stderr, "Inconsistency detected, base image not found\n"); 187 | return 1; 188 | } 189 | 190 | return 0; 191 | } 192 | 193 | static void usage_snapshot_list(void) 194 | { 195 | fprintf(stderr, "Usage: ploop snapshot-list [-H] [-o field[,field...]] [-s] [-u ] \\\n" 196 | " DiskDescriptor.xml\n"); 197 | } 198 | 199 | int plooptool_snapshot_list(int argc, char **argv) 200 | { 201 | int c; 202 | int no_hdr = 0; 203 | struct id_entry *field_entry = NULL; 204 | struct id_entry * uuid_entry = NULL; 205 | const char *output = NULL; 206 | const char *guid = NULL; 207 | struct option list_options[] = 208 | { 209 | {"no-header", no_argument, NULL, 'H'}, 210 | {"output", required_argument, NULL, 'o'}, 211 | {"help", no_argument, NULL, 'h'}, 212 | {"uuid", required_argument, NULL, 'u'}, 213 | {"id", required_argument, NULL, 'u'}, 214 | {"snapshot", required_argument, NULL, 's'}, 215 | { NULL, 0, NULL, 0 } 216 | }; 217 | 218 | while (1) { 219 | int option_index = -1; 220 | c = getopt_long(argc, argv, "Hho:u:s", 221 | list_options, &option_index); 222 | if (c == -1) 223 | break; 224 | 225 | switch (c) { 226 | case 'H': 227 | no_hdr = 1; 228 | break; 229 | case 'o' : 230 | output = optarg; 231 | break; 232 | case 'u': 233 | guid = parse_uuid(optarg); 234 | if (!guid) 235 | return SYSEXIT_PARAM; 236 | break; 237 | case 's': 238 | g_snapshot_mode = 1; 239 | break; 240 | case 'h': 241 | usage_snapshot_list(); 242 | return 0; 243 | default: 244 | usage_snapshot_list(); 245 | return 1; 246 | } 247 | } 248 | argc -= optind; 249 | argv += optind; 250 | 251 | if (argc < 1) { 252 | usage_snapshot_list(); 253 | return 1; 254 | } 255 | if (ploop_read_disk_descr(&g_di, argv[0])) { 256 | fprintf(stderr, "failed to read %s: %s\n", 257 | argv[0], ploop_get_last_error()); 258 | return 1; 259 | } 260 | 261 | if (build_field_order_list(output)) 262 | return 1; 263 | 264 | if (build_uuid_list(guid)) 265 | return 1; 266 | 267 | if (!no_hdr) { 268 | list_for_each(field_entry, &g_field_order_head, list) { 269 | printf(field_tbl[field_entry->id].fmt, 270 | field_tbl[field_entry->id].hdr); 271 | printf(" "); 272 | } 273 | printf("\n"); 274 | } 275 | 276 | list_for_each(uuid_entry, &g_uuid_list_head, list) { 277 | if (g_snapshot_mode && 278 | guidcmp(g_di->snapshots[uuid_entry->id]->guid, g_di->top_guid) == 0) 279 | continue; 280 | 281 | list_for_each(field_entry, &g_field_order_head, list) { 282 | g_last_field = ((void *)g_field_order_head.prev == (void *)field_entry); 283 | field_tbl[field_entry->id].print_fn(g_di->snapshots[uuid_entry->id]); 284 | } 285 | } 286 | return 0; 287 | } 288 | -------------------------------------------------------------------------------- /tools/ploop-stat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "ploop.h" 29 | 30 | #define FMT "/sys/block/%s/pstat" 31 | 32 | static void usage(void) 33 | { 34 | fprintf(stderr, "Usage: ploop stat [-c | -l] -d DEVICE\n"); 35 | } 36 | 37 | static int open_sysfs_file(char * devid, char *name, int flags) 38 | { 39 | char buf[PATH_MAX]; 40 | 41 | snprintf(buf, sizeof(buf)-1, FMT "/%s", devid, name); 42 | 43 | return open(buf, flags); 44 | } 45 | 46 | static DIR * open_sysfs_dir(char * devid) 47 | { 48 | char buf[sizeof(FMT) + strlen(devid)]; 49 | snprintf(buf, sizeof(buf)-1, FMT, devid); 50 | 51 | return opendir(buf); 52 | } 53 | 54 | int plooptool_stat(int argc, char **argv) 55 | { 56 | int i; 57 | int clear = 0; 58 | int load = 0; 59 | DIR *dp; 60 | struct dirent *de; 61 | char * device = NULL; 62 | int ret = 0; 63 | 64 | while ((i = getopt(argc, argv, "cld:")) != EOF) { 65 | switch (i) { 66 | case 'c': 67 | clear = 1; 68 | break; 69 | case 'l': 70 | load = 1; 71 | break; 72 | case 'd': 73 | device = optarg; 74 | break; 75 | default: 76 | usage(); 77 | return SYSEXIT_PARAM; 78 | } 79 | } 80 | 81 | argc -= optind; 82 | argv += optind; 83 | 84 | if (argc || !device) { 85 | usage(); 86 | return SYSEXIT_PARAM; 87 | } 88 | 89 | if (memcmp(device, "/dev/", 5) == 0) 90 | device += 5; 91 | 92 | dp = open_sysfs_dir(device); 93 | if (dp == NULL) { 94 | perror("sysfs opendir"); 95 | return SYSEXIT_SYSFS; 96 | } 97 | 98 | 99 | if (load) { 100 | char buf[128]; 101 | 102 | while (fgets(buf, sizeof(buf)-1, stdin)) { 103 | char name[128]; 104 | unsigned int val; 105 | int fd; 106 | 107 | buf[sizeof(buf)-1] = 0; 108 | if (sscanf(buf, "%s%u", name, &val) != 2) 109 | continue; 110 | 111 | 112 | fd = open_sysfs_file(device, name, O_WRONLY); 113 | if (fd < 0) { 114 | fprintf(stderr, "Variable \"%s\" is missing\n", name); 115 | continue; 116 | } 117 | snprintf(buf, sizeof(buf)-1, "%u\n", val); 118 | if (write(fd, buf, strlen(buf)) <= 0) 119 | perror("write"); 120 | close(fd); 121 | } 122 | goto out; 123 | } 124 | 125 | 126 | while ((de = readdir(dp)) != NULL) { 127 | int fd = -1; 128 | int n; 129 | char buf[128]; 130 | 131 | if (de->d_name[0] == '.') 132 | continue; 133 | 134 | fd = open_sysfs_file(device, de->d_name, clear ? O_WRONLY : O_RDONLY); 135 | if (fd < 0) { 136 | perror("openat"); 137 | ret = SYSEXIT_SYSFS; 138 | goto out; 139 | } 140 | if (clear) { 141 | if (write(fd, "0\n", 2) <= 0) 142 | perror("write"); 143 | } else { 144 | n = read(fd, buf, sizeof(buf)-1); 145 | if (n < 0) { 146 | perror("read"); 147 | close(fd); 148 | ret = SYSEXIT_SYSFS; 149 | goto out; 150 | } 151 | buf[n] = 0; 152 | printf("%-20s\t%s", de->d_name, buf); 153 | } 154 | close(fd); 155 | } 156 | 157 | out: 158 | closedir(dp); 159 | 160 | return ret; 161 | } 162 | -------------------------------------------------------------------------------- /tools/ploop-test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Virtuozzo International GmbH. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "ploop.h" 29 | 30 | static void usage(void) 31 | { 32 | fprintf(stderr, "Usage: ploop shuffle [-n ] IMAGE\n"); 33 | } 34 | 35 | int main(int argc, char **argv) 36 | { 37 | int n = 1, i; 38 | const char *cmd; 39 | 40 | 41 | cmd = argv[1]; 42 | argc--; 43 | argv++; 44 | 45 | while ((i = getopt(argc, argv, "n:")) != EOF) { 46 | switch (i) { 47 | case 'n': 48 | n = atoi(optarg); 49 | break; 50 | default: 51 | usage(); 52 | return SYSEXIT_PARAM; 53 | } 54 | } 55 | 56 | 57 | argc -= optind; 58 | argv += optind; 59 | 60 | if (argc != 1) { 61 | usage(); 62 | return SYSEXIT_PARAM; 63 | } 64 | 65 | ploop_set_verbose_level(3); 66 | if (strcmp(cmd, "shuffle") == 0) 67 | return ploop_image_shuffle(argv[0], n, 0); 68 | 69 | usage(); 70 | return SYSEXIT_PARAM; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /tools/ploop-volume.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008-2017 Parallels International GmbH. 3 | * Copyright (c) 2017-2019 Virtuozzo International GmbH. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #include "ploop.h" 37 | #include "libvolume.h" 38 | #include "common.h" 39 | 40 | static void usage_summary(void) 41 | { 42 | fprintf(stderr, "Usage: ploop-volume {create|clone|snapshot|delete|switch} path\n"); 43 | 44 | } 45 | 46 | static void usage_create(void) 47 | { 48 | fprintf(stderr, "Usage: ploop-volume create -s SIZE [--image ] \n"); 49 | } 50 | 51 | static int create(int argc, char **argv) 52 | { 53 | int i, f; 54 | off_t size_sec = 0; 55 | char *endptr; 56 | struct ploop_volume_data vol = {}; 57 | struct ploop_create_param param = { 58 | .fstype = "ext4", 59 | .mode = PLOOP_EXPANDED_MODE, 60 | .fmt_version = PLOOP_FMT_UNDEFINED, 61 | }; 62 | 63 | static struct option opts[] = { 64 | { "image", required_argument, 0, 1 }, 65 | { "size", required_argument, 0, 's' }, 66 | {} 67 | }; 68 | 69 | while ((i = getopt_long(argc, argv, "s:b:B:f:t:L:v:n:k:", 70 | opts, NULL)) != EOF) { 71 | switch (i) { 72 | case 1: 73 | vol.i_path = optarg; 74 | break; 75 | case 's': 76 | if (parse_size(optarg, &size_sec, "-s")) { 77 | usage_create(); 78 | return SYSEXIT_PARAM; 79 | } 80 | break; 81 | case 'b': 82 | param.blocksize = strtoul(optarg, &endptr, 0); 83 | if (*endptr != '\0') { 84 | usage_create(); 85 | return SYSEXIT_PARAM; 86 | } 87 | break; 88 | case 'B' : 89 | param.fsblocksize = strtoul(optarg, &endptr, 0); 90 | if (*endptr != '\0') { 91 | usage_create(); 92 | return SYSEXIT_PARAM; 93 | } 94 | break; 95 | case 'f': 96 | f = parse_format_opt(optarg); 97 | if (f < 0) { 98 | usage_create(); 99 | return SYSEXIT_PARAM; 100 | } 101 | param.mode = f; 102 | break; 103 | case 't': 104 | if (!strcmp(optarg, "none")) 105 | param.fstype = NULL; 106 | else if (!strcmp(optarg, "ext4") || 107 | !strcmp(optarg, "ext3")) { 108 | param.fstype = strdup(optarg); 109 | } else { 110 | fprintf(stderr, "Incorrect file system type " 111 | "specified: %s\n", optarg); 112 | return SYSEXIT_PARAM; 113 | } 114 | break; 115 | case 'L': 116 | param.fslabel = strdup(optarg); 117 | break; 118 | case 'n': 119 | param.flags |= PLOOP_CREATE_NOLAZY; 120 | break; 121 | case 'k': 122 | param.keyid = optarg; 123 | break; 124 | default: 125 | usage_create(); 126 | return SYSEXIT_PARAM; 127 | } 128 | } 129 | 130 | argc -= optind; 131 | argv += optind; 132 | 133 | if (argc != 1) { 134 | usage_create(); 135 | return SYSEXIT_PARAM; 136 | } 137 | 138 | if (size_sec == 0) { 139 | usage_create(); 140 | return SYSEXIT_PARAM; 141 | } 142 | 143 | param.size = (__u64) size_sec; 144 | vol.m_path = argv[0]; 145 | 146 | return ploop_volume_create(&vol, ¶m); 147 | } 148 | 149 | static void usage_clone(void) 150 | { 151 | fprintf(stderr, "Usage: ploop-volume clone \n"); 152 | } 153 | 154 | static int clone(int argc, char **argv) 155 | { 156 | int i; 157 | struct ploop_volume_data vol = {}; 158 | static struct option opts[] = { 159 | { "image", required_argument, 0, 1 }, 160 | {} 161 | }; 162 | 163 | while ((i = getopt_long(argc, argv, "", opts, NULL)) != EOF) { 164 | switch (i) { 165 | case 1: 166 | vol.i_path = optarg; 167 | break; 168 | default: 169 | return SYSEXIT_PARAM; 170 | } 171 | } 172 | 173 | argc -= optind; 174 | argv += optind; 175 | 176 | if (argc != 2) { 177 | usage_clone(); 178 | return SYSEXIT_PARAM; 179 | } 180 | 181 | vol.m_path = argv[1]; 182 | 183 | return ploop_volume_clone(argv[0], &vol); 184 | } 185 | 186 | static void usage_info(void) 187 | { 188 | fprintf(stderr, "Usage: ploop-voulume info \n"); 189 | } 190 | 191 | static int print_info(int argc, char **argv) 192 | { 193 | int i, rc; 194 | static struct option opts[] = { 195 | {} 196 | }; 197 | struct ploop_volume_info info; 198 | struct json_object *result; 199 | 200 | while ((i = getopt_long(argc, argv, "", opts, NULL)) != EOF) { 201 | switch (i) { 202 | default: 203 | return SYSEXIT_PARAM; 204 | } 205 | } 206 | 207 | argc -= optind; 208 | argv += optind; 209 | 210 | if (argc != 1) { 211 | usage_info(); 212 | return SYSEXIT_PARAM; 213 | } 214 | 215 | if ((rc = ploop_volume_get_info(argv[0], &info, sizeof(info)))) 216 | return rc; 217 | 218 | result = json_object_new_object(); 219 | json_object_object_add(result, "size", json_object_new_int64((int64_t) info.size)); 220 | printf("%s\n", json_object_to_json_string_ext(result, JSON_C_TO_STRING_PRETTY)); 221 | json_object_put(result); 222 | return 0; 223 | } 224 | 225 | static void usage_snapshot(void) 226 | { 227 | fprintf(stderr, "Usage: ploop-volume snapshot [--image snapshot_image] \n"); 228 | } 229 | 230 | static int snapshot(int argc, char **argv) 231 | { 232 | int i; 233 | struct ploop_volume_data vol = {}; 234 | static struct option opts[] = { 235 | { "image", required_argument, 0, 1 }, 236 | {} 237 | }; 238 | 239 | while ((i = getopt_long(argc, argv, "", opts, NULL)) != EOF) { 240 | switch (i) { 241 | case 1: 242 | vol.i_path = optarg; 243 | break; 244 | default: 245 | return SYSEXIT_PARAM; 246 | } 247 | } 248 | 249 | argc -= optind; 250 | argv += optind; 251 | 252 | if (argc != 2) { 253 | usage_snapshot(); 254 | return SYSEXIT_PARAM; 255 | } 256 | 257 | vol.m_path = argv[1]; 258 | 259 | return ploop_volume_snapshot(argv[0], &vol); 260 | } 261 | 262 | static void usage_switch(void) 263 | { 264 | fprintf(stderr, "Usage: ploop-volume switch \n"); 265 | } 266 | 267 | static int volume_switch(int argc, char **argv) 268 | { 269 | if (argc != 3) { 270 | usage_switch(); 271 | return SYSEXIT_PARAM; 272 | } 273 | 274 | return ploop_volume_switch(argv[1], argv[2]); 275 | } 276 | 277 | static void usage_tree(void) 278 | { 279 | fprintf(stderr, "Usage: ploop-voulume tree \n"); 280 | } 281 | 282 | static int print_tree(int argc, char **argv) 283 | { 284 | int i, rc; 285 | static struct option opts[] = { 286 | {} 287 | }; 288 | struct ploop_volume_list_head head, *children; 289 | struct ploop_volume_tree_element *vol; 290 | struct json_object *json_result, *json_children; 291 | 292 | while ((i = getopt_long(argc, argv, "", opts, NULL)) != EOF) { 293 | switch (i) { 294 | default: 295 | return SYSEXIT_PARAM; 296 | } 297 | } 298 | 299 | argc -= optind; 300 | argv += optind; 301 | 302 | if (argc != 1) { 303 | usage_tree(); 304 | return SYSEXIT_PARAM; 305 | } 306 | 307 | SLIST_INIT(&head); 308 | rc = ploop_volume_get_tree(argv[0], &head, sizeof(head)); 309 | if (rc) 310 | return rc; 311 | 312 | vol = SLIST_FIRST(&head); 313 | children = &vol->children; 314 | 315 | json_result = json_object_new_object(); 316 | json_children = json_object_new_array(); 317 | json_object_object_add(json_result, "path", json_object_new_string(vol->path)); 318 | SLIST_FOREACH(vol, children, next) { 319 | struct json_object *child = json_object_new_object(); 320 | json_object_object_add(child, "path", json_object_new_string(vol->path)); 321 | json_object_array_add(json_children, child); 322 | } 323 | json_object_object_add(json_result, "children", json_children); 324 | printf("%s\n", json_object_to_json_string_ext(json_result, JSON_C_TO_STRING_PRETTY)); 325 | json_object_put(json_result); 326 | ploop_volume_clear_tree(&head); 327 | return 0; 328 | } 329 | 330 | static void usage_delete(void) 331 | { 332 | fprintf(stderr, "Usage: ploop-voulume delete \n"); 333 | } 334 | 335 | static int delete(int argc, char **argv) 336 | { 337 | int i; 338 | static struct option opts[] = { 339 | {} 340 | }; 341 | 342 | while ((i = getopt_long(argc, argv, "", opts, NULL)) != EOF) { 343 | switch (i) { 344 | default: 345 | return SYSEXIT_PARAM; 346 | } 347 | } 348 | 349 | argc -= optind; 350 | argv += optind; 351 | 352 | if (argc != 1) { 353 | usage_delete(); 354 | return SYSEXIT_PARAM; 355 | } 356 | 357 | return ploop_volume_delete(argv[0]); 358 | } 359 | 360 | int main(int argc, char **argv) 361 | { 362 | char *cmd; 363 | 364 | if (argc < 2) { 365 | usage_summary(); 366 | return SYSEXIT_PARAM; 367 | } 368 | 369 | cmd = argv[1]; 370 | argc--; 371 | argv++; 372 | 373 | init_signals(); 374 | ploop_set_verbose_level(3); 375 | 376 | if (strcmp(cmd, "create") == 0) 377 | return create(argc, argv); 378 | if (strcmp(cmd, "clone") == 0) 379 | return clone(argc, argv); 380 | if (strcmp(cmd, "info") == 0) 381 | return print_info(argc, argv); 382 | if (strcmp(cmd, "snapshot") == 0) 383 | return snapshot(argc, argv); 384 | if (strcmp(cmd, "switch") == 0) 385 | return volume_switch(argc, argv); 386 | if (strcmp(cmd, "tree") == 0) 387 | return print_tree(argc, argv); 388 | if (strcmp(cmd, "delete") == 0) 389 | return delete(argc, argv); 390 | 391 | usage_summary(); 392 | 393 | return SYSEXIT_PARAM; 394 | } 395 | --------------------------------------------------------------------------------