VirtualBox

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

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