VirtualBox

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

Last change on this file since 1264 was 1231, checked in by bird, 17 years ago

Strip both \r\n and \n.

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