source: trunk/src/kmk/kmkbuiltin/md5sum.c@ 1102

Last change on this file since 1102 was 1102, checked in by bird, 18 years ago

reorged the code. progress option.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 20.9 KB
Line 
1/* $Id: md5sum.c 1102 2007-09-23 03:30:49Z bird $ */
2/** @file
3 * md5sum.
4 */
5
6/*
7 * Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 *
25 */
26
27#include <string.h>
28#include <stdio.h>
29#include <errno.h>
30#include <fcntl.h>
31#ifdef _MSC_VER
32# include <io.h>
33#else
34# include <unistd.h>
35#endif
36#include <sys/stat.h>
37#include "err.h"
38#include "kmkbuiltin.h"
39#include "../../lib/md5.h"
40
41//#define MD5SUM_USE_STDIO
42
43
44/**
45 * Prints the usage and return 1.
46 */
47static int usage(FILE *pOut)
48{
49 fprintf(pOut,
50 "usage: md5sum [-bt] file(s)\n"
51 " or: md5sum [-btwq] -c list-file(s)\n"
52 " or: md5sum [-btq] -C MD5 file\n"
53 "\n"
54 " -c, --check Check MD5 and files found in the specified list file(s).\n"
55 " The default is to compute MD5 sums of the specified files\n"
56 " and print them to stdout in list form.\n"
57 " -C, --check-file This is followed by an MD5 sum and the file to check.\n"
58 " -b, --binary Read files in binary mode. (default)\n"
59 " -t, --text Read files in text mode.\n"
60 " -q, --status Be quiet.\n"
61 " -w, --warn Ignored. Always warn, unless quiet.\n"
62 " -h, --help This usage info.\n"
63 " -v, --version Show version information and exit.\n"
64 );
65 return 1;
66}
67
68
69/**
70 * Prints the version string.
71 * @returns 0
72 */
73static int version(void)
74{
75#ifdef kmk_builtin_md5sum
76 fprintf(stdout, "kmk_md5sum (kBuild) %d.%d.%d\n"
77 "Copyright (c) 2007 knut st. osmundsen\n",
78 KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH);
79#else
80 fprintf(stdout, "kmk_builtin_md5sum (kBuild) %d.%d.%d\n"
81 "Copyright (c) 2007 knut st. osmundsen\n",
82 KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH);
83#endif
84 return 0;
85}
86
87
88/**
89 * Makes a string out of the given digest.
90 *
91 * @param pDigest The MD5 digest.
92 * @param pszDigest Where to put the digest string. Must be able to
93 * hold at least 33 bytes.
94 */
95static void digest_to_string(unsigned char pDigest[16], char *pszDigest)
96{
97 unsigned i;
98 for (i = 0; i < 16; i++)
99 {
100 static char s_achDigits[17] = "0123456789abcdef";
101 pszDigest[i*2] = s_achDigits[(pDigest[i] >> 4)];
102 pszDigest[i*2 + 1] = s_achDigits[(pDigest[i] & 0xf)];
103 }
104 pszDigest[i*2] = '\0';
105}
106
107
108/**
109 * Attempts to convert a string to a MD5 digest.
110 *
111 * @returns 0 on success, 1-based position of the failure first error.
112 * @param pszDigest The string to interpret.
113 * @param pDigest Where to put the MD5 digest.
114 */
115static int string_to_digest(const char *pszDigest, unsigned char pDigest[16])
116{
117 unsigned i;
118 unsigned iBase = 1;
119
120 /* skip blanks */
121 while ( *pszDigest == ' '
122 || *pszDigest == '\t'
123 || *pszDigest == '\n'
124 || *pszDigest == '\r')
125 pszDigest++, iBase++;
126
127 /* convert the digits. */
128 memset(pDigest, 0, 16);
129 for (i = 0; i < 32; i++, pszDigest++)
130 {
131 int iDigit;
132 if (*pszDigest >= '0' && *pszDigest <= '9')
133 iDigit = *pszDigest - '0';
134 else if (*pszDigest >= 'a' && *pszDigest <= 'f')
135 iDigit = *pszDigest - 'a' + 10;
136 else if (*pszDigest >= 'A' && *pszDigest <= 'F')
137 iDigit = *pszDigest - 'A' + 10;
138 else
139 return i + iBase;
140 if (i & 1)
141 pDigest[i >> 1] |= iDigit;
142 else
143 pDigest[i >> 1] |= iDigit << 4;
144 }
145
146 /* the rest of the string must now be blanks. */
147 while ( *pszDigest == ' '
148 || *pszDigest == '\t'
149 || *pszDigest == '\n'
150 || *pszDigest == '\r')
151 pszDigest++, i++;
152
153 return *pszDigest ? i + iBase : 0;
154}
155
156
157/**
158 * Opens the specified file for md5 sum calculation.
159 *
160 * @returns Opaque pointer on success, NULL and errno on failure.
161 * @param pszFilename The filename.
162 * @param fText Whether text or binary mode should be used.
163 */
164static void *open_file(const char *pszFilename, unsigned fText)
165{
166#if defined(MD5SUM_USE_STDIO)
167 FILE *pFile;
168
169 errno = 0;
170 pFile = fopen(pszFilename, fText ? "r" : "rb");
171 if (!pFile && errno == EINVAL && !fText)
172 pFile = fopen(pszFilename, "r");
173 return pFile;
174
175#else
176 int fd;
177 int fFlags;
178
179 /* figure out the appropriate flags. */
180 fFlags = O_RDONLY;
181#ifdef O_SEQUENTIAL
182 fFlags |= _O_SEQUENTIAL;
183#elif defined(_O_SEQUENTIAL)
184 fFlags |= _O_SEQUENTIAL;
185#endif
186#ifdef _O_BINARY
187 if (!fText) fFlags |= _O_BINARY;
188#elif defined(_O_BINARY
189 if (!fText) fFlags |= _O_BINARY;
190#endif
191#ifdef O_TEXT
192 if (fText) fFlags |= O_TEXT;
193#elif defined(O_TEXT)
194 if (fText) fFlags |= _O_TEXT;
195#endif
196
197 errno = 0;
198 fd = open(pszFilename, fFlags, 0755);
199 if (fd >= 0)
200 {
201 int *pFd = malloc(sizeof(*pFd));
202 if (pFd)
203 {
204 *pFd = fd;
205 return pFd;
206 }
207 close(fd);
208 errno = ENOMEM;
209 }
210
211 return NULL;
212#endif
213}
214
215
216/**
217 * Closes a file opened by open_file.
218 *
219 * @param pvFile The opaque pointer returned by open_file.
220 */
221static void close_file(void *pvFile)
222{
223#if defined(MD5SUM_USE_STDIO)
224 fclose((FILE *)pvFile);
225#else
226 close(*(int *)pvFile);
227 free(pvFile);
228#endif
229}
230
231
232/**
233 * Reads from a file opened by open_file.
234 *
235 * @returns Number of bytes read on success.
236 * 0 on EOF.
237 * Negated errno on read error.
238 * @param pvFile The opaque pointer returned by open_file.
239 * @param pvBuf Where to put the number of read bytes.
240 * @param cbBuf The max number of bytes to read.
241 * Must be less than a INT_MAX.
242 */
243static int read_file(void *pvFile, void *pvBuf, size_t cbBuf)
244{
245#if defined(MD5SUM_USE_STDIO)
246 int cb;
247
248 errno = 0;
249 cb = (int)fread(pvBuf, 1, cbBuf, (FILE *)pvFile);
250 if (cb >= 0)
251 return (int)cb;
252 if (!errno)
253 return -EINVAL;
254 return -errno;
255#else
256 int cb;
257
258 errno = 0;
259 cb = (int)read(*(int *)pvFile, pvBuf, (int)cbBuf);
260 if (cb >= 0)
261 return (int)cb;
262 if (!errno)
263 return -EINVAL;
264 return -errno;
265#endif
266}
267
268
269/**
270 * Gets the size of the file.
271 * This is informational and not necessarily 100% accurate.
272 *
273 * @returns File size.
274 * @param pvFile The opaque pointer returned by open_file
275 */
276static double size_file(void *pvFile)
277{
278#if defined(_MSC_VER)
279 __int64 cb;
280# if defined(MD5SUM_USE_STDIO)
281 cb = _filelengthi64(fileno((FILE *)pvFile));
282# else
283 cb = _filelengthi64(*(int *)pvFile);
284# endif
285 if (cb >= 0)
286 return (double)cb;
287
288#elif defined(MD5SUM_USE_STDIO)
289 struct stat st;
290 if (!fstat(fileno((FILE *)pvFile), &st))
291 return st.st_size;
292
293#else
294 struct stat st;
295 if (!fstat(*(int *)pvFile, &st))
296 return st.st_size;
297#endif
298 return 1024;
299}
300
301
302/**
303 * Calculates the md5sum of the sepecified file stream.
304 *
305 * @returns errno on failure, 0 on success.
306 * @param pvFile The file stream.
307 * @param pDigest Where to store the MD5 digest.
308 * @param fProgress Whether to show a progress bar.
309 */
310static int calc_md5sum(void *pvFile, unsigned char pDigest[16], unsigned fProgress)
311{
312 int cb;
313 int rc = 0;
314 char abBuf[32*1024];
315 struct MD5Context Ctx;
316 unsigned uPercent = 0;
317 double cbFile = 0.0;
318 double off = 0.0;
319
320 if (fProgress)
321 {
322 cbFile = size_file(pvFile);
323 if (cbFile < 1024*1024)
324 fProgress = 0;
325 }
326
327 MD5Init(&Ctx);
328 for (;;)
329 {
330 /* process a chunk. */
331 cb = read_file(pvFile, abBuf, sizeof(abBuf));
332 if (cb > 0)
333 MD5Update(&Ctx, abBuf, cb);
334 else if (!cb)
335 break;
336 else
337 {
338 rc = -cb;
339 break;
340 }
341
342 /* update the progress indicator. */
343 if (fProgress)
344 {
345 unsigned uNewPercent;
346 off += cb;
347 uNewPercent = (unsigned)((off / cbFile) * 100);
348 if (uNewPercent != uPercent)
349 {
350 if (uPercent)
351 printf("\b\b\b\b");
352 printf("%3d%%", uNewPercent);
353 fflush(stdout);
354 uPercent = uNewPercent;
355 }
356 }
357 }
358 MD5Final(pDigest, &Ctx);
359
360 if (fProgress)
361 printf("\b\b\b\b \b\b\b\b");
362
363 return rc;
364}
365
366
367/**
368 * Checks the if the specified digest matches the digest of the file stream.
369 *
370 * @returns 0 on match, -1 on mismatch, errno value (positive) on failure.
371 * @param pvFile The file stream.
372 * @param Digest The MD5 digest.
373 * @param fProgress Whether to show an progress indicator on large files.
374 */
375static int check_md5sum(void *pvFile, unsigned char Digest[16], unsigned fProgress)
376{
377 unsigned char DigestFile[16];
378 int rc;
379
380 rc = calc_md5sum(pvFile, DigestFile, fProgress);
381 if (!rc)
382 rc = memcmp(Digest, DigestFile, 16) ? -1 : 0;
383 return rc;
384}
385
386
387/**
388 * Checks if the specified file matches the given MD5 digest.
389 *
390 * @returns 0 if it matches, 1 if it doesn't or an error occurs.
391 * @param pszFilename The name of the file to check.
392 * @param pszDigest The MD5 digest string.
393 * @param fText Whether to open the file in text or binary mode.
394 * @param fQuiet Whether to go about this in a quiet fashion or not.
395 * @param fProgress Whether to show an progress indicator on large files.
396 */
397static int check_one_file(const char *pszFilename, const char *pszDigest, unsigned fText, unsigned fQuiet, unsigned fProgress)
398{
399 unsigned char Digest[16];
400 int rc;
401
402 rc = string_to_digest(pszDigest, Digest);
403 if (!rc)
404 {
405 void *pvFile;
406
407 pvFile = open_file(pszFilename, fText);
408 if (pvFile)
409 {
410 if (!fQuiet)
411 fprintf(stdout, "%s: ", pszFilename);
412 rc = check_md5sum(pvFile, Digest, fProgress);
413 close_file(pvFile);
414 if (!fQuiet)
415 {
416 fprintf(stdout, "%s\n", !rc ? "OK" : rc < 0 ? "FAILURE" : "ERROR");
417 fflush(stdout);
418 if (rc > 0)
419 errx(1, "Error reading '%s': %s", pszFilename, strerror(rc));
420 }
421 if (rc)
422 rc = 1;
423 }
424 else
425 {
426 if (!fQuiet)
427 errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
428 rc = 1;
429 }
430 }
431 else
432 {
433 errx(1, "Malformed MD5 digest '%s'!", pszDigest);
434 errx(1, " %*s^", rc - 1, "");
435 rc = 1;
436 }
437
438 return rc;
439}
440
441
442/**
443 * Checks the specified md5.lst file.
444 *
445 * @returns 0 if all checks out file, 1 if one or more fails or there are read errors.
446 * @param pszFilename The name of the file.
447 * @param fText The default mode, text or binary. Only used when fBinaryTextOpt is true.
448 * @param fBinaryTextOpt Whether a -b or -t option was specified and should be used.
449 * @param fQuiet Whether to be quiet.
450 * @param fProgress Whether to show an progress indicator on large files.
451 */
452static int check_files(const char *pszFilename, int fText, int fBinaryTextOpt, int fQuiet, unsigned fProgress)
453{
454 int rc = 0;
455 FILE *pFile;
456
457 /*
458 * Try open the md5.lst file and process it line by line.
459 */
460 pFile = fopen(pszFilename, "r");
461 if (pFile)
462 {
463 int iLine = 0;
464 char szLine[8192];
465 while (fgets(szLine, sizeof(szLine), pFile))
466 {
467 const char *pszDigest;
468 int fLineText;
469 char *psz;
470 int rc2;
471
472 iLine++;
473 psz = szLine;
474
475 /* leading blanks */
476 while (*psz == ' ' || *psz == '\t' || *psz == '\n')
477 psz++;
478
479 /* skip blank or comment lines. */
480 if (!*psz || *psz == '#' || *psz == ';' || *psz == '/')
481 continue;
482
483 /* remove the trailing newline. */
484 rc2 = (int)strlen(psz);
485 if (psz[rc2 - 1] == '\n')
486 psz[rc2 - 1] = '\0';
487
488 /* skip to the end of the digest and terminate it. */
489 pszDigest = psz;
490 while (*psz != ' ' && *psz != '\t' && *psz)
491 psz++;
492 if (*psz)
493 {
494 *psz++ = '\0';
495
496 /* blanks */
497 while (*psz == ' ' || *psz == '\t' || *psz == '\n')
498 psz++;
499
500 /* check for binary asterix */
501 if (*psz != '*')
502 fLineText = fBinaryTextOpt ? fText : 0;
503 else
504 {
505 fLineText = 0;
506 psz++;
507 }
508 if (*psz)
509 {
510 unsigned char Digest[16];
511
512 /* the rest is filename. */
513 pszFilename = psz;
514
515 /*
516 * Do the job.
517 */
518 rc2 = string_to_digest(pszDigest, Digest);
519 if (!rc2)
520 {
521 void *pvFile = open_file(pszFilename, fLineText);
522 if (pvFile)
523 {
524 if (!fQuiet)
525 fprintf(stdout, "%s: ", pszFilename);
526 rc2 = check_md5sum(pvFile, Digest, fProgress);
527 close_file(pvFile);
528 if (!fQuiet)
529 {
530 fprintf(stdout, "%s\n", !rc2 ? "OK" : rc2 < 0 ? "FAILURE" : "ERROR");
531 fflush(stdout);
532 if (rc2 > 0)
533 errx(1, "Error reading '%s': %s", pszFilename, strerror(rc2));
534 }
535 if (rc2)
536 rc = 1;
537 }
538 else
539 {
540 if (!fQuiet)
541 errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
542 rc = 1;
543 }
544 }
545 else if (!fQuiet)
546 {
547 errx(1, "%s (%d): Ignoring malformed digest '%s' (digest)", pszFilename, iLine, pszDigest);
548 errx(1, "%s (%d): %*s^", pszFilename, iLine, rc2 - 1, "");
549 }
550 }
551 else if (!fQuiet)
552 errx(1, "%s (%d): Ignoring malformed line!", pszFilename, iLine);
553 }
554 else if (!fQuiet)
555 errx(1, "%s (%d): Ignoring malformed line!", pszFilename, iLine);
556 } /* while more lines */
557
558 fclose(pFile);
559 }
560 else
561 {
562 errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
563 rc = 1;
564 }
565
566 return rc;
567}
568
569
570/**
571 * Calculates the MD5 sum for one file and prints it.
572 *
573 * @returns 0 on success, 1 on any kind of failure.
574 * @param pszFilename The file to process.
575 * @param fText The mode to open the file in.
576 * @param fQuiet Whether to be quiet or verbose about errors.
577 * @param fProgress Whether to show an progress indicator on large files.
578 */
579static int md5sum_file(const char *pszFilename, unsigned fText, unsigned fQuiet, unsigned fProgress)
580{
581 int rc;
582 void *pvFile;
583
584 /*
585 * Calcuate and print the MD5 sum for one file.
586 */
587 pvFile = open_file(pszFilename, fText);
588 if (pvFile)
589 {
590 unsigned char Digest[16];
591 rc = calc_md5sum(pvFile, Digest, fProgress);
592 close_file(pvFile);
593 if (!rc)
594 {
595 char szDigest[36];
596 digest_to_string(Digest, szDigest);
597 fprintf(stdout, "%s %s%s\n", szDigest, fText ? "" : "*", pszFilename);
598 fflush(stdout);
599 }
600 else
601 {
602 if (!fQuiet)
603 errx(1, "Failed to open '%s': %s", pszFilename, strerror(rc));
604 rc = 1;
605 }
606 }
607 else
608 {
609 if (!fQuiet)
610 errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
611 rc = 1;
612 }
613 return rc;
614}
615
616
617
618/**
619 * md5sum, calculates and checks the md5sum of files.
620 * Somewhat similar to the GNU coreutil md5sum command.
621 */
622int kmk_builtin_md5sum(int argc, char **argv, char **envp)
623{
624 int i;
625 int rc = 0;
626 int fText = 0;
627 int fBinaryTextOpt = 0;
628 int fQuiet = 0;
629 int fNewLine = 0;
630 int fChecking = 0;
631 int fProgress = 0;
632 int fNoMoreOptions = 0;
633
634 g_progname = argv[0];
635
636 /*
637 * Print usage if no arguments.
638 */
639 if (argc <= 1)
640 return usage(stderr);
641
642 /*
643 * Process the arguments, FIFO style.
644 */
645 i = 1;
646 while (i < argc)
647 {
648 char *psz = argv[i];
649 if (!fNoMoreOptions && psz[0] == '-' && psz[1] == '-' && !psz[2])
650 fNoMoreOptions = 1;
651 else if (*psz == '-' && !fNoMoreOptions)
652 {
653 psz++;
654
655 /* convert long options for gnu just for fun */
656 if (*psz == '-')
657 {
658 if (!strcmp(psz, "-binary"))
659 psz = "b";
660 else if (!strcmp(psz, "-text"))
661 psz = "t";
662 else if (!strcmp(psz, "-check"))
663 psz = "c";
664 else if (!strcmp(psz, "-check-file"))
665 psz = "C";
666 else if (!strcmp(psz, "-progress"))
667 psz = "p";
668 else if (!strcmp(psz, "-status"))
669 psz = "q";
670 else if (!strcmp(psz, "-warn"))
671 psz = "w";
672 else if (!strcmp(psz, "-help"))
673 psz = "h";
674 else if (!strcmp(psz, "-version"))
675 psz = "v";
676 }
677
678 /* short options */
679 do
680 {
681 switch (*psz)
682 {
683 case 'c':
684 fChecking = 1;
685 break;
686
687 case 'b':
688 fText = 0;
689 fBinaryTextOpt = 1;
690 break;
691
692 case 't':
693 fText = 1;
694 fBinaryTextOpt = 1;
695 break;
696
697 case 'p':
698 fProgress = 1;
699 break;
700
701 case 'q':
702 fQuiet = 1;
703 break;
704
705 case 'w':
706 /* ignored */
707 break;
708
709 case 'h':
710 usage(stdout);
711 return 0;
712
713 case 'v':
714 return version();
715
716 /*
717 * -C md5 file
718 */
719 case 'C':
720 {
721 const char *pszFilename;
722 const char *pszDigest;
723
724 if (psz[1])
725 pszDigest = &psz[1];
726 else if (i + 1 < argc)
727 pszDigest = argv[++i];
728 else
729 {
730 errx(1, "'-C' is missing the MD5 sum!");
731 return 1;
732 }
733 if (i + 1 < argc)
734 pszFilename = argv[++i];
735 else
736 {
737 errx(1, "'-C' is missing the filename!");
738 return 1;
739 }
740
741 rc |= check_one_file(pszFilename, pszDigest, fText, fQuiet, fProgress && !fQuiet);
742 psz = "\0";
743 break;
744 }
745
746 default:
747 errx(1, "Invalid option '%c'! (%s)", *psz, argv[i]);
748 return usage(stderr);
749 }
750 } while (*++psz);
751 }
752 else if (fChecking)
753 rc |= check_files(argv[i], fText, fBinaryTextOpt, fQuiet, fProgress && !fQuiet);
754 else
755 rc |= md5sum_file(argv[i], fText, fQuiet, fProgress && !fQuiet);
756 i++;
757 }
758
759 return rc;
760}
761
762
Note: See TracBrowser for help on using the repository browser.