]> arthur.barton.de Git - bup.git/commitdiff
Test symlink target changes between stat and readlink
authorJohannes Berg <johannes@sipsolutions.net>
Thu, 31 Dec 2020 22:10:05 +0000 (23:10 +0100)
committerRob Browning <rlb@defaultvalue.org>
Sun, 30 May 2021 16:24:40 +0000 (11:24 -0500)
Test the changes in "metadata: fix symlink stat() vs. readlink() race".

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Reviewed-by: Rob Browning <rlb@defaultvalue.org>
[rlb@defaultvalue.org: adjust commit message; rework to use injection]
Signed-off-by: Rob Browning <rlb@defaultvalue.org>
Tested-by: Rob Browning <rlb@defaultvalue.org>
lib/bup/cmd/save.py
lib/bup/metadata.py
test/ext/test-save-symlink-race [new file with mode: 0755]

index 3e5696ce4c8c5abb6eb6950062acfe5512d9ff9c..1f9f3223d46aecdaeae03168156ad9bf7d78275c 100755 (executable)
@@ -37,10 +37,15 @@ graft=     a graft point *old_path*=*new_path* (can be used more than once)
 #,compress=  set compression level to # (0-9, 9 is highest) [1]
 """
 
-# Test hook
+
+### Test hooks
+
+after_nondir_metadata_stat = None
+
 def before_saving_regular_file(name):
     return
 
+
 def main(argv):
 
     # Hack around lack of nonlocal vars in python 2
@@ -416,7 +421,8 @@ def main(argv):
             hlink = find_hardlink_target(hlink_db, ent)
             try:
                 meta = metadata.from_path(ent.name, hardlink_target=hlink,
-                                          normalized=True)
+                                          normalized=True,
+                                          after_stat=after_nondir_metadata_stat)
             except (OSError, IOError) as e:
                 add_error(e)
                 lastskip_name = ent.name
index 67f3a079d9480aab29a876d76e175eb27624456b..b68c9851f3c9e82bf79e3f640fc69154fe89eaf7 100644 (file)
@@ -887,7 +887,7 @@ class Metadata:
 
 def from_path(path, statinfo=None, archive_path=None,
               save_symlinks=True, hardlink_target=None,
-              normalized=False):
+              normalized=False, after_stat=None):
     # This function is also a test hook; see test-save-errors
     """Return the metadata associated with the path.  When normalized is
     true, return the metadata appropriate for a typical save, which
@@ -895,6 +895,8 @@ def from_path(path, statinfo=None, archive_path=None,
     result = Metadata()
     result.path = archive_path
     st = statinfo or xstat.lstat(path)
+    if after_stat:
+        after_stat(path)
     result._add_common(path, st)
     if save_symlinks:
         result._add_symlink_target(path, st)
diff --git a/test/ext/test-save-symlink-race b/test/ext/test-save-symlink-race
new file mode 100755 (executable)
index 0000000..288059a
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+. wvtest.sh
+. wvtest-bup.sh
+. dev/lib.sh
+
+set -o pipefail
+
+top="$(WVPASS pwd)" || exit $?
+tmpdir="$(WVPASS wvmktempdir)" || exit $?
+export BUP_DIR="$tmpdir/bup"
+
+bup() { "$top/bup" "$@"; }
+
+# Inject code to coordinate test
+
+WVPASS rm -rf "$tmpdir/mod"
+WVPASS mkdir -p "$tmpdir/mod"
+cat > "$tmpdir/mod/pause_after_save_stat.py" << EOF
+
+import os, time
+import bup.cmd.save
+
+import sys
+
+def test_save_symlink_race_pause_save(name):
+    if name == b'$tmpdir/save/link':
+        with open('$tmpdir/waiting-after-save-stat', 'w') as f:
+             pass
+        while os.path.exists('$tmpdir/block-save'):
+           time.sleep(0.01)
+
+bup.cmd.save.after_nondir_metadata_stat = test_save_symlink_race_pause_save
+
+EOF
+
+instrumented-bup()
+{
+    PYTHONPATH="$tmpdir/mod" bup --import-py-module pause_after_save_stat "$@"
+}
+
+WVPASS cd "$tmpdir"
+WVPASS bup init
+WVPASS mkdir "$tmpdir/save"
+
+WVSTART "symlink metadata vs. content race"
+WVPASS ln -sf a "$tmpdir/save/link"
+WVPASS bup index "$tmpdir/save"
+WVPASS touch "$tmpdir/block-save"
+
+(
+    set -e
+    while ! test -e "$tmpdir/waiting-after-save-stat"; do
+        "$top/dev/python" -c 'import time; time.sleep(0.01)'
+    done
+    ln -sf abc "$tmpdir/save/link"
+    rm "$tmpdir/block-save"
+) &
+truncator=$!
+trap "kill $truncator" EXIT
+
+WVPASS instrumented-bup save -n test "$tmpdir/save"
+
+meta_tgt=$(WVPASS bup ls -ls "test/latest/$tmpdir/save/link" |
+               sed 's/.* -> //')
+data_tgt=$(git -C "$BUP_DIR" show $(WVPASS bup ls -ls "test/latest/$tmpdir/save/link" |
+                                        sed 's/ .*//'))
+WVPASSEQ abc $meta_tgt
+WVPASSEQ abc $data_tgt
+
+WVPASS rm -rf "$tmpdir"