git: 4849767cb16a - main - md5: Improve compatibility.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Mon, 08 May 2023 08:05:25 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=4849767cb16a4dbd4d1b923db25d34029c09e7b0

commit 4849767cb16a4dbd4d1b923db25d34029c09e7b0
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2023-05-08 06:56:09 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2023-05-08 06:56:22 +0000

    md5: Improve compatibility.
    
    * Overhaul the GNU compatibility mode to more closely emulate what the GNU tools do.
    
    * Add a Perl compatibility mode which emulates the shasum tool that ships with Perl.  This is currently not installed.
    
    * Overhaul the tests.
    
    Sponsored by:   Klara, Inc.
    Reviewed by:    kevans
    Differential Revision:  https://reviews.freebsd.org/D39446
---
 ObsoleteFiles.inc                       |  73 ++++
 sbin/md5/Makefile                       |   6 +
 sbin/md5/md5.1                          | 313 +++++++++++----
 sbin/md5/md5.c                          | 665 ++++++++++++++++++++++++--------
 sbin/md5/tests/1.inp                    |   0
 sbin/md5/tests/1.sha512-p.chk           |   1 -
 sbin/md5/tests/1.sha512sum-p.chk        |   1 -
 sbin/md5/tests/2.inp                    |   1 -
 sbin/md5/tests/2.sha512-p.chk           |   1 -
 sbin/md5/tests/2.sha512sum-p.chk        |   1 -
 sbin/md5/tests/3.inp                    |   1 -
 sbin/md5/tests/3.sha512-p.chk           |   1 -
 sbin/md5/tests/3.sha512sum-p.chk        |   1 -
 sbin/md5/tests/4.inp                    |   1 -
 sbin/md5/tests/4.sha512-p.chk           |   1 -
 sbin/md5/tests/4.sha512sum-p.chk        |   1 -
 sbin/md5/tests/5.inp                    |   1 -
 sbin/md5/tests/5.sha512-p.chk           |   1 -
 sbin/md5/tests/5.sha512sum-p.chk        |   1 -
 sbin/md5/tests/6.inp                    |   1 -
 sbin/md5/tests/6.sha512-p.chk           |   1 -
 sbin/md5/tests/6.sha512sum-p.chk        |   1 -
 sbin/md5/tests/7.inp                    |   1 -
 sbin/md5/tests/7.sha512-p.chk           |   1 -
 sbin/md5/tests/7.sha512sum-p.chk        |   1 -
 sbin/md5/tests/8.inp                    |   1 -
 sbin/md5/tests/8.sha512-p.chk           |   1 -
 sbin/md5/tests/8.sha512sum-p.chk        |   1 -
 sbin/md5/tests/Makefile                 |  35 --
 sbin/md5/tests/bsd-c-test.SH            |  23 --
 sbin/md5/tests/bsd-p-test.SH            |  24 --
 sbin/md5/tests/bsd-s-test.SH            |  30 --
 sbin/md5/tests/coreutils-c-test.SH      |  21 -
 sbin/md5/tests/md5.digest               |   8 -
 sbin/md5/tests/md5_test.sh              | 346 +++++++++++++++--
 sbin/md5/tests/md5sum.digest            |   8 -
 sbin/md5/tests/rmd160.digest            |   8 -
 sbin/md5/tests/rmd160sum.digest         |   8 -
 sbin/md5/tests/self-test.SH             |   8 -
 sbin/md5/tests/self-test.md5.chk        |   9 -
 sbin/md5/tests/self-test.rmd160.chk     |   9 -
 sbin/md5/tests/self-test.sh_inp         |   8 -
 sbin/md5/tests/self-test.sha1.chk       |   9 -
 sbin/md5/tests/self-test.sha224.chk     |   9 -
 sbin/md5/tests/self-test.sha256.chk     |   9 -
 sbin/md5/tests/self-test.sha384.chk     |   9 -
 sbin/md5/tests/self-test.sha512.chk     |   9 -
 sbin/md5/tests/self-test.sha512t224.chk |   9 -
 sbin/md5/tests/self-test.sha512t256.chk |   9 -
 sbin/md5/tests/self-test.skein1024.chk  |   9 -
 sbin/md5/tests/self-test.skein256.chk   |   9 -
 sbin/md5/tests/self-test.skein512.chk   |   9 -
 sbin/md5/tests/sha1.digest              |   8 -
 sbin/md5/tests/sha1sum.digest           |   8 -
 sbin/md5/tests/sha224.digest            |   8 -
 sbin/md5/tests/sha224sum.digest         |   8 -
 sbin/md5/tests/sha256.digest            |   8 -
 sbin/md5/tests/sha256sum.digest         |   8 -
 sbin/md5/tests/sha384.digest            |   8 -
 sbin/md5/tests/sha384sum.digest         |   8 -
 sbin/md5/tests/sha512.digest            |   8 -
 sbin/md5/tests/sha512sum.digest         |   8 -
 sbin/md5/tests/sha512t224.digest        |   8 -
 sbin/md5/tests/sha512t224sum.digest     |   8 -
 sbin/md5/tests/sha512t256.digest        |   8 -
 sbin/md5/tests/sha512t256sum.digest     |   8 -
 sbin/md5/tests/skein1024.digest         |   8 -
 sbin/md5/tests/skein1024sum.digest      |   8 -
 sbin/md5/tests/skein256.digest          |   8 -
 sbin/md5/tests/skein256sum.digest       |   8 -
 sbin/md5/tests/skein512.digest          |   8 -
 sbin/md5/tests/skein512sum.digest       |   8 -
 sbin/md5/tests/sum_a.in                 |   1 -
 sbin/md5/tests/sum_b.in                 |   1 -
 sbin/md5/tests/sum_c.in                 |   1 -
 sbin/md5/tests/sum_sums.digest          |   3 -
 76 files changed, 1134 insertions(+), 747 deletions(-)

diff --git a/ObsoleteFiles.inc b/ObsoleteFiles.inc
index ede2871455ae..97881f40ac59 100644
--- a/ObsoleteFiles.inc
+++ b/ObsoleteFiles.inc
@@ -52,6 +52,79 @@
 #   xargs -n1 | sort | uniq -d;
 # done
 
+# 20230505: md5 tests are now self-contained
+OLD_FILES+=usr/tests/sbin/md5/1.inp
+OLD_FILES+=usr/tests/sbin/md5/1.sha512-p.chk
+OLD_FILES+=usr/tests/sbin/md5/1.sha512sum-p.chk
+OLD_FILES+=usr/tests/sbin/md5/2.inp
+OLD_FILES+=usr/tests/sbin/md5/2.sha512-p.chk
+OLD_FILES+=usr/tests/sbin/md5/2.sha512sum-p.chk
+OLD_FILES+=usr/tests/sbin/md5/3.inp
+OLD_FILES+=usr/tests/sbin/md5/3.sha512-p.chk
+OLD_FILES+=usr/tests/sbin/md5/3.sha512sum-p.chk
+OLD_FILES+=usr/tests/sbin/md5/4.inp
+OLD_FILES+=usr/tests/sbin/md5/4.sha512-p.chk
+OLD_FILES+=usr/tests/sbin/md5/4.sha512sum-p.chk
+OLD_FILES+=usr/tests/sbin/md5/5.inp
+OLD_FILES+=usr/tests/sbin/md5/5.sha512-p.chk
+OLD_FILES+=usr/tests/sbin/md5/5.sha512sum-p.chk
+OLD_FILES+=usr/tests/sbin/md5/6.inp
+OLD_FILES+=usr/tests/sbin/md5/6.sha512-p.chk
+OLD_FILES+=usr/tests/sbin/md5/6.sha512sum-p.chk
+OLD_FILES+=usr/tests/sbin/md5/7.inp
+OLD_FILES+=usr/tests/sbin/md5/7.sha512-p.chk
+OLD_FILES+=usr/tests/sbin/md5/7.sha512sum-p.chk
+OLD_FILES+=usr/tests/sbin/md5/8.inp
+OLD_FILES+=usr/tests/sbin/md5/8.sha512-p.chk
+OLD_FILES+=usr/tests/sbin/md5/8.sha512sum-p.chk
+OLD_FILES+=usr/tests/sbin/md5/algorithms.txt
+OLD_FILES+=usr/tests/sbin/md5/bsd-c-test
+OLD_FILES+=usr/tests/sbin/md5/bsd-p-test
+OLD_FILES+=usr/tests/sbin/md5/bsd-s-test
+OLD_FILES+=usr/tests/sbin/md5/coreutils-c-test
+OLD_FILES+=usr/tests/sbin/md5/md5.digest
+OLD_FILES+=usr/tests/sbin/md5/md5sum.digest
+OLD_FILES+=usr/tests/sbin/md5/rmd160.digest
+OLD_FILES+=usr/tests/sbin/md5/rmd160sum.digest
+OLD_FILES+=usr/tests/sbin/md5/self-test
+OLD_FILES+=usr/tests/sbin/md5/self-test.md5.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.rmd160.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.sh_inp
+OLD_FILES+=usr/tests/sbin/md5/self-test.sha1.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.sha224.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.sha256.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.sha384.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.sha512.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.sha512t224.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.sha512t256.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.skein1024.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.skein256.chk
+OLD_FILES+=usr/tests/sbin/md5/self-test.skein512.chk
+OLD_FILES+=usr/tests/sbin/md5/sha1.digest
+OLD_FILES+=usr/tests/sbin/md5/sha1sum.digest
+OLD_FILES+=usr/tests/sbin/md5/sha224.digest
+OLD_FILES+=usr/tests/sbin/md5/sha224sum.digest
+OLD_FILES+=usr/tests/sbin/md5/sha256.digest
+OLD_FILES+=usr/tests/sbin/md5/sha256sum.digest
+OLD_FILES+=usr/tests/sbin/md5/sha384.digest
+OLD_FILES+=usr/tests/sbin/md5/sha384sum.digest
+OLD_FILES+=usr/tests/sbin/md5/sha512.digest
+OLD_FILES+=usr/tests/sbin/md5/sha512sum.digest
+OLD_FILES+=usr/tests/sbin/md5/sha512t224.digest
+OLD_FILES+=usr/tests/sbin/md5/sha512t224sum.digest
+OLD_FILES+=usr/tests/sbin/md5/sha512t256.digest
+OLD_FILES+=usr/tests/sbin/md5/sha512t256sum.digest
+OLD_FILES+=usr/tests/sbin/md5/skein1024.digest
+OLD_FILES+=usr/tests/sbin/md5/skein1024sum.digest
+OLD_FILES+=usr/tests/sbin/md5/skein256.digest
+OLD_FILES+=usr/tests/sbin/md5/skein256sum.digest
+OLD_FILES+=usr/tests/sbin/md5/skein512.digest
+OLD_FILES+=usr/tests/sbin/md5/skein512sum.digest
+OLD_FILES+=usr/tests/sbin/md5/sum_a.in
+OLD_FILES+=usr/tests/sbin/md5/sum_b.in
+OLD_FILES+=usr/tests/sbin/md5/sum_c.in
+OLD_FILES+=usr/tests/sbin/md5/sum_sums.digest
+
 # 20230420: portsnap.8 removed
 OLD_FILES+=etc/portsnap.conf
 OLD_FILES+=usr/libexec/make_index
diff --git a/sbin/md5/Makefile b/sbin/md5/Makefile
index 6bda75437275..c9faec285aea 100644
--- a/sbin/md5/Makefile
+++ b/sbin/md5/Makefile
@@ -52,6 +52,12 @@ MLINKS=	md5.1 md5sum.1 \
 	md5.1 skein1024.1 \
 	md5.1 skein1024sum.1
 
+# md5 can also emulate the shasum script that comes with Perl, except
+# that, in bits input mode, it can only handle input lengths that are
+# a multiple of 8 (see manual page).
+#LINKS+= ${BINDIR}/md5 ${BINDIR}/shasum
+#MLINKS+= md5.1 shasum.1
+
 LIBADD=	md
 
 .ifndef(BOOTSTRAPPING)
diff --git a/sbin/md5/md5.1 b/sbin/md5/md5.1
index ba654e131c3c..bd619587e7a9 100644
--- a/sbin/md5/md5.1
+++ b/sbin/md5/md5.1
@@ -1,5 +1,5 @@
 .\" $FreeBSD$
-.Dd February 6, 2023
+.Dd April 12, 2023
 .Dt MD5 1
 .Os
 .Sh NAME
@@ -8,7 +8,8 @@
 .Nm rmd160 , skein256 , skein512 , skein1024 ,
 .Nm md5sum , sha1sum , sha224sum , sha256sum , sha384sum ,
 .Nm sha512sum , sha512t224sum , sha512t256sum ,
-.Nm rmd160sum , skein256sum , skein512sum , skein1024sum
+.Nm rmd160sum , skein256sum , skein512sum , skein1024sum ,
+.Nm shasum
 .Nd calculate a message-digest fingerprint (checksum) for a file
 .Sh SYNOPSIS
 .Nm
@@ -18,12 +19,40 @@
 .Op Ar
 .Pp
 .Nm md5sum
-.Op Fl pqrtx
-.Op Fl c Ar file
-.Op Fl s Ar string
+.Op Fl bctwz
+.Op Fl -binary
+.Op Fl -check
+.Op Fl -help
+.Op Fl -ignore-missing
+.Op Fl -quiet
+.Op Fl -status
+.Op Fl -strict
+.Op Fl -tag
+.Op Fl -text
+.Op Fl -version
+.Op Fl -warn
+.Op Fl -zero
 .Op Ar
 .Pp
 (All other hashes have the same options and usage.)
+.Pp
+.Nm shasum
+.Op Fl 0bchqstUvw
+.Op Fl -01
+.Op Fl a | -algorithm Ar alg
+.Op Fl -binary
+.Op Fl -check
+.Op Fl -help
+.Op Fl -ignore-missing
+.Op Fl -quiet
+.Op Fl -status
+.Op Fl -strict
+.Op Fl -tag
+.Op Fl -text
+.Op Fl -UNIVERSAL
+.Op Fl -version
+.Op Fl -warn
+.Op Ar
 .Sh DESCRIPTION
 The
 .Nm md5 , sha1 , sha224 , sha256 , sha384 , sha512 , sha512t224 , sha512t256 ,
@@ -36,15 +65,21 @@ output a
 or
 .Dq message digest
 of the input.
+.Pp
 The
 .Nm md5sum , sha1sum , sha224sum , sha256sum , sha384sum , sha512sum ,
 .Nm sha512t224sum , sha512t256sum , rmd160sum , skein256sum , skein512sum ,
 and
 .Nm skein1024sum
-utilities do the same, but default to the reversed format of
-the
-.Fl r
-flag.
+utilities do the same, but with command-line options and an output
+format that match those of their similary named GNU utilities.
+.Pp
+The
+.Nm shasum
+utility does the same, but with command-line options and an output
+format that match those of the similarly named utility that ships with
+Perl.
+.Pp
 It is conjectured that it is computationally infeasible to
 produce two messages having the same message digest, or to produce any
 message having a given prespecified target message digest.
@@ -75,73 +110,171 @@ to 224 bits.
 .Pp
 It is recommended that all new applications use SHA-512 or SKEIN-512
 instead of one of the other hash functions.
-.Pp
-The following options may be used in any combination and must
-precede any files named on the command line.
-The hexadecimal checksum of each file listed on the command line is printed
-after the options are processed.
+.Ss BSD OPTIONS
+The following options are available in BSD mode, i.e. when the program
+is invoked with a name that does not end in
+.Dq sum :
 .Bl -tag -width indent
-.It Fl b
-Make the
-.Nm -sum
-programs separate hash and digest with a blank followed by an asterisk instead
-of by 2 blank characters for full compatibility with the output generated by the
-coreutils versions of these programs.
-.It Fl c Ar string
-If the program was called with a name that does not end in
-.Nm sum ,
-compare the digest of the file against this string.
+.It Fl c Ar string , Fl -check= Ns Ar string
+Compare the digest of the file against this string.
 If combined with the
 .Fl q
+or
+.Fl -quiet
 option, the calculated digest is printed in addition to the exit status being set.
 .Pq Note that this option is not yet useful if multiple files are specified.
-.It Fl c Ar file
-If the program was called with a name that does end in
-.Nm sum ,
-the file passed as argument must contain digest lines generated by the same
-digest algorithm with or without the
-.Fl r
-option
-.Pq i.e., in either classical BSD format or in GNU coreutils format .
-A line with the file name followed by a colon
-.Dq ":"
-and either OK or FAILED is written for each well-formed line in the digest file.
-If applicable, the number of failed comparisons and the number of lines that were
-skipped since they were not well-formed are printed at the end.
-The
-.Fl q
-option can be used to quiesce the output unless there are mismatched entries in
-the digest.
-.Pp
-.It Fl s Ar string
-Print a checksum of the given
-.Ar string .
-.It Fl p
+.It Fl p , -passthrough
 Echo stdin to stdout and append the checksum to stdout.
-.It Fl q
+.It Fl q , -quiet
 Quiet mode \(em only the checksum is printed out.
 Overrides the
 .Fl r
+or
+.Fl -reverse
 option.
-.It Fl r
+.It Fl r , -reverse
 Reverses the format of the output.
 This helps with visual diffs.
 Does nothing
 when combined with the
 .Fl ptx
 options.
-.It Fl t
+.It Fl s Ar string , Fl -string= Ns Ar string
+Print a checksum of the given
+.Ar string .
+.It Fl t , Fl -time-trial
 Run a built-in time trial.
 For the
 .Nm -sum
 versions, this is a nop for compatibility with coreutils.
-.It Fl x
+.It Fl x , Fl -self-test
 Run a built-in test script.
 .El
+.Ss GNU OPTIONS
+The following options are available in GNU mode, i.e. when the program
+is invoked with a name that ends in
+.Dq sum :
+.Bl -tag -width indent
+.It Fl b , Fl -binary
+Read files in binary mode.
+.It Fl c , Fl -check
+The file passed as arguments must contain digest lines generated by the same
+digest algorithm in either classical BSD format or in GNU coreutils format.
+A line with the file name followed by a colon
+.Dq ":"
+and either OK or FAILED is written for each well-formed line in the digest file.
+If applicable, the number of failed comparisons and the number of lines that were
+skipped since they were not well-formed are printed at the end.
+The
+.Fl -quiet
+option can be used to quiesce the output unless there are mismatched entries in
+the digest.
+.It Fl -help
+Print a usage message and exit.
+.It Fl -ignore-missing
+When verifying checksums, ignore files for which checksums are given
+but which aren't found on disk.
+.It Fl -quiet
+When verifying checksums, do not print anything unless the
+verification fails.
+.It Fl -status
+When verifying checksums, do not print anything at all.
+The exit code will reflect whether verification succeeded.
+.It Fl -strict
+When verifying checksums, fail if the input is malformed.
+.It Fl -tag
+Produce BSD-style output.
+.It Fl t , Fl -text
+Read files in text mode.
+This is the default.
+Note that this implementation does not differentiate between binary
+and text mode.
+.It Fl -version
+Print version information and exit.
+.It Fl w , Fl -warn
+When verifying checksums, warn about malformed input.
+.It Fl z , Fl -zero
+Terminate output lines with NUL rather than with newline.
+.El
+.Ss PERL OPTIONS
+The following options are available in Perl mode, i.e. when the program
+is invoked with the name
+.Dq shasum :
+.Bl -tag -width indent
+.It Fl 0 , Fl -01
+Read files in bits mode: ASCII
+.Sq 0
+and
+.Sq 1
+characters correspond to 0 and 1 bits, respectively, and all other
+characters are ignored.
+See
+.Sx BUGS .
+.It Fl a Ar alg , Fl -algorithm Ar alg
+Use the specified algorithm:
+.Dq 1
+for SHA-1 (default),
+.Dq xxx
+for
+.Va xxx Ns -bit
+SHA-2 (e.g.
+.Dq 256
+for SHA-256)
+or
+.Dq xxxyyy
+for
+.Va xxx Ns -bit
+SHA-2 truncated to
+.Va yyy
+bits (e.g.
+.Dq 512224
+for SHA-512/224).
+.It Fl b , Fl -binary
+Read files in binary mode.
+.It Fl c , Fl -check
+The file passed as arguments must contain digest lines generated by the same
+digest algorithm in either classical BSD format or in GNU coreutils format.
+A line with the file name followed by a colon
+.Dq ":"
+and either OK or FAILED is written for each well-formed line in the digest file.
+If applicable, the number of failed comparisons and the number of lines that were
+skipped since they were not well-formed are printed at the end.
+The
+.Fl -quiet
+option can be used to quiesce the output unless there are mismatched entries in
+the digest.
+.It Fl -help
+Print a usage message and exit.
+.It Fl -ignore-missing
+When verifying checksums, ignore files for which checksums are given
+but which aren't found on disk.
+.It Fl -quiet
+When verifying checksums, do not print anything unless the
+verification fails.
+.It Fl -status
+When verifying checksums, do not print anything at all.
+The exit code will reflect whether verification succeeded.
+.It Fl -strict
+When verifying checksums, fail if the input is malformed.
+.It Fl -tag
+Produce BSD-style output.
+.It Fl t , Fl -text
+Read files in text mode.
+This is the default.
+Note that this implementation does not differentiate between binary
+and text mode.
+.It Fl U , Fl -UNIVERSAL
+Read files in universal mode: any CR-LF pair, as well as any CR not
+followed by LF, is translated to LF before the digest is computed.
+.It Fl -version
+Print version information and exit.
+.It Fl w , Fl -warn
+When verifying checksums, warn about malformed input.
+.El
 .Sh EXIT STATUS
 The
-.Nm md5 , sha1 , sha224 , sha256 , sha512 , sha512t256 , rmd160 ,
-.Nm skein256 , skein512 ,
+.Nm md5 , sha1 , sha224 , sha256 , sha512 , sha512t224 , sha512t256 ,
+.Nm rmd160 , skein256 , skein512 ,
 and
 .Nm skein1024
 utilities exit 0 on success,
@@ -149,6 +282,16 @@ utilities exit 0 on success,
 and 2 if at least one file does not have the same hash as the
 .Fl c
 option.
+.Pp
+The
+.Nm md5sum , sha1sum , sha224sum , sha256sum , sha512sum ,
+.Nm sha512t224sum , sha512t256sum ,
+.Nm rmd160 , skein256 , skein512 , skein1024
+and
+.Nm shasum
+utilities exit 0 on success and 1 if at least one of the input files
+could not be read or, when verifying checksums, does not have the
+expected checksum.
 .Sh EXAMPLES
 Calculate the MD5 checksum of the string
 .Dq Hello .
@@ -169,11 +312,22 @@ Calculate the checksum of multiple files reversing the output:
 $ md5 -r /boot/loader.conf /etc/rc.conf
 ada5f60f23af88ff95b8091d6d67bef6 /boot/loader.conf
 d80bf36c332dc0fdc479366ec3fa44cd /etc/rc.conf
-.Pd
-The
-.Nm -sum
-variants put 2 blank characters between hash and file name for full compatibility
-with the coreutils versions of these commands.
+.Ed
+.Pp
+This is almost but not quite identical to the output from GNU mode:
+.Bd -literal -offset indent
+$ md5sum /boot/loader.conf /etc/rc.conf
+ada5f60f23af88ff95b8091d6d67bef6  /boot/loader.conf
+d80bf36c332dc0fdc479366ec3fa44cd  /etc/rc.conf
+.Ed
+.Pp
+Note the two spaces between hash and file name.
+If binary mode is requested, they are instead separated by a space and
+an asterisk:
+.Bd -literal -offset indent
+$ md5sum -b /boot/loader.conf /etc/rc.conf
+ada5f60f23af88ff95b8091d6d67bef6 */boot/loader.conf
+d80bf36c332dc0fdc479366ec3fa44cd */etc/rc.conf
 .Ed
 .Pp
 Write the digest for
@@ -197,9 +351,7 @@ $ md5 -c randomstring /boot/loader.conf
 MD5 (/boot/loader.conf) = ada5f60f23af88ff95b8091d6d67bef6 [ Failed ]
 .Ed
 .Pp
-If invoked with a name ending in
-.Nm -sum
-the
+In GNU mode, the
 .Fl c
 option does not compare against a hash string passed as parameter.
 Instead, it expects a digest file, as created under the name
@@ -212,11 +364,12 @@ $ md5 -c digest /boot/loader.conf
 /boot/loader.conf: OK
 .Ed
 .Pp
-The digest file may contain any number of lines in the format generated with or without the
-.Fl r
-option
-.Pq i.e., in either classical BSD format or in GNU coreutils format .
-If a hash value does not match the file, FAILED is printed instead of OK.
+The digest file may contain any number of lines in the format
+generated in either BSD or GNU mode.
+If a hash value does not match the file,
+.Dq FAILED
+is printed instead of
+.Dq OK .
 .Sh SEE ALSO
 .Xr cksum 1 ,
 .Xr md5 3 ,
@@ -252,13 +405,29 @@ Secure Hash Standard (SHS):
 The RIPEMD-160 page:
 .Pa https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
 .Sh BUGS
-All of the utilities that end in
-.Sq sum
-are intended to be compatible with the GNU coreutils programs.
-However, the long option functionality is not provided.
+In bits mode, the original
+.Nm shasum
+script is capable of processing inputs of arbitrary length.
+This implementation is not, and will issue an error if the input
+length is not a multiple of eight bits.
 .Sh ACKNOWLEDGMENTS
-This program is placed in the public domain for free general use by
-RSA Data Security.
+.An -nosplit
+This utility was originally derived from a program which was placed in
+the public domain for free general use by RSA Data Security.
 .Pp
-Support for SHA-1 and RIPEMD-160 has been added by
+Support for SHA-1 and RIPEMD-160 was added by
 .An Oliver Eikemeier Aq Mt eik@FreeBSD.org .
+.Pp
+Support for SHA-2 was added by
+.An Colin Percival Aq Mt cperciva@FreeBSD.org
+and
+.An Allan Jude Aq Mt allanjude@FreeBSD.org .
+.Pp
+Support for SKEIN was added by
+.An Allan Jude Aq Mt allanjude@FreeBSD.org .
+.Pp
+Compatibility with GNU coreutils was added by
+.An Warner Losh Aq Mt imp@FreeBSD.org
+and much expanded by
+.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org ,
+who also added Perl compatibility.
diff --git a/sbin/md5/md5.c b/sbin/md5/md5.c
index 6bc1a780df86..98cfb37110d5 100644
--- a/sbin/md5/md5.c
+++ b/sbin/md5/md5.c
@@ -21,11 +21,13 @@
 __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
-#include <sys/time.h>
 #include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
 
 #include <err.h>
 #include <fcntl.h>
+#include <getopt.h>
 #include <md5.h>
 #include <ripemd.h>
 #include <sha.h>
@@ -54,16 +56,20 @@ __FBSDID("$FreeBSD$");
 #define TEST_BLOCK_COUNT 100000
 #define MDTESTCOUNT 8
 
-static int bflag;
-static int cflag;
-static int pflag;
-static int qflag;
-static int rflag;
-static int sflag;
-static int skip;
+static char *progname;
+
+static bool cflag;
+static bool pflag;
+static bool qflag;
+static bool sflag;
+static bool wflag;
+static bool strict;
+static bool skip;
+static bool ignoreMissing;
 static char* checkAgainst;
 static int checksFailed;
-static int failed;
+static bool failed;
+static int endl = '\n';
 
 typedef void (DIGEST_Init)(void *);
 typedef void (DIGEST_Update)(void *, const unsigned char *, size_t);
@@ -84,21 +90,22 @@ extern const char *SKEIN1024_TestOutput[MDTESTCOUNT];
 
 typedef struct Algorithm_t {
 	const char *progname;
+	const char *perlname;
 	const char *name;
 	const char *(*TestOutput)[MDTESTCOUNT];
 	DIGEST_Init *Init;
 	DIGEST_Update *Update;
 	DIGEST_End *End;
 	char *(*Data)(const void *, unsigned int, char *);
-	char *(*Fd)(int, char *);
 } Algorithm_t;
 
 static void MD5_Update(MD5_CTX *, const unsigned char *, size_t);
-static void MDOutput(const Algorithm_t *, char *, char **);
+static char *MDInput(const Algorithm_t *, FILE *, char *, bool);
+static void MDOutput(const Algorithm_t *, char *, const char *);
 static void MDTimeTrial(const Algorithm_t *);
 static void MDTestSuite(const Algorithm_t *);
-static char *MDFilter(const Algorithm_t *, char*, int);
 static void usage(const Algorithm_t *);
+static void version(void);
 
 typedef union {
 	MD5_CTX md5;
@@ -121,47 +128,155 @@ typedef union {
 /* algorithm function table */
 
 static const struct Algorithm_t Algorithm[] = {
-	{ "md5", "MD5", &MD5TestOutput, (DIGEST_Init*)&MD5Init,
+	{ "md5", NULL, "MD5",
+		&MD5TestOutput, (DIGEST_Init*)&MD5Init,
 		(DIGEST_Update*)&MD5_Update, (DIGEST_End*)&MD5End,
-		&MD5Data, &MD5Fd },
-	{ "sha1", "SHA1", &SHA1_TestOutput, (DIGEST_Init*)&SHA1_Init,
+		&MD5Data },
+	{ "sha1", "1", "SHA1",
+		&SHA1_TestOutput, (DIGEST_Init*)&SHA1_Init,
 		(DIGEST_Update*)&SHA1_Update, (DIGEST_End*)&SHA1_End,
-		&SHA1_Data, &SHA1_Fd },
-	{ "sha224", "SHA224", &SHA224_TestOutput, (DIGEST_Init*)&SHA224_Init,
+		&SHA1_Data },
+	{ "sha224", "224", "SHA224",
+		&SHA224_TestOutput, (DIGEST_Init*)&SHA224_Init,
 		(DIGEST_Update*)&SHA224_Update, (DIGEST_End*)&SHA224_End,
-		&SHA224_Data, &SHA224_Fd },
-	{ "sha256", "SHA256", &SHA256_TestOutput, (DIGEST_Init*)&SHA256_Init,
+		&SHA224_Data },
+	{ "sha256", "256", "SHA256",
+		&SHA256_TestOutput, (DIGEST_Init*)&SHA256_Init,
 		(DIGEST_Update*)&SHA256_Update, (DIGEST_End*)&SHA256_End,
-		&SHA256_Data, &SHA256_Fd },
-	{ "sha384", "SHA384", &SHA384_TestOutput, (DIGEST_Init*)&SHA384_Init,
+		&SHA256_Data },
+	{ "sha384", "384", "SHA384",
+		&SHA384_TestOutput, (DIGEST_Init*)&SHA384_Init,
 		(DIGEST_Update*)&SHA384_Update, (DIGEST_End*)&SHA384_End,
-		&SHA384_Data, &SHA384_Fd },
-	{ "sha512", "SHA512", &SHA512_TestOutput, (DIGEST_Init*)&SHA512_Init,
+		&SHA384_Data },
+	{ "sha512", "512", "SHA512",
+		&SHA512_TestOutput, (DIGEST_Init*)&SHA512_Init,
 		(DIGEST_Update*)&SHA512_Update, (DIGEST_End*)&SHA512_End,
-		&SHA512_Data, &SHA512_Fd },
-	{ "sha512t224", "SHA512t224", &SHA512t224_TestOutput, (DIGEST_Init*)&SHA512_224_Init,
+		&SHA512_Data },
+	{ "sha512t224", "512224", "SHA512t224",
+		&SHA512t224_TestOutput, (DIGEST_Init*)&SHA512_224_Init,
 		(DIGEST_Update*)&SHA512_224_Update, (DIGEST_End*)&SHA512_224_End,
-		&SHA512_224_Data, &SHA512_224_Fd },
-	{ "sha512t256", "SHA512t256", &SHA512t256_TestOutput, (DIGEST_Init*)&SHA512_256_Init,
+		&SHA512_224_Data },
+	{ "sha512t256", "512256", "SHA512t256",
+		&SHA512t256_TestOutput, (DIGEST_Init*)&SHA512_256_Init,
 		(DIGEST_Update*)&SHA512_256_Update, (DIGEST_End*)&SHA512_256_End,
-		&SHA512_256_Data, &SHA512_256_Fd },
-	{ "rmd160", "RMD160", &RIPEMD160_TestOutput,
+		&SHA512_256_Data },
+	{ "rmd160", NULL, "RMD160",
+		&RIPEMD160_TestOutput,
 		(DIGEST_Init*)&RIPEMD160_Init, (DIGEST_Update*)&RIPEMD160_Update,
-		(DIGEST_End*)&RIPEMD160_End, &RIPEMD160_Data, &RIPEMD160_Fd },
-	{ "skein256", "Skein256", &SKEIN256_TestOutput,
+		(DIGEST_End*)&RIPEMD160_End, &RIPEMD160_Data },
+	{ "skein256", NULL, "Skein256",
+		&SKEIN256_TestOutput,
 		(DIGEST_Init*)&SKEIN256_Init, (DIGEST_Update*)&SKEIN256_Update,
-		(DIGEST_End*)&SKEIN256_End, &SKEIN256_Data, &SKEIN256_Fd },
-	{ "skein512", "Skein512", &SKEIN512_TestOutput,
+		(DIGEST_End*)&SKEIN256_End, &SKEIN256_Data },
+	{ "skein512", NULL, "Skein512",
+		&SKEIN512_TestOutput,
 		(DIGEST_Init*)&SKEIN512_Init, (DIGEST_Update*)&SKEIN512_Update,
-		(DIGEST_End*)&SKEIN512_End, &SKEIN512_Data, &SKEIN512_Fd },
-	{ "skein1024", "Skein1024", &SKEIN1024_TestOutput,
+		(DIGEST_End*)&SKEIN512_End, &SKEIN512_Data },
+	{ "skein1024", NULL, "Skein1024",
+		&SKEIN1024_TestOutput,
 		(DIGEST_Init*)&SKEIN1024_Init, (DIGEST_Update*)&SKEIN1024_Update,
-		(DIGEST_End*)&SKEIN1024_End, &SKEIN1024_Data, &SKEIN1024_Fd }
+		(DIGEST_End*)&SKEIN1024_End, &SKEIN1024_Data },
+	{ }
 };
 
-static unsigned	digest;
-static unsigned	malformed;
-static bool	gnu_emu = false;
+static int digest = -1;
+static unsigned int malformed;
+
+static enum mode {
+	mode_bsd,
+	mode_gnu,
+	mode_perl,
+} mode = mode_bsd;
+
+static enum input_mode {
+	input_binary	 = '*',
+	input_text	 = ' ',
+	input_universal	 = 'U',
+	input_bits	 = '^',
+} input_mode = input_binary;
+
+static enum output_mode {
+	output_bare,
+	output_tagged,
+	output_reverse,
+	output_gnu,
+} output_mode = output_tagged;
+
+enum optval {
+	opt_end = -1,
+	/* ensure we don't collide with shortopts */
+	opt_dummy = CHAR_MAX,
+	/* BSD options */
+	opt_check,
+	opt_passthrough,
+	opt_quiet,
+	opt_reverse,
+	opt_string,
+	opt_time_trial,
+	opt_self_test,
+	/* GNU options */
+	opt_binary,
+	opt_help,
+	opt_ignore_missing,
+	opt_status,
+	opt_strict,
+	opt_tag,
+	opt_text,
+	opt_warn,
+	opt_version,
+	opt_zero,
+	/* Perl options */
+	opt_algorithm,
+	opt_bits,
+	opt_universal,
+};
+
+static const struct option bsd_longopts[] = {
+	{ "check",		required_argument,	0, opt_check },
+	{ "passthrough",	no_argument,		0, opt_passthrough },
+	{ "quiet",		no_argument,		0, opt_quiet },
+	{ "reverse",		no_argument,		0, opt_reverse },
+	{ "string",		required_argument,	0, opt_string },
+	{ "time-trial",		no_argument,		0, opt_time_trial },
+	{ "self-test",		no_argument,		0, opt_self_test },
+	{ }
+};
+static const char *bsd_shortopts = "bc:pqrs:tx";
+
+static const struct option gnu_longopts[] = {
+	{ "binary",		no_argument,		0, opt_binary },
+	{ "check",		no_argument,		0, opt_check },
+	{ "help",		no_argument,		0, opt_help },
+	{ "ignore-missing",	no_argument,		0, opt_ignore_missing },
+	{ "quiet",		no_argument,		0, opt_quiet },
+	{ "status",		no_argument,		0, opt_status },
+	{ "strict",		no_argument,		0, opt_strict },
+	{ "tag",		no_argument,		0, opt_tag },
+	{ "text",		no_argument,		0, opt_text },
+	{ "version",		no_argument,		0, opt_version },
+	{ "warn",		no_argument,		0, opt_warn },
+	{ "zero",		no_argument,		0, opt_zero },
+	{ }
+};
+static const char *gnu_shortopts = "bctwz";
+
+static const struct option perl_longopts[] = {
+	{ "algorithm",		required_argument,	0, opt_algorithm },
+	{ "check",		required_argument,	0, opt_check },
+	{ "help",		no_argument,		0, opt_help },
+	{ "ignore-missing",	no_argument,		0, opt_ignore_missing },
+	{ "quiet",		no_argument,		0, opt_quiet },
+	{ "status",		no_argument,		0, opt_status },
+	{ "strict",		no_argument,		0, opt_strict },
+	{ "tag",		no_argument,		0, opt_tag },
+	{ "text",		no_argument,		0, opt_text },
+	{ "UNIVERSAL",		no_argument,		0, opt_universal },
+	{ "version",		no_argument,		0, opt_version },
+	{ "warn",		no_argument,		0, opt_warn },
+	{ "01",			no_argument,		0, opt_bits },
+	{ }
+};
+static const char *perl_shortopts = "0a:bchqstUvw";
 
 static void
 MD5_Update(MD5_CTX *c, const unsigned char *data, size_t len)
@@ -177,60 +292,70 @@ struct chksumrec {
 
 static struct chksumrec *head = NULL;
 static struct chksumrec **next = &head;
+static unsigned int numrecs;
 
 #define PADDING	7	/* extra padding for "SHA512t256 (...) = ...\n" style */
 #define CHKFILELINELEN	(HEX_DIGEST_LENGTH + MAXPATHLEN + PADDING)
 
-static int gnu_check(const char *checksumsfile)
+static void
+gnu_check(const char *checksumsfile)
 {
 	FILE	*inp;
-	char	linebuf[CHKFILELINELEN];
-	int	linelen;
+	char	*linebuf = NULL;
+	size_t	linecap;
+	ssize_t	linelen;
 	int	lineno;
 	char	*filename;
 	char	*hashstr;
 	struct chksumrec	*rec;
 	const char	*digestname;
-	int	digestnamelen;
-	int	hashstrlen;
+	size_t	digestnamelen;
+	size_t	hashstrlen;
 
-	if ((inp = fopen(checksumsfile, "r")) == NULL)
+	if (strcmp(checksumsfile, "-") == 0)
+		inp = stdin;
+	else if ((inp = fopen(checksumsfile, "r")) == NULL)
 		err(1, "%s", checksumsfile);
 	digestname = Algorithm[digest].name;
 	digestnamelen = strlen(digestname);
 	hashstrlen = strlen(*(Algorithm[digest].TestOutput[0]));
-	lineno = 1;
-	while (fgets(linebuf, sizeof(linebuf), inp) != NULL) {
-		linelen = strlen(linebuf) - 1;
-		if (linelen <= 0)
-			break;
-		if (linebuf[linelen] != '\n')
-			errx(1, "malformed input line %d (len=%d)", lineno, linelen);
+	lineno = 0;
+	linecap = CHKFILELINELEN;
+	while ((linelen = getline(&linebuf, &linecap, inp)) > 0) {
+		lineno++;
+		while (linelen > 0 && linebuf[linelen - 1] == '\n')
+			linelen--;
 		linebuf[linelen] = '\0';
 		filename = linebuf + digestnamelen + 2;
 		hashstr = linebuf + linelen - hashstrlen;
 		/*
 		 * supported formats:
 		 * BSD: <DigestName> (<Filename>): <Digest>
-		 * GNU: <Digest> [ *]<Filename>
+		 * GNU: <Digest> [ *U^]<Filename>
 		 */
-		if (linelen >= digestnamelen + hashstrlen + 6 &&
+		if ((size_t)linelen >= digestnamelen + hashstrlen + 6 &&
 		    strncmp(linebuf, digestname, digestnamelen) == 0 &&
 		    strncmp(filename - 2, " (", 2) == 0 &&
-		    strncmp(hashstr - 4, ") = ", 4) == 0) {
+		    strncmp(hashstr - 4, ") = ", 4) == 0 &&
+		    strspn(hashstr, "0123456789ABCDEFabcdef") == hashstrlen) {
 			*(hashstr - 4) = '\0';
-		} else if (linelen >= hashstrlen + 3 &&
+		} else if ((size_t)linelen >= hashstrlen + 3 &&
+		    strspn(linebuf, "0123456789ABCDEFabcdef") == hashstrlen &&
 		    linebuf[hashstrlen] == ' ') {
 			linebuf[hashstrlen] = '\0';
 			hashstr = linebuf;
 			filename = linebuf + hashstrlen + 1;
-			if (*filename == ' ' || *filename == '*')
-				filename++;
 		} else {
+			if (wflag) {
+				warnx("%s: %d: improperly formatted "
+				    "%s checksum line",
+				    checksumsfile, lineno,
+				    mode == mode_perl ? "SHA" : digestname);
+			}
 			malformed++;
 			continue;
 		}
-		rec = malloc(sizeof (*rec));
+		rec = malloc(sizeof(*rec));
 		if (rec == NULL)
 			errx(1, "malloc failed");
 		rec->chksum = strdup(hashstr);
@@ -240,10 +365,10 @@ static int gnu_check(const char *checksumsfile)
 		rec->next = NULL;
 		*next = rec;
 		next = &rec->next;
-		lineno++;
+		numrecs++;
 	}
-	fclose(inp);
-	return (lineno - 1);
+	if (inp != stdin)
+		fclose(inp);
 }
 
 /* Main driver.
@@ -261,18 +386,19 @@ main(int argc, char *argv[])
 #ifdef HAVE_CAPSICUM
 	cap_rights_t	rights;
 #endif
-	int	ch, fd;
-	char   *p, *string;
+	const struct option *longopts;
*** 1830 LINES SKIPPED ***