Browse Source

Merge remote-tracking branch 'origin/pr/384'

* origin/pr/384:
  storage/file: fix is_dirty() false positive
  storage/file: refactor is_dirty()
Marek Marczykowski-Górecki 3 years ago
parent
commit
df7cd63bd1
2 changed files with 16 additions and 6 deletions
  1. 3 1
      linux/system-config/block-snapshot
  2. 13 5
      qubes/storage/file.py

+ 3 - 1
linux/system-config/block-snapshot

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

+ 13 - 5
qubes/storage/file.py

@@ -33,6 +33,12 @@ import qubes.utils
 
 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
@@ -214,11 +220,13 @@ class FileVolume(qubes.storage.Volume):
             _remove_if_exists(self.path_cow)
 
     def is_dirty(self):
-        if not self.save_on_stop:
-            return False
-        if os.path.exists(self.path_cow):
-            stat = os.stat(self.path_cow)
-            return stat.st_blocks > 0
+        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 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):