Skip to content

Commit

Permalink
storage/file: fix is_dirty() false positive
Browse files Browse the repository at this point in the history
is_dirty() returned a false positive if the volume was merely the source
of a currently running volume. For example, if fedora-33:root was the
source volume for myappvm:root and myappvm was running - then is_dirty()
returned True for fedora-33:root, because fedora-33/root-cow.img
contains some allocated blocks (one 256 KiB chunk containing only the
header) in this scenario, even though fedora-33 is shut down.

Fixes QubesOS/qubes-issues#6371
  • Loading branch information
rustybird committed Feb 11, 2021
1 parent 796e6f5 commit 05eb051
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 2 deletions.
4 changes: 3 additions & 1 deletion linux/system-config/block-snapshot
Expand Up @@ -4,6 +4,8 @@
#
# This creates dm-snapshot device on given arguments

SNAPSHOT_CHUNKSIZE=256 # same as in file.py

dir=$(dirname "$0")
if [ "$1" = "prepare" ] || [ "$1" = "cleanup" ]; then
# shellcheck disable=SC1090,SC1091
Expand Down Expand Up @@ -80,7 +82,7 @@ create_dm_snapshot() {
base_dev=$(get_dev "$base")
cow_dev=$(get_dev "$cow")
base_sz=$(blockdev --getsz "$base_dev")
do_or_die dmsetup create "$dm_devname" --table "0 $base_sz snapshot $base_dev $cow_dev P 256"
do_or_die dmsetup create "$dm_devname" --table "0 $base_sz snapshot $base_dev $cow_dev P $SNAPSHOT_CHUNKSIZE"
fi

}
Expand Down
11 changes: 10 additions & 1 deletion qubes/storage/file.py
Expand Up @@ -33,6 +33,12 @@

BLKSIZE = 512

# 256 KiB chunk, same as in block-snapshot script. Header created by
# struct.pack('<4I', 0x70416e53, 1, 1, 256) mimicking write_header()
# in linux/drivers/md/dm-snap-persistent.c
EMPTY_SNAPSHOT = b'SnAp\x01\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00\x00' \
+ bytes(262128)


class FilePool(qubes.storage.Pool):
''' File based 'original' disk implementation
Expand Down Expand Up @@ -217,7 +223,10 @@ def is_dirty(self):
if self.save_on_stop:
with suppress(FileNotFoundError), open(self.path_cow, 'rb') as cow:
cow_used = os.fstat(cow.fileno()).st_blocks * BLKSIZE
return cow_used > 0
return (cow_used > 0 and
(cow_used > len(EMPTY_SNAPSHOT) or
cow.read(len(EMPTY_SNAPSHOT)) != EMPTY_SNAPSHOT or
cow_used > cow.seek(0, os.SEEK_HOLE)))
return False

def resize(self, size):
Expand Down

0 comments on commit 05eb051

Please sign in to comment.