source: trunk/src/kmk/kmkbuiltin/md5sum.c@ 1101

Last change on this file since 1101 was 1101, checked in by bird, 18 years ago

Added md5sum as a builtin tool.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 15.2 KB
Line 
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 */
39static 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 */
52static 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 */
74static 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 */
94static 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 */
143static 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 */
180static 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 */
199static 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 */
215int 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
Note: See TracBrowser for help on using the repository browser.