VirtualBox

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

Last change on this file since 2984 was 2984, checked in by bird, 9 years ago

md5sum: buffer fun.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 23.6 KB
Line 
1/* $Id: md5sum.c 2984 2016-11-01 18:24:11Z bird $ */
2/** @file
3 * md5sum.
4 */
5
6/*
7 * Copyright (c) 2007-2010 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 struct MD5Context Ctx;
303 unsigned uPercent = 0;
304 double off = 0.0;
305 double cbFile = size_file(pvFile);
306
307 /* Get a decent sized buffer assuming we'll be spending more time reading
308 from the storage than doing MD5 sums. (2MB was choosen based on recent
309 SATA storage benchmarks which used that block size for sequential
310 tests.) We align the buffer address on a 16K boundrary to avoid most
311 transfer alignment issues. */
312 char *pabBufAligned;
313 size_t const cbBufAlign = 16*1024 - 1;
314 size_t const cbBufMax = 2048*1024;
315 size_t cbBuf = cbFile >= cbBufMax ? cbBufMax : ((size_t)cbFile + cbBufAlign) & ~(size_t)cbBufAlign;
316 char *pabBuf = (char *)malloc(cbBuf + cbBufAlign);
317 if (pabBuf)
318 pabBufAligned = (char *)(((uintptr_t)pabBuf + cbBufAlign) & ~(uintptr_t)cbBufAlign );
319 else
320 {
321 do
322 {
323 cbBuf /= 2;
324 pabBuf = (char *)malloc(cbBuf);
325 } while (!pabBuf && cbBuf > 4096);
326 if (!pabBuf)
327 return ENOMEM;
328 pabBufAligned = pabBuf;
329 }
330
331 if (cbFile < cbBuf * 4)
332 fProgress = 0;
333
334 MD5Init(&Ctx);
335 for (;;)
336 {
337 /* process a chunk. */
338 cb = read_file(pvFile, pabBufAligned, cbBuf);
339 if (cb > 0)
340 MD5Update(&Ctx, (unsigned char *)pabBufAligned, cb);
341 else if (!cb)
342 break;
343 else
344 {
345 rc = -cb;
346 break;
347 }
348
349 /* update the progress indicator. */
350 if (fProgress)
351 {
352 unsigned uNewPercent;
353 off += cb;
354 uNewPercent = (unsigned)((off / cbFile) * 100);
355 if (uNewPercent != uPercent)
356 {
357 if (uPercent)
358 printf("\b\b\b\b");
359 printf("%3d%%", uNewPercent);
360 fflush(stdout);
361 uPercent = uNewPercent;
362 }
363 }
364 }
365 MD5Final(pDigest, &Ctx);
366
367 if (fProgress)
368 printf("\b\b\b\b \b\b\b\b");
369
370 free(pabBuf);
371 return rc;
372}
373
374
375/**
376 * Checks the if the specified digest matches the digest of the file stream.
377 *
378 * @returns 0 on match, -1 on mismatch, errno value (positive) on failure.
379 * @param pvFile The file stream.
380 * @param Digest The MD5 digest.
381 * @param fProgress Whether to show an progress indicator on large files.
382 */
383static int check_md5sum(void *pvFile, unsigned char Digest[16], unsigned fProgress)
384{
385 unsigned char DigestFile[16];
386 int rc;
387
388 rc = calc_md5sum(pvFile, DigestFile, fProgress);
389 if (!rc)
390 rc = memcmp(Digest, DigestFile, 16) ? -1 : 0;
391 return rc;
392}
393
394
395/**
396 * Checks if the specified file matches the given MD5 digest.
397 *
398 * @returns 0 if it matches, 1 if it doesn't or an error occurs.
399 * @param pszFilename The name of the file to check.
400 * @param pszDigest The MD5 digest string.
401 * @param fText Whether to open the file in text or binary mode.
402 * @param fQuiet Whether to go about this in a quiet fashion or not.
403 * @param fProgress Whether to show an progress indicator on large files.
404 */
405static int check_one_file(const char *pszFilename, const char *pszDigest, unsigned fText, unsigned fQuiet, unsigned fProgress)
406{
407 unsigned char Digest[16];
408 int rc;
409
410 rc = string_to_digest(pszDigest, Digest);
411 if (!rc)
412 {
413 void *pvFile;
414
415 pvFile = open_file(pszFilename, fText);
416 if (pvFile)
417 {
418 if (!fQuiet)
419 fprintf(stdout, "%s: ", pszFilename);
420 rc = check_md5sum(pvFile, Digest, fProgress);
421 close_file(pvFile);
422 if (!fQuiet)
423 {
424 fprintf(stdout, "%s\n", !rc ? "OK" : rc < 0 ? "FAILURE" : "ERROR");
425 fflush(stdout);
426 if (rc > 0)
427 errx(1, "Error reading '%s': %s", pszFilename, strerror(rc));
428 }
429 if (rc)
430 rc = 1;
431 }
432 else
433 {
434 if (!fQuiet)
435 errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
436 rc = 1;
437 }
438 }
439 else
440 {
441 errx(1, "Malformed MD5 digest '%s'!", pszDigest);
442 errx(1, " %*s^", rc - 1, "");
443 rc = 1;
444 }
445
446 return rc;
447}
448
449
450/**
451 * Checks the specified md5.lst file.
452 *
453 * @returns 0 if all checks out file, 1 if one or more fails or there are read errors.
454 * @param pszFilename The name of the file.
455 * @param fText The default mode, text or binary. Only used when fBinaryTextOpt is true.
456 * @param fBinaryTextOpt Whether a -b or -t option was specified and should be used.
457 * @param fQuiet Whether to be quiet.
458 * @param fProgress Whether to show an progress indicator on large files.
459 */
460static int check_files(const char *pszFilename, int fText, int fBinaryTextOpt, int fQuiet, unsigned fProgress)
461{
462 int rc = 0;
463 FILE *pFile;
464
465 /*
466 * Try open the md5.lst file and process it line by line.
467 */
468 pFile = fopen(pszFilename, "r");
469 if (pFile)
470 {
471 int iLine = 0;
472 char szLine[8192];
473 while (fgets(szLine, sizeof(szLine), pFile))
474 {
475 const char *pszDigest;
476 int fLineText;
477 char *psz;
478 int rc2;
479
480 iLine++;
481 psz = szLine;
482
483 /* leading blanks */
484 while (*psz == ' ' || *psz == '\t' || *psz == '\n')
485 psz++;
486
487 /* skip blank or comment lines. */
488 if (!*psz || *psz == '#' || *psz == ';' || *psz == '/')
489 continue;
490
491 /* remove the trailing newline. */
492 rc2 = (int)strlen(psz);
493 if (psz[rc2 - 1] == '\n')
494 psz[rc2 - (rc2 >= 2 && psz[rc2 - 2] == '\r' ? 2 : 1)] = '\0';
495
496 /* skip to the end of the digest and terminate it. */
497 pszDigest = psz;
498 while (*psz != ' ' && *psz != '\t' && *psz)
499 psz++;
500 if (*psz)
501 {
502 *psz++ = '\0';
503
504 /* blanks */
505 while (*psz == ' ' || *psz == '\t' || *psz == '\n')
506 psz++;
507
508 /* check for binary asterix */
509 if (*psz != '*')
510 fLineText = fBinaryTextOpt ? fText : 0;
511 else
512 {
513 fLineText = 0;
514 psz++;
515 }
516 if (*psz)
517 {
518 unsigned char Digest[16];
519
520 /* the rest is filename. */
521 pszFilename = psz;
522
523 /*
524 * Do the job.
525 */
526 rc2 = string_to_digest(pszDigest, Digest);
527 if (!rc2)
528 {
529 void *pvFile = open_file(pszFilename, fLineText);
530 if (pvFile)
531 {
532 if (!fQuiet)
533 fprintf(stdout, "%s: ", pszFilename);
534 rc2 = check_md5sum(pvFile, Digest, fProgress);
535 close_file(pvFile);
536 if (!fQuiet)
537 {
538 fprintf(stdout, "%s\n", !rc2 ? "OK" : rc2 < 0 ? "FAILURE" : "ERROR");
539 fflush(stdout);
540 if (rc2 > 0)
541 errx(1, "Error reading '%s': %s", pszFilename, strerror(rc2));
542 }
543 if (rc2)
544 rc = 1;
545 }
546 else
547 {
548 if (!fQuiet)
549 errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
550 rc = 1;
551 }
552 }
553 else if (!fQuiet)
554 {
555 errx(1, "%s (%d): Ignoring malformed digest '%s' (digest)", pszFilename, iLine, pszDigest);
556 errx(1, "%s (%d): %*s^", pszFilename, iLine, rc2 - 1, "");
557 }
558 }
559 else if (!fQuiet)
560 errx(1, "%s (%d): Ignoring malformed line!", pszFilename, iLine);
561 }
562 else if (!fQuiet)
563 errx(1, "%s (%d): Ignoring malformed line!", pszFilename, iLine);
564 } /* while more lines */
565
566 fclose(pFile);
567 }
568 else
569 {
570 errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
571 rc = 1;
572 }
573
574 return rc;
575}
576
577
578/**
579 * Calculates the MD5 sum for one file and prints it.
580 *
581 * @returns 0 on success, 1 on any kind of failure.
582 * @param pszFilename The file to process.
583 * @param fText The mode to open the file in.
584 * @param fQuiet Whether to be quiet or verbose about errors.
585 * @param fProgress Whether to show an progress indicator on large files.
586 * @param pOutput Where to write the list. Progress is always written to stdout.
587 */
588static int md5sum_file(const char *pszFilename, unsigned fText, unsigned fQuiet, unsigned fProgress, FILE *pOutput)
589{
590 int rc;
591 void *pvFile;
592
593 /*
594 * Calculate and print the MD5 sum for one file.
595 */
596 pvFile = open_file(pszFilename, fText);
597 if (pvFile)
598 {
599 unsigned char Digest[16];
600
601 if (fProgress && pOutput)
602 fprintf(stdout, "%s: ", pszFilename);
603
604 rc = calc_md5sum(pvFile, Digest, fProgress);
605 close_file(pvFile);
606
607 if (fProgress && pOutput)
608 {
609 size_t cch = strlen(pszFilename) + 2;
610 while (cch-- > 0)
611 fputc('\b', stdout);
612 }
613
614 if (!rc)
615 {
616 char szDigest[36];
617 digest_to_string(Digest, szDigest);
618 if (pOutput)
619 {
620 fprintf(pOutput, "%s %s%s\n", szDigest, fText ? "" : "*", pszFilename);
621 fflush(pOutput);
622 }
623 fprintf(stdout, "%s %s%s\n", szDigest, fText ? "" : "*", pszFilename);
624 fflush(stdout);
625 }
626 else
627 {
628 if (!fQuiet)
629 errx(1, "Failed to open '%s': %s", pszFilename, strerror(rc));
630 rc = 1;
631 }
632 }
633 else
634 {
635 if (!fQuiet)
636 errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
637 rc = 1;
638 }
639 return rc;
640}
641
642
643
644/**
645 * md5sum, calculates and checks the md5sum of files.
646 * Somewhat similar to the GNU coreutil md5sum command.
647 */
648int kmk_builtin_md5sum(int argc, char **argv, char **envp)
649{
650 int i;
651 int rc = 0;
652 int fText = 0;
653 int fBinaryTextOpt = 0;
654 int fQuiet = 0;
655 int fChecking = 0;
656 int fProgress = 0;
657 int fNoMoreOptions = 0;
658 const char *pszOutput = NULL;
659 FILE *pOutput = NULL;
660
661 g_progname = argv[0];
662
663 /*
664 * Print usage if no arguments.
665 */
666 if (argc <= 1)
667 return usage(stderr);
668
669 /*
670 * Process the arguments, FIFO style.
671 */
672 i = 1;
673 while (i < argc)
674 {
675 char *psz = argv[i];
676 if (!fNoMoreOptions && psz[0] == '-' && psz[1] == '-' && !psz[2])
677 fNoMoreOptions = 1;
678 else if (*psz == '-' && !fNoMoreOptions)
679 {
680 psz++;
681
682 /* convert long options for gnu just for fun */
683 if (*psz == '-')
684 {
685 if (!strcmp(psz, "-binary"))
686 psz = "b";
687 else if (!strcmp(psz, "-text"))
688 psz = "t";
689 else if (!strcmp(psz, "-check"))
690 psz = "c";
691 else if (!strcmp(psz, "-check-file"))
692 psz = "C";
693 else if (!strcmp(psz, "-output"))
694 psz = "o";
695 else if (!strcmp(psz, "-progress"))
696 psz = "p";
697 else if (!strcmp(psz, "-status"))
698 psz = "q";
699 else if (!strcmp(psz, "-warn"))
700 psz = "w";
701 else if (!strcmp(psz, "-help"))
702 psz = "h";
703 else if (!strcmp(psz, "-version"))
704 psz = "v";
705 }
706
707 /* short options */
708 do
709 {
710 switch (*psz)
711 {
712 case 'c':
713 fChecking = 1;
714 break;
715
716 case 'b':
717 fText = 0;
718 fBinaryTextOpt = 1;
719 break;
720
721 case 't':
722 fText = 1;
723 fBinaryTextOpt = 1;
724 break;
725
726 case 'p':
727 fProgress = 1;
728 break;
729
730 case 'q':
731 fQuiet = 1;
732 break;
733
734 case 'w':
735 /* ignored */
736 break;
737
738 case 'h':
739 usage(stdout);
740 return 0;
741
742 case 'v':
743 return kbuild_version(argv[0]);
744
745 /*
746 * -C md5 file
747 */
748 case 'C':
749 {
750 const char *pszFilename;
751 const char *pszDigest;
752
753 if (psz[1])
754 pszDigest = &psz[1];
755 else if (i + 1 < argc)
756 pszDigest = argv[++i];
757 else
758 {
759 errx(1, "'-C' is missing the MD5 sum!");
760 return 1;
761 }
762 if (i + 1 < argc)
763 pszFilename = argv[++i];
764 else
765 {
766 errx(1, "'-C' is missing the filename!");
767 return 1;
768 }
769
770 rc |= check_one_file(pszFilename, pszDigest, fText, fQuiet, fProgress && !fQuiet);
771 psz = "\0";
772 break;
773 }
774
775 /*
776 * Output file.
777 */
778 case 'o':
779 {
780 if (fChecking)
781 {
782 errx(1, "'-o' cannot be used with -c or -C!");
783 return 1;
784 }
785
786 if (psz[1])
787 pszOutput = &psz[1];
788 else if (i + 1 < argc)
789 pszOutput = argv[++i];
790 else
791 {
792 errx(1, "'-o' is missing the file name!");
793 return 1;
794 }
795
796 psz = "\0";
797 break;
798 }
799
800 default:
801 errx(1, "Invalid option '%c'! (%s)", *psz, argv[i]);
802 return usage(stderr);
803 }
804 } while (*++psz);
805 }
806 else if (fChecking)
807 rc |= check_files(argv[i], fText, fBinaryTextOpt, fQuiet, fProgress && !fQuiet);
808 else
809 {
810 /* lazily open the output if specified. */
811 if (pszOutput)
812 {
813 if (pOutput)
814 fclose(pOutput);
815 pOutput = fopen(pszOutput, "w");
816 if (!pOutput)
817 {
818 rc = err(1, "fopen(\"%s\", \"w\") failed", pszOutput);
819 break;
820 }
821 pszOutput = NULL;
822 }
823
824 rc |= md5sum_file(argv[i], fText, fQuiet, fProgress && !fQuiet, pOutput);
825 }
826 i++;
827 }
828
829 if (pOutput)
830 fclose(pOutput);
831 return rc;
832}
833
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