VirtualBox

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

Last change on this file since 2134 was 2130, checked in by bird, 16 years ago

md5sum: warnings.

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