src/md5sum.c - coreutils-8.23
Global variables defined
Functions defined
Macros defined
Source code
#include <config.h>
#include <getopt.h>
#include <sys/types.h>
#include "system.h"
#if HASH_ALGO_MD5
# include "md5.h"
#endif
#if HASH_ALGO_SHA1
# include "sha1.h"
#endif
#if HASH_ALGO_SHA256 || HASH_ALGO_SHA224
# include "sha256.h"
#endif
#if HASH_ALGO_SHA512 || HASH_ALGO_SHA384
# include "sha512.h"
#endif
#include "error.h"
#include "fadvise.h"
#include "stdio--.h"
#include "xfreopen.h"
#if HASH_ALGO_MD5
# define PROGRAM_NAME "md5sum"
# define DIGEST_TYPE_STRING "MD5"
# define DIGEST_STREAM md5_stream
# define DIGEST_BITS 128
# define DIGEST_REFERENCE "RFC 1321"
# define DIGEST_ALIGN 4
#elif HASH_ALGO_SHA1
# define PROGRAM_NAME "sha1sum"
# define DIGEST_TYPE_STRING "SHA1"
# define DIGEST_STREAM sha1_stream
# define DIGEST_BITS 160
# define DIGEST_REFERENCE "FIPS-180-1"
# define DIGEST_ALIGN 4
#elif HASH_ALGO_SHA256
# define PROGRAM_NAME "sha256sum"
# define DIGEST_TYPE_STRING "SHA256"
# define DIGEST_STREAM sha256_stream
# define DIGEST_BITS 256
# define DIGEST_REFERENCE "FIPS-180-2"
# define DIGEST_ALIGN 4
#elif HASH_ALGO_SHA224
# define PROGRAM_NAME "sha224sum"
# define DIGEST_TYPE_STRING "SHA224"
# define DIGEST_STREAM sha224_stream
# define DIGEST_BITS 224
# define DIGEST_REFERENCE "RFC 3874"
# define DIGEST_ALIGN 4
#elif HASH_ALGO_SHA512
# define PROGRAM_NAME "sha512sum"
# define DIGEST_TYPE_STRING "SHA512"
# define DIGEST_STREAM sha512_stream
# define DIGEST_BITS 512
# define DIGEST_REFERENCE "FIPS-180-2"
# define DIGEST_ALIGN 8
#elif HASH_ALGO_SHA384
# define PROGRAM_NAME "sha384sum"
# define DIGEST_TYPE_STRING "SHA384"
# define DIGEST_STREAM sha384_stream
# define DIGEST_BITS 384
# define DIGEST_REFERENCE "FIPS-180-2"
# define DIGEST_ALIGN 8
#else
# error "Can't decide which hash algorithm to compile."
#endif
#define DIGEST_HEX_BYTES (DIGEST_BITS / 4)
#define DIGEST_BIN_BYTES (DIGEST_BITS / 8)
#define AUTHORS \
proper_name ("Ulrich Drepper"), \
proper_name ("Scott Miller"), \
proper_name ("David Madore")
#define MIN_DIGEST_LINE_LENGTH \
(DIGEST_HEX_BYTES \
+ 1 \
+ 1 )
static bool have_read_stdin;
static size_t min_digest_line_length;
static size_t digest_hex_bytes;
static bool status_only = false;
static bool warn = false;
static bool quiet = false;
static bool strict = false;
static int bsd_reversed = -1;
enum
{
STATUS_OPTION = CHAR_MAX + 1,
QUIET_OPTION,
STRICT_OPTION,
TAG_OPTION
};
static struct option const long_options[] =
{
{ "binary", no_argument, NULL, 'b' },
{ "check", no_argument, NULL, 'c' },
{ "quiet", no_argument, NULL, QUIET_OPTION },
{ "status", no_argument, NULL, STATUS_OPTION },
{ "text", no_argument, NULL, 't' },
{ "warn", no_argument, NULL, 'w' },
{ "strict", no_argument, NULL, STRICT_OPTION },
{ "tag", no_argument, NULL, TAG_OPTION },
{ GETOPT_HELP_OPTION_DECL },
{ GETOPT_VERSION_OPTION_DECL },
{ NULL, 0, NULL, 0 }
};
void
usage (int status)
{
if (status != EXIT_SUCCESS)
emit_try_help ();
else
{
printf (_("\
Usage: %s [OPTION]... [FILE]...\n\
Print or check %s (%d-bit) checksums.\n\
With no FILE, or when FILE is -, read standard input.\n\
\n\
"),
program_name,
DIGEST_TYPE_STRING,
DIGEST_BITS);
if (O_BINARY)
fputs (_("\
-b, --binary read in binary mode (default unless reading tty stdin)\n\
"), stdout);
else
fputs (_("\
-b, --binary read in binary mode\n\
"), stdout);
printf (_("\
-c, --check read %s sums from the FILEs and check them\n"),
DIGEST_TYPE_STRING);
fputs (_("\
--tag create a BSD-style checksum\n\
"), stdout);
if (O_BINARY)
fputs (_("\
-t, --text read in text mode (default if reading tty stdin)\n\
"), stdout);
else
fputs (_("\
-t, --text read in text mode (default)\n\
"), stdout);
fputs (_("\
\n\
The following four options are useful only when verifying checksums:\n\
--quiet don't print OK for each successfully verified file\n\
--status don't output anything, status code shows success\n\
--strict exit non-zero for improperly formatted checksum lines\n\
-w, --warn warn about improperly formatted checksum lines\n\
\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
printf (_("\
\n\
The sums are computed as described in %s. When checking, the input\n\
should be a former output of this program. The default mode is to print\n\
a line with checksum, a character indicating input mode ('*' for binary,\n\
space for text), and name for each FILE.\n"),
DIGEST_REFERENCE);
emit_ancillary_info ();
}
exit (status);
}
#define ISWHITE(c) ((c) == ' ' || (c) == '\t')
static char *
filename_unescape (char *s, size_t s_len)
{
char *dst = s;
for (size_t i = 0; i < s_len; i++)
{
switch (s[i])
{
case '\\':
if (i == s_len - 1)
{
return NULL;
}
++i;
switch (s[i])
{
case 'n':
*dst++ = '\n';
break;
case '\\':
*dst++ = '\\';
break;
default:
return NULL;
}
break;
case '\0':
return NULL;
default:
*dst++ = s[i];
break;
}
}
if (dst < s + s_len)
*dst = '\0';
return s;
}
static bool
bsd_split_3 (char *s, size_t s_len, unsigned char **hex_digest,
char **file_name, bool escaped_filename)
{
size_t i;
if (s_len == 0)
return false;
i = s_len - 1;
while (i && s[i] != ')')
i--;
if (s[i] != ')')
return false;
*file_name = s;
if (escaped_filename && filename_unescape (s, i) == NULL)
return false;
s[i++] = '\0';
while (ISWHITE (s[i]))
i++;
if (s[i] != '=')
return false;
i++;
while (ISWHITE (s[i]))
i++;
*hex_digest = (unsigned char *) &s[i];
return true;
}
static bool
split_3 (char *s, size_t s_len,
unsigned char **hex_digest, int *binary, char **file_name)
{
bool escaped_filename = false;
size_t algo_name_len;
size_t i = 0;
while (ISWHITE (s[i]))
++i;
if (s[i] == '\\')
{
++i;
escaped_filename = true;
}
algo_name_len = strlen (DIGEST_TYPE_STRING);
if (STREQ_LEN (s + i, DIGEST_TYPE_STRING, algo_name_len))
{
if (s[i + algo_name_len] == ' ')
++i;
if (s[i + algo_name_len] == '(')
{
*binary = 0;
return bsd_split_3 (s + i + algo_name_len + 1,
s_len - (i + algo_name_len + 1),
hex_digest, file_name, escaped_filename);
}
}
if (s_len - i < min_digest_line_length + (s[i] == '\\'))
return false;
*hex_digest = (unsigned char *) &s[i];
i += digest_hex_bytes;
if (!ISWHITE (s[i]))
return false;
s[i++] = '\0';
if ((s_len - i == 1) || (s[i] != ' ' && s[i] != '*'))
{
if (bsd_reversed == 0)
return false;
bsd_reversed = 1;
}
else if (bsd_reversed != 1)
{
bsd_reversed = 0;
*binary = (s[i++] == '*');
}
*file_name = &s[i];
if (escaped_filename)
return filename_unescape (&s[i], s_len - i) != NULL;
return true;
}
static bool _GL_ATTRIBUTE_PURE
hex_digits (unsigned char const *s)
{
unsigned int i;
for (i = 0; i < digest_hex_bytes; i++)
{
if (!isxdigit (*s))
return false;
++s;
}
return *s == '\0';
}
static bool
digest_file (const char *filename, int *binary, unsigned char *bin_result)
{
FILE *fp;
int err;
bool is_stdin = STREQ (filename, "-");
if (is_stdin)
{
have_read_stdin = true;
fp = stdin;
if (O_BINARY && *binary)
{
if (*binary < 0)
*binary = ! isatty (STDIN_FILENO);
if (*binary)
xfreopen (NULL, "rb", stdin);
}
}
else
{
fp = fopen (filename, (O_BINARY && *binary ? "rb" : "r"));
if (fp == NULL)
{
error (0, errno, "%s", filename);
return false;
}
}
fadvise (fp, FADVISE_SEQUENTIAL);
err = DIGEST_STREAM (fp, bin_result);
if (err)
{
error (0, errno, "%s", filename);
if (fp != stdin)
fclose (fp);
return false;
}
if (!is_stdin && fclose (fp) != 0)
{
error (0, errno, "%s", filename);
return false;
}
return true;
}
static bool
digest_check (const char *checkfile_name)
{
FILE *checkfile_stream;
uintmax_t n_misformatted_lines = 0;
uintmax_t n_properly_formatted_lines = 0;
uintmax_t n_improperly_formatted_lines = 0;
uintmax_t n_mismatched_checksums = 0;
uintmax_t n_open_or_read_failures = 0;
unsigned char bin_buffer_unaligned[DIGEST_BIN_BYTES + DIGEST_ALIGN];
unsigned char *bin_buffer = ptr_align (bin_buffer_unaligned, DIGEST_ALIGN);
uintmax_t line_number;
char *line;
size_t line_chars_allocated;
bool is_stdin = STREQ (checkfile_name, "-");
if (is_stdin)
{
have_read_stdin = true;
checkfile_name = _("standard input");
checkfile_stream = stdin;
}
else
{
checkfile_stream = fopen (checkfile_name, "r");
if (checkfile_stream == NULL)
{
error (0, errno, "%s", checkfile_name);
return false;
}
}
line_number = 0;
line = NULL;
line_chars_allocated = 0;
do
{
char *filename IF_LINT ( = NULL);
int binary;
unsigned char *hex_digest IF_LINT ( = NULL);
ssize_t line_length;
++line_number;
if (line_number == 0)
error (EXIT_FAILURE, 0, _("%s: too many checksum lines"),
checkfile_name);
line_length = getline (&line, &line_chars_allocated, checkfile_stream);
if (line_length <= 0)
break;
if (line[0] == '#')
continue;
if (line[line_length - 1] == '\n')
line[--line_length] = '\0';
if (! (split_3 (line, line_length, &hex_digest, &binary, &filename)
&& ! (is_stdin && STREQ (filename, "-"))
&& hex_digits (hex_digest)))
{
++n_misformatted_lines;
if (warn)
{
error (0, 0,
_("%s: %" PRIuMAX
": improperly formatted %s checksum line"),
checkfile_name, line_number,
DIGEST_TYPE_STRING);
}
++n_improperly_formatted_lines;
}
else
{
static const char bin2hex[] = { '0', '1', '2', '3',
'4', '5', '6', '7',
'8', '9', 'a', 'b',
'c', 'd', 'e', 'f' };
bool ok;
++n_properly_formatted_lines;
ok = digest_file (filename, &binary, bin_buffer);
if (!ok)
{
++n_open_or_read_failures;
if (!status_only)
{
printf (_("%s: FAILED open or read\n"), filename);
}
}
else
{
size_t digest_bin_bytes = digest_hex_bytes / 2;
size_t cnt;
for (cnt = 0; cnt < digest_bin_bytes; ++cnt)
{
if (tolower (hex_digest[2 * cnt])
!= bin2hex[bin_buffer[cnt] >> 4]
|| (tolower (hex_digest[2 * cnt + 1])
!= (bin2hex[bin_buffer[cnt] & 0xf])))
break;
}
if (cnt != digest_bin_bytes)
++n_mismatched_checksums;
if (!status_only)
{
if (cnt != digest_bin_bytes)
printf ("%s: %s\n", filename, _("FAILED"));
else if (!quiet)
printf ("%s: %s\n", filename, _("OK"));
}
}
}
}
while (!feof (checkfile_stream) && !ferror (checkfile_stream));
free (line);
if (ferror (checkfile_stream))
{
error (0, 0, _("%s: read error"), checkfile_name);
return false;
}
if (!is_stdin && fclose (checkfile_stream) != 0)
{
error (0, errno, "%s", checkfile_name);
return false;
}
if (n_properly_formatted_lines == 0)
{
error (0, 0, _("%s: no properly formatted %s checksum lines found"),
checkfile_name, DIGEST_TYPE_STRING);
}
else
{
if (!status_only)
{
if (n_misformatted_lines != 0)
error (0, 0,
(ngettext
("WARNING: %" PRIuMAX " line is improperly formatted",
"WARNING: %" PRIuMAX " lines are improperly formatted",
select_plural (n_misformatted_lines))),
n_misformatted_lines);
if (n_open_or_read_failures != 0)
error (0, 0,
(ngettext
("WARNING: %" PRIuMAX " listed file could not be read",
"WARNING: %" PRIuMAX " listed files could not be read",
select_plural (n_open_or_read_failures))),
n_open_or_read_failures);
if (n_mismatched_checksums != 0)
error (0, 0,
(ngettext
("WARNING: %" PRIuMAX " computed checksum did NOT match",
"WARNING: %" PRIuMAX " computed checksums did NOT match",
select_plural (n_mismatched_checksums))),
n_mismatched_checksums);
}
}
return (n_properly_formatted_lines != 0
&& n_mismatched_checksums == 0
&& n_open_or_read_failures == 0
&& (!strict || n_improperly_formatted_lines == 0));
}
static void
print_filename (char const *file, bool escape)
{
if (! escape)
{
fputs (file, stdout);
return;
}
while (*file)
{
switch (*file)
{
case '\n':
fputs ("\\n", stdout);
break;
case '\\':
fputs ("\\\\", stdout);
break;
default:
putchar (*file);
break;
}
file++;
}
}
int
main (int argc, char **argv)
{
unsigned char bin_buffer_unaligned[DIGEST_BIN_BYTES + DIGEST_ALIGN];
unsigned char *bin_buffer = ptr_align (bin_buffer_unaligned, DIGEST_ALIGN);
bool do_check = false;
int opt;
bool ok = true;
int binary = -1;
bool prefix_tag = false;
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdout);
setvbuf (stdout, NULL, _IOLBF, 0);
while ((opt = getopt_long (argc, argv, "bctw", long_options, NULL)) != -1)
switch (opt)
{
case 'b':
binary = 1;
break;
case 'c':
do_check = true;
break;
case STATUS_OPTION:
status_only = true;
warn = false;
quiet = false;
break;
case 't':
binary = 0;
break;
case 'w':
status_only = false;
warn = true;
quiet = false;
break;
case QUIET_OPTION:
status_only = false;
warn = false;
quiet = true;
break;
case STRICT_OPTION:
strict = true;
break;
case TAG_OPTION:
prefix_tag = true;
binary = 1;
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
default:
usage (EXIT_FAILURE);
}
min_digest_line_length = MIN_DIGEST_LINE_LENGTH;
digest_hex_bytes = DIGEST_HEX_BYTES;
if (prefix_tag && !binary)
{
error (0, 0, _("--tag does not support --text mode"));
usage (EXIT_FAILURE);
}
if (prefix_tag && do_check)
{
error (0, 0, _("the --tag option is meaningless when "
"verifying checksums"));
usage (EXIT_FAILURE);
}
if (0 <= binary && do_check)
{
error (0, 0, _("the --binary and --text options are meaningless when "
"verifying checksums"));
usage (EXIT_FAILURE);
}
if (status_only && !do_check)
{
error (0, 0,
_("the --status option is meaningful only when verifying checksums"));
usage (EXIT_FAILURE);
}
if (warn && !do_check)
{
error (0, 0,
_("the --warn option is meaningful only when verifying checksums"));
usage (EXIT_FAILURE);
}
if (quiet && !do_check)
{
error (0, 0,
_("the --quiet option is meaningful only when verifying checksums"));
usage (EXIT_FAILURE);
}
if (strict & !do_check)
{
error (0, 0,
_("the --strict option is meaningful only when verifying checksums"));
usage (EXIT_FAILURE);
}
if (!O_BINARY && binary < 0)
binary = 0;
if (optind == argc)
argv[argc++] = bad_cast ("-");
for (; optind < argc; ++optind)
{
char *file = argv[optind];
if (do_check)
ok &= digest_check (file);
else
{
int file_is_binary = binary;
if (! digest_file (file, &file_is_binary, bin_buffer))
ok = false;
else
{
bool needs_escape = strchr (file, '\\') || strchr (file, '\n');
if (prefix_tag)
{
if (needs_escape)
putchar ('\\');
fputs (DIGEST_TYPE_STRING, stdout);
fputs (" (", stdout);
print_filename (file, needs_escape);
fputs (") = ", stdout);
}
size_t i;
if (!prefix_tag && needs_escape)
putchar ('\\');
for (i = 0; i < (digest_hex_bytes / 2); ++i)
printf ("%02x", bin_buffer[i]);
if (!prefix_tag)
{
putchar (' ');
putchar (file_is_binary ? '*' : ' ');
print_filename (file, needs_escape);
}
putchar ('\n');
}
}
}
if (have_read_stdin && fclose (stdin) == EOF)
error (EXIT_FAILURE, errno, _("standard input"));
exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
}