VirtualBox

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

Last change on this file since 2047 was 2019, checked in by bird, 17 years ago

GPLv2 -> GPLv3. See Ticket #44 for clarifications. Fixes #44.

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

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