changeset 157:27d1aaf5fe39

Implement "--mode" flag for "treesum.py" to consider file portable mode bits
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 06 Jan 2025 21:03:10 +0100
parents 481cc9b26861
children d8cdd1985d43
files cutils/treesum.py
diffstat 1 files changed, 31 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/cutils/treesum.py	Mon Jan 06 14:38:07 2025 +0100
+++ b/cutils/treesum.py	Mon Jan 06 21:03:10 2025 +0100
@@ -18,6 +18,7 @@
 import binascii
 import datetime
 import os
+import stat
 import sys
 import time
 
@@ -78,6 +79,12 @@
             help="Use mmap if available. Default is to determine "
                  "automatically from the filesize.")
         gp.add_argument(
+            "--mode", action="store_true", dest="metadata_mode",
+            help="Consider the permission bits of stat using S_IMODE (i.e. "
+                 "all bits without the filetype bits) when "
+                 "computing directory digests. Note that symbolic links "
+                 "are not considered.")
+        gp.add_argument(
             "--mtime", action="store_true", dest="metadata_mtime",
             help="Consider the mtime of files (non-directories) when "
                  "generating digests for directories. Digests for files are "
@@ -189,6 +196,7 @@
                       follow_directory_symlinks=False,
                       logical=None,
                       minimal=None,
+                      mode=False,
                       mmap=None,
                       mtime=False,
                       output=None):
@@ -203,6 +211,7 @@
         logical=logical,
         minimal=minimal,
         mmap=mmap,
+        metadata_mode=mode,
         metadata_mtime=mtime,
         output=output)
     return opts
@@ -240,6 +249,7 @@
             generate_treesum_for_directory(
                 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical,
                 opts.follow_directory_symlinks,
+                opts.metadata_mode,
                 opts.metadata_mtime,
                 minimal=opts.minimal,
                 comment=opts.comment)
@@ -247,7 +257,7 @@
 
 def generate_treesum_for_directory(
         outfp, root, algorithm, use_mmap, use_base64, handle_root_logical,
-        follow_directory_symlinks, with_metadata_mtime,
+        follow_directory_symlinks, with_metadata_mode, with_metadata_mtime,
         minimal=None, comment=None):
     """
 
@@ -321,6 +331,7 @@
                     linkdgst.update(b"%d:%s," % (len(linktgt), linktgt))
                     dir_dgst.update(b"1:S,%d:%s,"
                                     % (len(fso.fsname), fso.fsname))
+                    # no mtime and no mode for symlinks
                     dir_dgst.update(linkdgst.digest())
                     opath = "/".join(top) + "/" + fso.name if top else fso.name
                     outfp.write(
@@ -335,6 +346,11 @@
                 dgst = dir_digests[top + (fso.name,)]
                 dir_dgst.update(b"1:d,%d:%s," % (len(fso.fsname), fso.fsname))
                 dir_dgst.update(dgst)
+                if with_metadata_mode:
+                    modestr = normalized_mode_str(fso.stat.st_mode)
+                    if not isinstance(modestr, bytes):
+                        modestr = modestr.encode("ascii")
+                    dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr))
             else:
                 dir_dgst.update(b"1:f,%d:%s," % (len(fso.fsname), fso.fsname))
                 if with_metadata_mtime:
@@ -344,6 +360,11 @@
                     if not isinstance(mtime, bytes):
                         mtime = mtime.encode("ascii")
                     dir_dgst.update(b"5:mtime,%d:%s," % (len(mtime), mtime))
+                if with_metadata_mode:
+                    modestr = normalized_mode_str(fso.stat.st_mode)
+                    if not isinstance(modestr, bytes):
+                        modestr = modestr.encode("ascii")
+                    dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr))
                 dgst = digest.compute_digest_file(
                     algorithm[0], fso.path, use_mmap=use_mmap)
                 dir_dgst.update(dgst)
@@ -359,6 +380,15 @@
         dir_digests[top] = dir_dgst.digest()
 
 
+def normalized_mode_str(mode):
+    # XXX FIXME: Windows and "executable"
+    modebits = stat.S_IMODE(mode)
+    modestr = "%o" % (modebits,)
+    if not modestr.startswith("0"):
+        modestr = "0" + modestr
+    return modestr
+
+
 def format_bsd_line(digestname, value, filename, use_base64):
     ls = os.linesep if isinstance(os.linesep, bytes) \
         else os.linesep.encode("utf-8")