1 | /* $Id: md5sum.c 1101 2007-09-22 22:01:37Z bird $ */
|
---|
2 | /** @file
|
---|
3 | * md5sum.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net>
|
---|
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 "err.h"
|
---|
31 | #include "kmkbuiltin.h"
|
---|
32 | #include "../../lib/md5.h"
|
---|
33 |
|
---|
34 |
|
---|
35 |
|
---|
36 | /**
|
---|
37 | * Prints the usage and return 1.
|
---|
38 | */
|
---|
39 | static int usage(void)
|
---|
40 | {
|
---|
41 | fprintf(stderr,
|
---|
42 | "usage: md5sum [-bt] file [string ...]\n"
|
---|
43 | " or: md5sum [-cbtwq] file\n");
|
---|
44 | return 1;
|
---|
45 | }
|
---|
46 |
|
---|
47 |
|
---|
48 | /**
|
---|
49 | * Prints the version string.
|
---|
50 | * @returns 0
|
---|
51 | */
|
---|
52 | static int version(void)
|
---|
53 | {
|
---|
54 | #ifdef kmk_builtin_md5sum
|
---|
55 | fprintf(stdout, "kmk_md5sum (kBuild) %d.%d.%d\n"
|
---|
56 | "Copyright (c) 2007 knut st. osmundsen\n",
|
---|
57 | KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH);
|
---|
58 | #else
|
---|
59 | fprintf(stdout, "kmk_builtin_md5sum (kBuild) %d.%d.%d\n"
|
---|
60 | "Copyright (c) 2007 knut st. osmundsen\n",
|
---|
61 | KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH);
|
---|
62 | #endif
|
---|
63 | return 0;
|
---|
64 | }
|
---|
65 |
|
---|
66 |
|
---|
67 | /**
|
---|
68 | * Makes a string out of the given digest.
|
---|
69 | *
|
---|
70 | * @param pDigest The MD5 digest.
|
---|
71 | * @param pszDigest Where to put the digest string. Must be able to
|
---|
72 | * hold at least 33 bytes.
|
---|
73 | */
|
---|
74 | static void digest_to_string(unsigned char pDigest[16], char *pszDigest)
|
---|
75 | {
|
---|
76 | unsigned i;
|
---|
77 | for (i = 0; i < 16; i++)
|
---|
78 | {
|
---|
79 | static char s_achDigits[17] = "0123456789abcdef";
|
---|
80 | pszDigest[i*2] = s_achDigits[(pDigest[i] >> 4)];
|
---|
81 | pszDigest[i*2 + 1] = s_achDigits[(pDigest[i] & 0xf)];
|
---|
82 | }
|
---|
83 | pszDigest[i*2] = '\0';
|
---|
84 | }
|
---|
85 |
|
---|
86 |
|
---|
87 | /**
|
---|
88 | * Attempts to convert a string to a MD5 digest.
|
---|
89 | *
|
---|
90 | * @returns 0 on success, 1-based position of the failure first error.
|
---|
91 | * @param pszDigest The string to interpret.
|
---|
92 | * @param pDigest Where to put the MD5 digest.
|
---|
93 | */
|
---|
94 | static int string_to_digest(const char *pszDigest, unsigned char pDigest[16])
|
---|
95 | {
|
---|
96 | unsigned i;
|
---|
97 | unsigned iBase = 1;
|
---|
98 |
|
---|
99 | /* skip blanks */
|
---|
100 | while ( *pszDigest == ' '
|
---|
101 | || *pszDigest == '\t'
|
---|
102 | || *pszDigest == '\n'
|
---|
103 | || *pszDigest == '\r')
|
---|
104 | pszDigest++, iBase++;
|
---|
105 |
|
---|
106 | /* convert the digits. */
|
---|
107 | memset(pDigest, 0, 16);
|
---|
108 | for (i = 0; i < 32; i++, pszDigest++)
|
---|
109 | {
|
---|
110 | int iDigit;
|
---|
111 | if (*pszDigest >= '0' && *pszDigest <= '9')
|
---|
112 | iDigit = *pszDigest - '0';
|
---|
113 | else if (*pszDigest >= 'a' && *pszDigest <= 'f')
|
---|
114 | iDigit = *pszDigest - 'a' + 10;
|
---|
115 | else if (*pszDigest >= 'A' && *pszDigest <= 'F')
|
---|
116 | iDigit = *pszDigest - 'A' + 10;
|
---|
117 | else
|
---|
118 | return i + iBase;
|
---|
119 | if (i & 1)
|
---|
120 | pDigest[i >> 1] |= iDigit;
|
---|
121 | else
|
---|
122 | pDigest[i >> 1] |= iDigit << 4;
|
---|
123 | }
|
---|
124 |
|
---|
125 | /* the rest of the string must now be blanks. */
|
---|
126 | while ( *pszDigest == ' '
|
---|
127 | || *pszDigest == '\t'
|
---|
128 | || *pszDigest == '\n'
|
---|
129 | || *pszDigest == '\r')
|
---|
130 | pszDigest++, i++;
|
---|
131 |
|
---|
132 | return *pszDigest ? i + iBase : 0;
|
---|
133 | }
|
---|
134 |
|
---|
135 |
|
---|
136 | /**
|
---|
137 | * Calculates the md5sum of the sepecified file stream.
|
---|
138 | *
|
---|
139 | * @returns errno on failure, 0 on success.
|
---|
140 | * @param pFile The file stream.
|
---|
141 | * @param pDigest Where to store the MD5 digest.
|
---|
142 | */
|
---|
143 | static int calc_md5sum(FILE *pFile, unsigned char pDigest[16])
|
---|
144 | {
|
---|
145 | int rc = 0;
|
---|
146 | int cb;
|
---|
147 | char abBuf[16384];
|
---|
148 | struct MD5Context Ctx;
|
---|
149 |
|
---|
150 | MD5Init(&Ctx);
|
---|
151 | for (;;)
|
---|
152 | {
|
---|
153 | errno = 0;
|
---|
154 | cb = (int)fread(abBuf, 1, sizeof(abBuf), pFile);
|
---|
155 | if (cb > 0)
|
---|
156 | MD5Update(&Ctx, abBuf, cb);
|
---|
157 | else if (cb == 0)
|
---|
158 | break;
|
---|
159 | else
|
---|
160 | {
|
---|
161 | rc = errno;
|
---|
162 | if (!rc)
|
---|
163 | rc = EINVAL;
|
---|
164 | break;
|
---|
165 | }
|
---|
166 | }
|
---|
167 | MD5Final(pDigest, &Ctx);
|
---|
168 |
|
---|
169 | return rc;
|
---|
170 | }
|
---|
171 |
|
---|
172 |
|
---|
173 | /**
|
---|
174 | * Checks the if the specified digest matches the digest of the file stream.
|
---|
175 | *
|
---|
176 | * @returns 0 on match, -1 on mismatch, errno value (positive) on failure.
|
---|
177 | * @param pFile The file stream.
|
---|
178 | * @param Digest The MD5 digest.
|
---|
179 | */
|
---|
180 | static int check_md5sum(FILE *pFile, unsigned char Digest[16])
|
---|
181 | {
|
---|
182 | unsigned char DigestFile[16];
|
---|
183 | int rc;
|
---|
184 |
|
---|
185 | rc = calc_md5sum(pFile, DigestFile);
|
---|
186 | if (!rc)
|
---|
187 | rc = memcmp(Digest, DigestFile, 16) ? -1 : 0;
|
---|
188 | return rc;
|
---|
189 | }
|
---|
190 |
|
---|
191 |
|
---|
192 | /**
|
---|
193 | * Opens the specified file for md5 sum calculation.
|
---|
194 | *
|
---|
195 | * @returns File stream on success, NULL and errno on failure.
|
---|
196 | * @param pszFilename The filename.
|
---|
197 | * @param fText Whether text or binary mode should be used.
|
---|
198 | */
|
---|
199 | static FILE *open_file(const char *pszFilename, unsigned fText)
|
---|
200 | {
|
---|
201 | FILE *pFile;
|
---|
202 |
|
---|
203 | errno = 0;
|
---|
204 | pFile = fopen(pszFilename, fText ? "r" : "rb");
|
---|
205 | if (!pFile && errno == EINVAL && !fText)
|
---|
206 | pFile = fopen(pszFilename, "r");
|
---|
207 | return pFile;
|
---|
208 | }
|
---|
209 |
|
---|
210 |
|
---|
211 | /**
|
---|
212 | * md5sum, calculates and checks the md5sum of files.
|
---|
213 | * Somewhat similar to the GNU coreutil md5sum command.
|
---|
214 | */
|
---|
215 | int kmk_builtin_md5sum(int argc, char **argv, char **envp)
|
---|
216 | {
|
---|
217 | int i;
|
---|
218 | int rc = 0;
|
---|
219 | int fText = 0;
|
---|
220 | int fBinaryTextOpt = 0;
|
---|
221 | int fQuiet = 0;
|
---|
222 | int fNewLine = 0;
|
---|
223 | int fChecking = 0;
|
---|
224 | int fNoMoreOptions = 0;
|
---|
225 | unsigned char Digest[16];
|
---|
226 | const char *pszFilename;
|
---|
227 | const char *pszDigest;
|
---|
228 | char szDigest[36];
|
---|
229 | FILE *pFile;
|
---|
230 | int rc2;
|
---|
231 |
|
---|
232 | g_progname = argv[0];
|
---|
233 |
|
---|
234 | /*
|
---|
235 | * Print usage if no arguments.
|
---|
236 | */
|
---|
237 | if (argc <= 1)
|
---|
238 | return usage();
|
---|
239 |
|
---|
240 | /*
|
---|
241 | * Process the arguments, FIFO style.
|
---|
242 | */
|
---|
243 | i = 1;
|
---|
244 | while (i < argc)
|
---|
245 | {
|
---|
246 | char *psz = argv[i];
|
---|
247 | if (!fNoMoreOptions && psz[0] == '-' && psz[1] == '-' && !psz[2])
|
---|
248 | fNoMoreOptions = 1;
|
---|
249 | else if (*psz == '-' && !fNoMoreOptions)
|
---|
250 | {
|
---|
251 | psz++;
|
---|
252 | /* convert long options for gnu just for fun */
|
---|
253 | if (*psz == '-')
|
---|
254 | {
|
---|
255 | if (!strcmp(psz, "-binary"))
|
---|
256 | psz = "b";
|
---|
257 | else if (!strcmp(psz, "-text"))
|
---|
258 | psz = "t";
|
---|
259 | else if (!strcmp(psz, "-check"))
|
---|
260 | psz = "c";
|
---|
261 | else if (!strcmp(psz, "-check-this"))
|
---|
262 | psz = "C";
|
---|
263 | else if (!strcmp(psz, "-status"))
|
---|
264 | psz = "q";
|
---|
265 | else if (!strcmp(psz, "-warn"))
|
---|
266 | psz = "w";
|
---|
267 | else if (!strcmp(psz, "-help"))
|
---|
268 | psz = "h";
|
---|
269 | else if (!strcmp(psz, "-version"))
|
---|
270 | psz = "v";
|
---|
271 | }
|
---|
272 |
|
---|
273 | /* short options */
|
---|
274 | do
|
---|
275 | {
|
---|
276 | switch (*psz)
|
---|
277 | {
|
---|
278 | case 'c':
|
---|
279 | fChecking = 1;
|
---|
280 | break;
|
---|
281 |
|
---|
282 | case 'b':
|
---|
283 | fText = 0;
|
---|
284 | fBinaryTextOpt = 1;
|
---|
285 | break;
|
---|
286 |
|
---|
287 | case 't':
|
---|
288 | fText = 1;
|
---|
289 | fBinaryTextOpt = 1;
|
---|
290 | break;
|
---|
291 |
|
---|
292 | case 'q':
|
---|
293 | fQuiet = 1;
|
---|
294 | break;
|
---|
295 |
|
---|
296 | case 'w':
|
---|
297 | /* ignored */
|
---|
298 | break;
|
---|
299 |
|
---|
300 | case 'v':
|
---|
301 | return version();
|
---|
302 |
|
---|
303 | /*
|
---|
304 | * -C md5 file
|
---|
305 | */
|
---|
306 | case 'C':
|
---|
307 | {
|
---|
308 | if (psz[1])
|
---|
309 | pszDigest = &psz[1];
|
---|
310 | else if (i + 1 < argc)
|
---|
311 | pszDigest = argv[++i];
|
---|
312 | else
|
---|
313 | {
|
---|
314 | errx(1, "'-C' is missing the MD5 sum!");
|
---|
315 | return 1;
|
---|
316 | }
|
---|
317 | rc2 = string_to_digest(pszDigest, Digest);
|
---|
318 | if (rc2)
|
---|
319 | {
|
---|
320 | errx(1, "Malformed MD5 digest '%s'!", pszDigest);
|
---|
321 | errx(1, " %*s^", rc2 - 1, "");
|
---|
322 | return 1;
|
---|
323 | }
|
---|
324 |
|
---|
325 | if (i + 1 < argc)
|
---|
326 | pszFilename = argv[++i];
|
---|
327 | else
|
---|
328 | {
|
---|
329 | errx(1, "'-C' is missing the filename!");
|
---|
330 | return 1;
|
---|
331 | }
|
---|
332 | pFile = open_file(pszFilename, fText);
|
---|
333 | if (pFile)
|
---|
334 | {
|
---|
335 | rc2 = check_md5sum(pFile, Digest);
|
---|
336 | if (!fQuiet)
|
---|
337 | {
|
---|
338 | if (rc2 <= 0)
|
---|
339 | {
|
---|
340 | fprintf(stdout, "%s: %s\n", pszFilename, !rc2 ? "OK" : "FAILURE");
|
---|
341 | fflush(stdout);
|
---|
342 | }
|
---|
343 | else
|
---|
344 | errx(1, "Error reading '%s': %s", pszFilename, strerror(rc));
|
---|
345 | }
|
---|
346 | if (rc2)
|
---|
347 | rc = 1;
|
---|
348 | fclose(pFile);
|
---|
349 | }
|
---|
350 | else
|
---|
351 | {
|
---|
352 | if (!fQuiet)
|
---|
353 | errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
|
---|
354 | rc = 1;
|
---|
355 | }
|
---|
356 | psz = "\0";
|
---|
357 | break;
|
---|
358 | }
|
---|
359 |
|
---|
360 | default:
|
---|
361 | errx(1, "Invalid option '%c'! (%s)", *psz, argv[i]);
|
---|
362 | return usage();
|
---|
363 | }
|
---|
364 | } while (*++psz);
|
---|
365 | }
|
---|
366 | else if (fChecking)
|
---|
367 | {
|
---|
368 | pFile = fopen(argv[i], "r");
|
---|
369 | if (pFile)
|
---|
370 | {
|
---|
371 | int iLine = 0;
|
---|
372 | char szLine[8192];
|
---|
373 | while (fgets(szLine, sizeof(szLine), pFile))
|
---|
374 | {
|
---|
375 | int fLineText;
|
---|
376 | char *psz = szLine;
|
---|
377 | iLine++;
|
---|
378 |
|
---|
379 | /* leading blanks */
|
---|
380 | while (*psz == ' ' || *psz == '\t' || *psz == '\n')
|
---|
381 | psz++;
|
---|
382 |
|
---|
383 | /* skip blank or comment lines. */
|
---|
384 | if (!*psz || *psz == '#' || *psz == ';' || *psz == '/')
|
---|
385 | continue;
|
---|
386 |
|
---|
387 | /* remove the trailing newline. */
|
---|
388 | rc2 = (int)strlen(psz);
|
---|
389 | if (psz[rc2 - 1] == '\n')
|
---|
390 | psz[rc2 - 1] = '\0';
|
---|
391 |
|
---|
392 | /* skip to the end of the digest and terminate it. */
|
---|
393 | pszDigest = psz;
|
---|
394 | while (*psz != ' ' && *psz != '\t' && *psz)
|
---|
395 | psz++;
|
---|
396 | if (*psz)
|
---|
397 | {
|
---|
398 | *psz++ = '\0';
|
---|
399 |
|
---|
400 | /* blanks */
|
---|
401 | while (*psz == ' ' || *psz == '\t' || *psz == '\n')
|
---|
402 | psz++;
|
---|
403 |
|
---|
404 | /* check for binary asterix */
|
---|
405 | if (*psz != '*')
|
---|
406 | fLineText = fBinaryTextOpt ? fText : 0;
|
---|
407 | else
|
---|
408 | {
|
---|
409 | fLineText = 0;
|
---|
410 | psz++;
|
---|
411 | }
|
---|
412 | if (*psz)
|
---|
413 | {
|
---|
414 | /* the rest is filename. */
|
---|
415 | pszFilename = psz;
|
---|
416 |
|
---|
417 | /*
|
---|
418 | * Do the job.
|
---|
419 | */
|
---|
420 | rc2 = string_to_digest(pszDigest, Digest);
|
---|
421 | if (!rc2)
|
---|
422 | {
|
---|
423 | FILE *pFile2 = open_file(pszFilename, fLineText);
|
---|
424 | if (pFile2)
|
---|
425 | {
|
---|
426 | rc2 = check_md5sum(pFile2, Digest);
|
---|
427 | if (!fQuiet)
|
---|
428 | {
|
---|
429 | if (rc2 <= 0)
|
---|
430 | {
|
---|
431 | fprintf(stdout, "%s: %s\n", pszFilename, !rc2 ? "OK" : "FAILURE");
|
---|
432 | fflush(stdout);
|
---|
433 | }
|
---|
434 | else
|
---|
435 | errx(1, "Error reading '%s': %s", pszFilename, strerror(rc));
|
---|
436 | }
|
---|
437 | if (rc2)
|
---|
438 | rc = 1;
|
---|
439 | fclose(pFile2);
|
---|
440 | }
|
---|
441 | else
|
---|
442 | {
|
---|
443 | if (!fQuiet)
|
---|
444 | errx(1, "Failed to open '%s': %s", pszFilename, strerror(errno));
|
---|
445 | rc = 1;
|
---|
446 | }
|
---|
447 | }
|
---|
448 | else if (!fQuiet)
|
---|
449 | {
|
---|
450 | errx(1, "%s (%d): Ignoring malformed digest '%s' (digest)", argv[i], iLine, pszDigest);
|
---|
451 | errx(1, "%s (%d): %*s^", argv[i], iLine, rc2 - 1, "");
|
---|
452 | }
|
---|
453 | }
|
---|
454 | else if (!fQuiet)
|
---|
455 | errx(1, "%s (%d): Ignoring malformed line!", argv[i], iLine);
|
---|
456 | }
|
---|
457 | else if (!fQuiet)
|
---|
458 | errx(1, "%s (%d): Ignoring malformed line!", argv[i], iLine);
|
---|
459 | }
|
---|
460 |
|
---|
461 | fclose(pFile);
|
---|
462 | }
|
---|
463 | else
|
---|
464 | {
|
---|
465 | errx(1, "Failed to open '%s': %s", argv[i], strerror(errno));
|
---|
466 | rc = 1;
|
---|
467 | }
|
---|
468 | }
|
---|
469 | else
|
---|
470 | {
|
---|
471 | /*
|
---|
472 | * Calcuate and print the MD5 sum for one file.
|
---|
473 | */
|
---|
474 | pFile = open_file(argv[i], fText);
|
---|
475 | if (pFile)
|
---|
476 | {
|
---|
477 | rc2 = calc_md5sum(pFile, Digest);
|
---|
478 | if (!rc2)
|
---|
479 | {
|
---|
480 | digest_to_string(Digest, szDigest);
|
---|
481 | fprintf(stdout, "%s %s%s\n", szDigest, fText ? "" : "*", argv[i]);
|
---|
482 | fflush(stdout);
|
---|
483 | }
|
---|
484 | else
|
---|
485 | {
|
---|
486 | if (!fQuiet)
|
---|
487 | errx(1, "Failed to open '%s': %s", argv[i], strerror(rc));
|
---|
488 | rc = 1;
|
---|
489 | }
|
---|
490 | fclose(pFile);
|
---|
491 | }
|
---|
492 | else
|
---|
493 | {
|
---|
494 | if (!fQuiet)
|
---|
495 | errx(1, "Failed to open '%s': %s", argv[i], strerror(errno));
|
---|
496 | rc = 1;
|
---|
497 | }
|
---|
498 | }
|
---|
499 | i++;
|
---|
500 | }
|
---|
501 |
|
---|
502 | return rc;
|
---|
503 | }
|
---|
504 |
|
---|
505 |
|
---|