Mercurial > hgrepos > Python > apps > py-cutils
comparison shasum.py @ 22:6bdfc5ad4656
Implemented OpenBSD's -C (aka --checklist) option for shasum
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 05 Dec 2020 15:08:46 +0100 |
| parents | f2d634270e1c |
| children | 232063b73e45 |
comparison
equal
deleted
inserted
replaced
| 21:f2d634270e1c | 22:6bdfc5ad4656 |
|---|---|
| 49 be verified against the specified paths. Output consists of the digest | 49 be verified against the specified paths. Output consists of the digest |
| 50 used, the file name, and an OK, FAILED, or MISSING for the result of | 50 used, the file name, and an OK, FAILED, or MISSING for the result of |
| 51 the comparison. This will validate any of the supported checksums. | 51 the comparison. This will validate any of the supported checksums. |
| 52 If no file is given, stdin is used.""") | 52 If no file is given, stdin is used.""") |
| 53 aparser.add_argument( | 53 aparser.add_argument( |
| 54 "--checklist", "-C", metavar="CHECKLIST", | |
| 55 help="""Compare the checksum of each FILE against the checksums in | |
| 56 the CHECKLIST. Any specified FILE that is not listed in the CHECKLIST will | |
| 57 generate an error.""") | |
| 58 | |
| 59 aparser.add_argument( | |
| 54 "--reverse", "-r", action="store_false", dest="bsd", default=False, | 60 "--reverse", "-r", action="store_false", dest="bsd", default=False, |
| 55 help="explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") | 61 help="explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") |
| 56 aparser.add_argument( | 62 aparser.add_argument( |
| 57 "--tag", action="store_true", dest="bsd", default=False, | 63 "--tag", action="store_true", dest="bsd", default=False, |
| 58 help="alias for the `--bsd' option (to be compatible with :command:`b2sum`)") | 64 help="alias for the `--bsd' option (to be compatible with :command:`b2sum`)") |
| 66 | 72 |
| 67 opts = aparser.parse_args(args=argv) | 73 opts = aparser.parse_args(args=argv) |
| 68 | 74 |
| 69 if opts.text_mode: | 75 if opts.text_mode: |
| 70 print("ERROR: text mode not supported", file=sys.stderr) | 76 print("ERROR: text mode not supported", file=sys.stderr) |
| 71 sys.exit(78) # :manpage:`sysexits(3)` EX_CONFIG | 77 sys.exit(78) # :manpage:`sysexits(3)` EX_CONFIG |
| 78 | |
| 79 if opts.check and opts.checklist: | |
| 80 print("ERROR: only one of --check or --checklist allowed", | |
| 81 file=sys.stderr) | |
| 82 sys.exit(64) # :manpage:`sysexits(3)` EX_USAGE | |
| 72 | 83 |
| 73 if not opts.algorithm: | 84 if not opts.algorithm: |
| 74 opts.algorithm = argv2algo("1") | 85 opts.algorithm = argv2algo("1") |
| 75 | 86 |
| 76 if opts.check: | 87 if opts.check: |
| 77 return verify_digests_from_files(opts) | 88 return verify_digests_from_files(opts) |
| 89 elif opts.checklist: | |
| 90 return verify_digests_with_checklist(opts) | |
| 78 else: | 91 else: |
| 79 return generate_digests(opts) | 92 return generate_digests(opts) |
| 80 | 93 |
| 81 | 94 |
| 82 def generate_digests(opts): | 95 def generate_digests(opts): |
| 104 compute_digest(opts.algorithm[0], source), | 117 compute_digest(opts.algorithm[0], source), |
| 105 fn, | 118 fn, |
| 106 opts.algorithm[1], | 119 opts.algorithm[1], |
| 107 True) | 120 True) |
| 108 return 0 | 121 return 0 |
| 122 | |
| 123 | |
| 124 def verify_digests_with_checklist(opts): | |
| 125 exit_code = 0 | |
| 126 if not opts.files or (len(opts.files) == 1 and opts.files[0] == '-'): | |
| 127 if PY2: | |
| 128 if sys.platform == "win32": | |
| 129 import os, msvcrt # noqa: E401 | |
| 130 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) | |
| 131 source = sys.stdin | |
| 132 else: | |
| 133 source = sys.stdin.buffer | |
| 134 pl = get_parsed_digest_line_from_checklist(opts.checklist, opts, None) | |
| 135 if pl is None: | |
| 136 exit_code = 1 | |
| 137 print("-: MISSING") | |
| 138 else: | |
| 139 tag, algo, cl_filename, cl_digest = pl | |
| 140 computed_digest = compute_digest(algo, source) | |
| 141 if cl_digest.lower() == computed_digest.lower(): | |
| 142 res = "OK" | |
| 143 else: | |
| 144 res = "FAILED" | |
| 145 exit_code = 1 | |
| 146 print("{}: {}: {}".format(tag, "-", res)) | |
| 147 else: | |
| 148 for fn in opts.files: | |
| 149 pl = get_parsed_digest_line_from_checklist(opts.checklist, opts, fn) | |
| 150 if pl is None: | |
| 151 print("{}: MISSING".format(fn)) | |
| 152 exit_code = 1 | |
| 153 else: | |
| 154 tag, algo, cl_filename, cl_digest = pl | |
| 155 with open(fn, "rb") as source: | |
| 156 computed_digest = compute_digest(algo, source) | |
| 157 if cl_digest.lower() == computed_digest.lower(): | |
| 158 res = "OK" | |
| 159 else: | |
| 160 exit_code = 1 | |
| 161 res = "FAILED" | |
| 162 print("{}: {}: {}".format(tag, fn, res)) | |
| 163 return exit_code | |
| 109 | 164 |
| 110 | 165 |
| 111 def verify_digests_from_files(opts): | 166 def verify_digests_from_files(opts): |
| 112 exit_code = 0 | 167 exit_code = 0 |
| 113 if not opts.files or (len(opts.files) == 1 and opts.files[0] == '-'): | 168 if not opts.files or (len(opts.files) == 1 and opts.files[0] == '-'): |
| 150 return ("ok", fn, tag) | 205 return ("ok", fn, tag) |
| 151 else: | 206 else: |
| 152 return ("failed", fn, tag) | 207 return ("failed", fn, tag) |
| 153 except EnvironmentError: | 208 except EnvironmentError: |
| 154 return ("missing", fn, tag) | 209 return ("missing", fn, tag) |
| 210 | |
| 211 | |
| 212 def get_parsed_digest_line_from_checklist(checklist, opts, filename): | |
| 213 if filename is None: | |
| 214 filenames = ("-", "stdin", "", ) | |
| 215 else: | |
| 216 filenames = ( | |
| 217 normalize_filename(filename, strip_leading_dot_slash=True),) | |
| 218 with io.open(checklist, "rt", encoding="utf-8") as clf: | |
| 219 for checkline in clf: | |
| 220 if not checkline: | |
| 221 continue | |
| 222 parts = parse_digest_line(opts, checkline) | |
| 223 if not parts: | |
| 224 raise ValueError( | |
| 225 "improperly formatted digest line: {}".format(checkline)) | |
| 226 fn = normalize_filename(parts[2], strip_leading_dot_slash=True) | |
| 227 if fn in filenames: | |
| 228 return parts | |
| 229 else: | |
| 230 return None | |
| 155 | 231 |
| 156 | 232 |
| 157 def parse_digest_line(opts, line): | 233 def parse_digest_line(opts, line): |
| 158 """Parse a `line` of a digest file and return its parts. | 234 """Parse a `line` of a digest file and return its parts. |
| 159 | 235 |
