VirtualBox

source: kBuild/trunk/src/kmk/kmkbuiltin/md5sum.c@ 1422

Last change on this file since 1422 was 1393, checked in by bird, 17 years ago

Added an --output/-o option for use with -p.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette