Substitute date and version into docs when we do gitdist
[jigit.git] / jigsum.c
1 /*
2  * jigsum
3  *
4  * Tool to calculate and print MD5 checksums in jigdo's awkward
5  * base64-ish encoding.
6  *
7  * Copyright (c) 2004-2019 Steve McIntyre <steve@einval.com>
8  *
9  * GPL v2 - see COPYING
10  */
11
12 #include <stdio.h>
13 #include <errno.h>
14 #include <stdlib.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <string.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include "jig-base64.h"
21 #include "md5.h"
22
23 #define BUF_SIZE 65536
24
25 /* MD5 is 128-bit! */
26 #define CKSUM_BITS 128
27 #define CKSUM_BYTES (CKSUM_BITS / 8)
28 #define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))
29 #define BASE64_CKSUM_BYTES ((ROUND_UP (CKSUM_BITS, 6)) / 6)
30
31 #ifndef MIN
32 #define MIN(x,y)        ( ((x) < (y)) ? (x) : (y))
33 #endif
34
35 enum mode_e
36 {
37     MODE_CHECK,
38     MODE_CALC
39 };
40
41 static int md5_file(char *filename, char *md5, int verbose)
42 {
43     FILE *file = NULL;
44     char buf[BUF_SIZE];
45     unsigned char file_md5[CKSUM_BYTES] = {0};
46     char *base64_md5 = NULL;
47     struct mk_MD5Context file_context;
48     int done = 0;
49     int bytes_read = 0;
50     int error = 0;
51     off_t file_size = -1;
52
53     mk_MD5Init(&file_context);
54
55     /* Check if we're reading from stdin */
56     if (!strcmp("-", filename))
57         file = stdin;
58     else
59     {
60         file = fopen(filename, "rb");
61         if (!file)
62         {
63             switch (errno)
64             {
65                 case EACCES:
66                 case EISDIR:
67                     break;
68                 default:
69                     fprintf(stderr, "Unable to open file %s; error %d\n", filename, errno);
70                     break;
71             }
72             return errno;
73         }
74     }
75
76     if (verbose == 3)
77     {
78         fprintf(stderr, "Checking %s:\r", filename);
79         fflush(stdout);
80     }
81
82     if (verbose == 4)
83     {
84         struct stat st;
85         if (file != stdin)
86         {
87             stat(filename, &st);
88             file_size = st.st_size;
89             fprintf(stderr, "Checking %s: 0 / %lld bytes\r", filename, (long long)file_size);
90         }
91         else
92         {
93             fprintf(stderr, "Checking stdin: 0 bytes\r");
94         }
95         fflush(stdout);
96     }
97     
98     while (!done)
99     {
100         int used = 0;
101         memset(buf, 0, BUF_SIZE);
102
103         used = fread(buf, 1, BUF_SIZE, file);
104         bytes_read += used;
105         if (used)
106             mk_MD5Update(&file_context, (unsigned char *)buf, used);
107         else
108         {
109             if (ferror(file) && (EISDIR != errno))
110             {
111                 fprintf(stderr, "Unable to read from file %s; error %d\n",
112                         filename, errno);
113                 error = errno;
114             }
115             break;
116         }
117         if (verbose == 4)
118         {
119             if (file != stdin)
120                 fprintf(stderr, "Checking %s: %lld / %lld bytes\r",
121                         filename, (long long) bytes_read, (long long)file_size);
122             else
123                 fprintf(stderr, "Checking stdin: %lld bytes\r", (long long) bytes_read);
124             fflush(stdout);
125         }
126     }
127     if (verbose == 4)
128         fprintf(stderr, "\n");
129     
130     mk_MD5Final(file_md5, &file_context);
131     base64_md5 = base64_dump(file_md5, CKSUM_BYTES);
132     memcpy(md5, base64_md5, BASE64_CKSUM_BYTES + 1);
133     fflush(stdout);
134     free(base64_md5);
135
136     if (file != stdin)
137         fclose(file);
138     
139     return error;
140 }
141
142 static int md5_check(char *filename, int verbose)
143 {
144     FILE *file = NULL;
145     char *line = NULL;
146     size_t len = 0;
147     ssize_t read;
148     char base64_md5[BASE64_CKSUM_BYTES + 1] = {0};
149     int error = 0;
150
151     /* Check if we're reading from stdin */
152     if (!strcmp("-", filename))
153         file = stdin;
154     else
155     {
156         file = fopen(filename, "rb");
157         if (!file)
158         {
159             switch (errno)
160             {
161                 case EACCES:
162                 case EISDIR:
163                     break;
164                 default:
165                     fprintf(stderr, "Unable to open file %s; error %d\n", filename, errno);
166                     break;
167             }
168             return errno;
169         }
170     }
171
172     while ((read = getline(&line, &len, file)) != -1) {
173         /* Check the format of the line we've read. Should be:
174
175            <N chars of sum><SPACE><SPACE><filename>
176
177            where N == BASE64_CKSUM_BYTES
178
179            Look for the spaces and length at least. Use the strings
180            directly in the buffer, add pointers to them in place.
181         */
182         char *this_md5;
183         char *this_filename;
184         int this_error = 0;
185
186         if (read > (BASE64_CKSUM_BYTES + 2)
187             && line[BASE64_CKSUM_BYTES] == ' '
188             && line[BASE64_CKSUM_BYTES + 1] == ' ')
189         {
190             line[BASE64_CKSUM_BYTES] = 0;
191             this_md5 = line;
192             this_filename = &line[BASE64_CKSUM_BYTES + 2];
193             if (line[read - 1] == '\n')
194                 line[--read] = '\0';
195             
196             this_error = md5_file(this_filename, base64_md5, verbose);
197             if (this_error)
198             {
199                 fprintf(stderr, "Failed to read %s, error %d (%s)\n",
200                         this_filename, this_error, strerror(errno));
201             }
202             else
203             {
204                 if (strcmp(base64_md5, this_md5))
205                 {
206                     if (verbose > 0)
207                         fprintf(stderr, "FAILED: %s\n", this_filename);
208                     this_error++;
209                 }
210                 else
211                 {
212                     if (verbose > 1)
213                         fprintf(stderr, "OK: %s\n", this_filename);
214                 }
215             }                
216         }
217         else
218         {
219             printf("ignoring malformed line %s\n", line);
220         }
221         error += this_error;
222     }
223
224     if (file != stdin)
225         fclose(file);
226
227     return error;
228 }
229
230 int main(int argc, char **argv)
231 {
232     int i = 0;
233     char base64_md5[BASE64_CKSUM_BYTES];
234     enum mode_e mode = MODE_CALC;
235     int verbose = 1;
236     int c = -1;
237     int error = 0;
238
239     while(1)
240     {
241         c = getopt(argc, argv, "cv");
242         if (-1 == c)
243             break;
244
245         switch(c)
246         {
247             case 'v':
248                 verbose++;
249                 break;
250             case 'c':
251                 mode = MODE_CHECK;
252                 break;
253         }
254     }
255
256     if (mode == MODE_CALC)
257     {
258         if (argc == optind)
259         {
260             if (!md5_file("-", base64_md5, verbose))
261                 printf("%s  %s\n", base64_md5, "-");
262         }
263         else
264         {
265             for (i = optind; i < argc; i++)
266             {
267                 if (!md5_file(argv[i], base64_md5, verbose))
268                     printf("%s  %s\n", base64_md5, argv[i]);
269                 fflush(stdout);
270             }
271         }
272     }
273     else
274     {
275         if (argc == optind)
276         {
277             error += md5_check("-", verbose);
278         }
279         else
280         {
281             for (i = optind; i < argc; i++)
282                 error += md5_check(argv[i], verbose);
283         }
284     }
285     
286     return error;
287 }
288