diff --git a/copyparty/bos/bos.py b/copyparty/bos/bos.py index 6c876e04..408244b9 100644 --- a/copyparty/bos/bos.py +++ b/copyparty/bos/bos.py @@ -2,18 +2,22 @@ from __future__ import print_function, unicode_literals import os +import time from ..util import SYMTIME, fsdec, fsenc from . import path as path if True: # pylint: disable=using-constant-test - from typing import Any, Optional + from typing import Any, Optional, Union + + from ..util import NamedLogger MKD_755 = {"chmod_d": 0o755} MKD_700 = {"chmod_d": 0o700} +UTIME_CLAMPS = ((max, -2147483647), (max, 1), (min, 4294967294), (min, 2147483646)) -_ = (path, MKD_755, MKD_700) -__all__ = ["path", "MKD_755", "MKD_700"] +_ = (path, MKD_755, MKD_700, UTIME_CLAMPS) +__all__ = ["path", "MKD_755", "MKD_700", "UTIME_CLAMPS"] # grep -hRiE '(^|[^a-zA-Z_\.-])os\.' . | gsed -r 's/ /\n/g;s/\(/(\n/g' | grep -hRiE '(^|[^a-zA-Z_\.-])os\.' | sort | uniq -c # printf 'os\.(%s)' "$(grep ^def bos/__init__.py | gsed -r 's/^def //;s/\(.*//' | tr '\n' '|' | gsed -r 's/.$//')" @@ -99,6 +103,40 @@ def utime( return os.utime(fsenc(p), times) +def utime_c( + log: Union["NamedLogger", Any], p: str, ts: int, follow_symlinks: bool = True, throw: bool = False +) -> Optional[int]: + clamp = 0 + ov = ts + bp = fsenc(p) + now = int(time.time()) + while True: + try: + if SYMTIME: + os.utime(bp, (now, ts), follow_symlinks=follow_symlinks) + else: + os.utime(bp, (now, ts)) + if clamp: + t = "filesystem rejected utime(%r); clamped %s to %s" + log(t % (p, ov, ts)) + return ts + except Exception as ex: + pv = ts + while clamp < len(UTIME_CLAMPS): + fun, cv = UTIME_CLAMPS[clamp] + ts = fun(ts, cv) + clamp += 1 + if ts != pv: + break + if clamp >= len(UTIME_CLAMPS): + if throw: + raise + else: + t = "could not utime(%r) to %s; %s, %r" + log(t % (p, ov, ex, ex)) + return None + + if hasattr(os, "lstat"): def lstat(p: str) -> os.stat_result: diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index b0eb8736..ed18a0e7 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -409,12 +409,8 @@ class FtpFs(AbstractedFS): return st def utime(self, path: str, timeval: float) -> None: - try: - ap = self.rv2a(path, w=True)[0] - return bos.utime(ap, (int(time.time()), int(timeval))) - except Exception as ex: - logging.error("ftp.utime: %s, %r", ex, ex) - raise + ap = self.rv2a(path, w=True)[0] + bos.utime_c(logging.warning, ap, int(timeval), False) def lstat(self, path: str) -> os.stat_result: ap = self.rv2a(path)[0] diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index a379f655..072c5962 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -2329,12 +2329,7 @@ class HttpCli(object): at = mt = time.time() - lifetime cli_mt = self.headers.get("x-oc-mtime") if cli_mt: - try: - mt = int(cli_mt) - times = (int(time.time()), mt) - bos.utime(path, times, False) - except: - pass + bos.utime_c(self.log, path, int(cli_mt), False) if nameless and "magic" in vfs.flags: try: diff --git a/copyparty/smbd.py b/copyparty/smbd.py index 7caec6cc..3fbc1130 100644 --- a/copyparty/smbd.py +++ b/copyparty/smbd.py @@ -373,7 +373,7 @@ class SMB(object): t = "blocked utime (no-write-acc %s): /%s @%s" yeet(t % (vfs.axs.uwrite, vpath, uname)) - return bos.utime(ap, times) + bos.utime_c(info, ap, int(times[1]), False) def _p_exists(self, vpath: str) -> bool: # ap = "?" diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 1fc52333..1486ac17 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -3435,10 +3435,9 @@ class Up2k(object): cur.connection.commit() ap = djoin(job["ptop"], job["prel"], job["name"]) - times = (int(time.time()), int(cj["lmod"])) - bos.utime(ap, times, False) + mt = bos.utime_c(self.log, ap, int(cj["lmod"]), False, True) - self.log("touched %r from %d to %d" % (ap, job["lmod"], cj["lmod"])) + self.log("touched %r from %d to %d" % (ap, job["lmod"], mt)) except Exception as ex: self.log("umod failed, %r" % (ex,), 3) @@ -3593,8 +3592,7 @@ class Up2k(object): shutil.copy2(fsenc(csrc), fsenc(dst)) if lmod and (not linked or SYMTIME): - times = (int(time.time()), int(lmod)) - bos.utime(dst, times, False) + bos.utime_c(self.log, dst, int(lmod), False) def handle_chunks( self, ptop: str, wark: str, chashes: list[str] @@ -3767,10 +3765,8 @@ class Up2k(object): times = (int(time.time()), int(job["lmod"])) t = "no more chunks, setting times %s (%d) on %r" self.log(t % (times, bos.path.getsize(dst), dst)) - try: - bos.utime(dst, times) - except: - self.log("failed to utime (%r, %s)" % (dst, times)) + bos.utime_c(self.log, dst, times[1], False) + # the above logmsg (and associated logic) is retained due to unforget.py zs = "prel name lmod size ptop vtop wark dwrk host user addr" z2 = [job[x] for x in zs.split()] @@ -4919,7 +4915,10 @@ class Up2k(object): mt = bos.path.getmtime(slabs, False) flags = self.flags.get(ptop) or {} atomic_move(self.log, sabs, slabs, flags) - bos.utime(slabs, (int(time.time()), int(mt)), False) + try: + bos.utime(slabs, (int(time.time()), int(mt)), False) + except: + self.log("relink: failed to utime(%r, %s)" % (slabs, mt), 3) self._symlink(slabs, sabs, flags, False, is_mv=True) full[slabs] = (ptop, rem) sabs = slabs