comparison cutils/treesum.py @ 131:3a18d71d7c50

Implement --follow-directory-symlinks when walking a directory tree
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 02 Jan 2025 20:52:49 +0100
parents d5621028ce39
children 8b73dca5db97
comparison
equal deleted inserted replaced
130:d5621028ce39 131:3a18d71d7c50
44 help="Append to the output file instead of overwriting it.") 44 help="Append to the output file instead of overwriting it.")
45 aparser.add_argument( 45 aparser.add_argument(
46 "--base64", action="store_true", 46 "--base64", action="store_true",
47 help="Output checksums in base64 notation, not hexadecimal (OpenBSD).") 47 help="Output checksums in base64 notation, not hexadecimal (OpenBSD).")
48 aparser.add_argument( 48 aparser.add_argument(
49 "--follow-directory-symlinks", action="store_true",
50 dest="follow_directory_symlinks",
51 help="Follow symbolic links to directories when walking a directory"
52 " tree. Note that this is different from using \"--logical\" or"
53 " \"--physical\" for arguments given directly on the command"
54 " line")
55 aparser.add_argument(
49 "--logical", "-L", dest="logical", action="store_true", default=None, 56 "--logical", "-L", dest="logical", action="store_true", default=None,
50 help="Follow symbolic links given on command line arguments." 57 help="Follow symbolic links given on command line arguments."
51 " Note that this is a different setting as to follow symbolic" 58 " Note that this is a different setting as to follow symbolic"
52 " links to directories when traversing a directory tree.") 59 " links to directories when traversing a directory tree.")
53 aparser.add_argument( 60 aparser.add_argument(
81 88
82 def gen_opts(directories=[], 89 def gen_opts(directories=[],
83 algorithm="BLAKE2b-256", 90 algorithm="BLAKE2b-256",
84 append_output=False, 91 append_output=False,
85 base64=False, 92 base64=False,
93 follow_directory_symlinks=False,
86 logical=None, 94 logical=None,
87 mmap=None, 95 mmap=None,
88 output=None): 96 output=None):
89 opts = argparse.Namespace(directories=directories, 97 opts = argparse.Namespace(
90 algorithm=(util.algotag2algotype(algorithm), 98 directories=directories,
91 algorithm), 99 algorithm=(util.algotag2algotype(algorithm),
92 append_output=append_output, 100 algorithm),
93 base64=base64, 101 append_output=append_output,
94 logical=logical, 102 base64=base64,
95 mmap=mmap, 103 follow_directory_symlinks=follow_directory_symlinks,
96 output=output) 104 logical=logical,
105 mmap=mmap,
106 output=output)
97 return opts 107 return opts
98 108
99 109
100 def treesum(opts): 110 def treesum(opts):
101 # XXX TBD: opts.check and opts.checklist (as in shasum.py) 111 # XXX TBD: opts.check and opts.checklist (as in shasum.py)
118 out_cm = open(opts.output, "wb") 128 out_cm = open(opts.output, "wb")
119 129
120 with out_cm as outfp: 130 with out_cm as outfp:
121 for d in opts.directories: 131 for d in opts.directories:
122 generate_treesum_for_directory( 132 generate_treesum_for_directory(
123 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical) 133 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical,
134 opts.follow_directory_symlinks)
124 135
125 136
126 def generate_treesum_for_directory( 137 def generate_treesum_for_directory(
127 outfp, root, algorithm, use_mmap, use_base64, handle_root_logical): 138 outfp, root, algorithm, use_mmap, use_base64, handle_root_logical,
139 follow_directory_symlinks):
128 """ 140 """
129 141
130 :param outfp: a *binary* file with a "write()" and a "flush()" method 142 :param outfp: a *binary* file with a "write()" and a "flush()" method
131 143
132 """ 144 """
135 147
136 # Note given non-default flags that are relevant for directory traversal 148 # Note given non-default flags that are relevant for directory traversal
137 flags = [] 149 flags = []
138 if handle_root_logical: 150 if handle_root_logical:
139 flags.append("logical") 151 flags.append("logical")
152 if follow_directory_symlinks:
153 flags.append("follow-directory-symlinks")
140 if flags: 154 if flags:
141 outfp.write(format_bsd_line("FLAGS", ",".join(flags), None, False)) 155 outfp.write(format_bsd_line("FLAGS", ",".join(flags), None, False))
142 156
143 # Write execution timestamps in POSIX epoch and ISO format 157 # Write execution timestamps in POSIX epoch and ISO format
144 ts = time.time() 158 ts = time.time()
161 "./@", 175 "./@",
162 use_base64)) 176 use_base64))
163 outfp.flush() 177 outfp.flush()
164 return 178 return
165 179
166 for top, dirs, nondirs in walk.walk(root, follow_symlinks=False): 180 for top, dirs, nondirs in walk.walk(
181 root,
182 follow_symlinks=follow_directory_symlinks):
167 dir_dgst = algorithm[0]() 183 dir_dgst = algorithm[0]()
168 for dn in dirs: 184 for dn in dirs:
169 if dn.is_symlink: 185 if dn.is_symlink and not follow_directory_symlinks:
170 linktgt = util.fsencode(os.readlink(dn.path)) 186 linktgt = util.fsencode(os.readlink(dn.path))
171 linkdgst = algorithm[0]() 187 linkdgst = algorithm[0]()
172 linkdgst.update(linktgt) 188 linkdgst.update(linktgt)
173 dir_dgst.update(b"1:S,%d:%s," % (len(dn.fsname), dn.fsname)) 189 dir_dgst.update(b"1:S,%d:%s," % (len(dn.fsname), dn.fsname))
174 dir_dgst.update(linkdgst.digest()) 190 dir_dgst.update(linkdgst.digest())
178 algorithm[1], 194 algorithm[1],
179 linkdgst.digest(), 195 linkdgst.digest(),
180 "%s/./@" % (opath,), 196 "%s/./@" % (opath,),
181 use_base64)) 197 use_base64))
182 outfp.flush() 198 outfp.flush()
183 else: 199 continue
184 # fetch from dir_digests 200 # fetch from dir_digests
185 dgst = dir_digests[top + (dn.name,)] 201 dgst = dir_digests[top + (dn.name,)]
186 dir_dgst.update(b"1:d,%d:%s," % (len(dn.fsname), dn.fsname)) 202 dir_dgst.update(b"1:d,%d:%s," % (len(dn.fsname), dn.fsname))
187 dir_dgst.update(dgst) 203 dir_dgst.update(dgst)
188 for fn in nondirs: 204 for fn in nondirs:
189 dir_dgst.update(b"1:f,%d:%s," % (len(fn.fsname), fn.fsname)) 205 dir_dgst.update(b"1:f,%d:%s," % (len(fn.fsname), fn.fsname))
190 dgst = digest.compute_digest_file( 206 dgst = digest.compute_digest_file(
191 algorithm[0], fn.path, use_mmap=use_mmap) 207 algorithm[0], fn.path, use_mmap=use_mmap)
192 dir_dgst.update(dgst) 208 dir_dgst.update(dgst)