ensure nested symlinks are not broken during deletes;

when moving/deleting a file, all symlinked dupes are verified to ensure
this action does not break any symlinks, however it did this by checking
the realpath of each link. This was not good enough, since the deleted
file may be a part of a series of nested symlinks

this situation occurs because the deduper tries to keep relative
symlinks as close as possible, only traversing into parent/sibling
folders as required, which can lead to several levels of nested links
This commit is contained in:
ed 2023-12-08 01:11:03 +00:00
parent e70ecd98ef
commit 9672b8c9b3
2 changed files with 43 additions and 4 deletions

View file

@ -43,6 +43,10 @@ def open(p: str, *a, **ka) -> int:
return os.open(fsenc(p), *a, **ka)
def readlink(p: str) -> str:
return fsdec(os.readlink(fsenc(p)))
def rename(src: str, dst: str) -> None:
return os.rename(fsenc(src), fsenc(dst))

View file

@ -3617,6 +3617,8 @@ class Up2k(object):
except:
self.log("relink: not found: [{}]".format(ap))
# self.log("full:\n" + "\n".join(" {:90}: {}".format(*x) for x in full.items()))
# self.log("links:\n" + "\n".join(" {:90}: {}".format(*x) for x in links.items()))
if not dabs and not full and links:
# deleting final remaining full copy; swap it with a symlink
slabs = list(sorted(links.keys()))[0]
@ -3634,12 +3636,45 @@ class Up2k(object):
dabs = list(sorted(full.keys()))[0]
for alink, parts in links.items():
lmod = None
lmod = 0.0
try:
if alink != sabs and absreal(alink) != sabs:
continue
faulty = False
ldst = alink
try:
for n in range(40): # MAXSYMLINKS
zs = bos.readlink(ldst)
ldst = os.path.join(os.path.dirname(ldst), zs)
ldst = bos.path.abspath(ldst)
if not bos.path.islink(ldst):
break
self.log("relinking [{}] to [{}]".format(alink, dabs))
if ldst == sabs:
t = "relink because level %d would break:"
self.log(t % (n,), 6)
faulty = True
except Exception as ex:
self.log("relink because walk failed: %s; %r" % (ex, ex), 3)
faulty = True
zs = absreal(alink)
if ldst != zs:
t = "relink because computed != actual destination:\n %s\n %s"
self.log(t % (ldst, zs), 3)
ldst = zs
faulty = True
if bos.path.islink(ldst):
raise Exception("broken symlink: %s" % (alink,))
if alink != sabs and ldst != sabs and not faulty:
continue # original symlink OK; leave it be
except Exception as ex:
t = "relink because symlink verification failed: %s; %r"
self.log(t % (ex, ex), 3)
self.log("relinking [%s] to [%s]" % (alink, dabs))
try:
lmod = bos.path.getmtime(alink, False)
bos.unlink(alink)
except: