diff cutils/treesum.py @ 125:12d6ec1f8613

Implement "--logical" and "--physical" to control following symlinked directories when given on the commandline
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 02 Jan 2025 17:03:10 +0100
parents 3bd3f32b5e60
children 6a50d02fe0ca
line wrap: on
line diff
--- a/cutils/treesum.py	Thu Jan 02 13:29:20 2025 +0100
+++ b/cutils/treesum.py	Thu Jan 02 17:03:10 2025 +0100
@@ -44,6 +44,11 @@
         "--base64", action="store_true",
         help="Output checksums in base64 notation, not hexadecimal (OpenBSD).")
     aparser.add_argument(
+        "--logical", "-L", dest="logical", action="store_true", default=None,
+        help="Follow symbolic links given on command line arguments."
+             " Note that this is a different setting as to follow symbolic"
+             " links to directories when traversing a directory tree.")
+    aparser.add_argument(
         "--mmap", action="store_true", dest="mmap", default=None,
         help="Use mmap if available. Default is to determine automatically "
              "from the filesize.")
@@ -55,6 +60,10 @@
         help="Put the checksum into given file. If not given of if it is given"
              " as `-' then stdout is used.")
     aparser.add_argument(
+        "--physical", "-P", dest="logical", action="store_false", default=None,
+        help="Do not follow symbolic links given on comment line arguments."
+             " This is the default.")
+    aparser.add_argument(
         "--version", "-v", action="version",
         version="%s (rv:%s)" % (__version__, __revision__))
     aparser.add_argument(
@@ -72,6 +81,7 @@
              algorithm="BLAKE2b-256",
              append_output=False,
              base64=False,
+             logical=None,
              mmap=None,
              output=None):
     opts = argparse.Namespace(directories=directories,
@@ -79,6 +89,7 @@
                                          algorithm),
                               append_output=append_output,
                               base64=base64,
+                              logical=logical,
                               mmap=mmap,
                               output=output)
     return opts
@@ -107,11 +118,11 @@
     with out_cm as outfp:
         for d in opts.directories:
             generate_treesum_for_directory(
-                outfp, d, opts.algorithm, opts.mmap, opts.base64)
+                outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical)
 
 
 def generate_treesum_for_directory(
-        outfp, root, algorithm, use_mmap, use_base64):
+        outfp, root, algorithm, use_mmap, use_base64, handle_root_logical):
     """
 
     :param outfp: a *binary* file with a "write()" and a "flush()" method
@@ -119,8 +130,31 @@
     """
     outfp.write(format_bsd_line("ROOT", None, root, False))
     outfp.flush()
+
+    # Note given non-default flags that are relevant for directory traversal
+    flags = []
+    if handle_root_logical:
+        flags.append(b"logical")
+    if flags:
+        outfp.write(format_bsd_line("FLAGS", None, b",".join(flags), False))
+
     dir_digests = {}
 
+    if not handle_root_logical and os.path.islink(root):
+        linktgt = util.fsencode(os.readlink(root))
+        linkdgst = algorithm[0]()
+        linkdgst.update(linktgt)
+        dir_dgst = algorithm[0]()
+        dir_dgst.update(b"1:S,4:/./@,")
+        dir_dgst.update(linkdgst.digest())
+        outfp.write(format_bsd_line(
+            algorithm[1],
+            dir_dgst.digest(),
+            "/./@",
+            use_base64))
+        outfp.flush()
+        return
+
     for top, dirs, nondirs in walk.walk(root, follow_symlinks=False):
         dir_dgst = algorithm[0]()
         for dn in dirs: