cpdup(1): Convert to LibreSSL MD5 hash API.
[dragonfly.git] / bin / cpdup / md5.c
1 /*-
2  * MD5.C
3  *
4  * (c) Copyright 1997-1999 by Matthew Dillon and Dima Ruban.  Permission to
5  *     use and distribute based on the FreeBSD copyright.  Supplied as-is,
6  *     USE WITH EXTREME CAUTION.
7  */
8
9 #include "cpdup.h"
10
11 #ifdef WITH_LIBMD
12 #include <md5.h>
13 #else
14 #include <openssl/md5.h>
15 #endif
16
17 typedef struct MD5Node {
18     struct MD5Node *md_Next;
19     char *md_Name;
20     char *md_Code;
21     int md_Accessed;
22 } MD5Node;
23
24 static MD5Node *md5_lookup(const char *sfile);
25 static void md5_cache(const char *spath, int sdirlen);
26 static char *doMD5File(const char *filename, char *buf, int is_target);
27
28 static char *MD5SCache;         /* cache source directory name */
29 static MD5Node *MD5Base;
30 static int MD5SCacheDirLen;
31 static int MD5SCacheDirty;
32
33 void
34 md5_flush(void)
35 {
36     if (MD5SCacheDirty && MD5SCache && NotForRealOpt == 0) {
37         FILE *fo;
38
39         if ((fo = fopen(MD5SCache, "w")) != NULL) {
40             MD5Node *node;
41
42             for (node = MD5Base; node; node = node->md_Next) {
43                 if (node->md_Accessed && node->md_Code) {
44                     fprintf(fo, "%s %zu %s\n",
45                         node->md_Code,
46                         strlen(node->md_Name),
47                         node->md_Name
48                     );
49                 }
50             }
51             fclose(fo);
52         }
53     }
54
55     MD5SCacheDirty = 0;
56
57     if (MD5SCache) {
58         MD5Node *node;
59
60         while ((node = MD5Base) != NULL) {
61             MD5Base = node->md_Next;
62
63             if (node->md_Code)
64                 free(node->md_Code);
65             if (node->md_Name)
66                 free(node->md_Name);
67             free(node);
68         }
69         free(MD5SCache);
70         MD5SCache = NULL;
71     }
72 }
73
74 static void
75 md5_cache(const char *spath, int sdirlen)
76 {
77     FILE *fi;
78
79     /*
80      * Already cached
81      */
82
83     if (
84         MD5SCache &&
85         sdirlen == MD5SCacheDirLen &&
86         strncmp(spath, MD5SCache, sdirlen) == 0
87     ) {
88         return;
89     }
90
91     /*
92      * Different cache, flush old cache
93      */
94
95     if (MD5SCache != NULL)
96         md5_flush();
97
98     /*
99      * Create new cache
100      */
101
102     MD5SCacheDirLen = sdirlen;
103     MD5SCache = mprintf("%*.*s%s", sdirlen, sdirlen, spath, MD5CacheFile);
104
105     if ((fi = fopen(MD5SCache, "r")) != NULL) {
106         MD5Node **pnode = &MD5Base;
107         int c;
108
109         c = fgetc(fi);
110         while (c != EOF) {
111             MD5Node *node = *pnode = malloc(sizeof(MD5Node));
112             char *s;
113             int nlen;
114
115             nlen = 0;
116
117             if (pnode == NULL || node == NULL) {
118                 fprintf(stderr, "out of memory\n");
119                 exit(EXIT_FAILURE);
120             }
121
122             bzero(node, sizeof(MD5Node));
123             node->md_Code = fextract(fi, -1, &c, ' ');
124             node->md_Accessed = 1;
125             if ((s = fextract(fi, -1, &c, ' ')) != NULL) {
126                 nlen = strtol(s, NULL, 0);
127                 free(s);
128             }
129             /*
130              * extracting md_Name - name may contain embedded control
131              * characters.
132              */
133             CountSourceReadBytes += nlen+1;
134             node->md_Name = fextract(fi, nlen, &c, EOF);
135             if (c != '\n') {
136                 fprintf(stderr, "Error parsing MD5 Cache: %s (%c)\n", MD5SCache, c);
137                 while (c != EOF && c != '\n')
138                     c = fgetc(fi);
139             }
140             if (c != EOF)
141                 c = fgetc(fi);
142             pnode = &node->md_Next;
143         }
144         fclose(fi);
145     }
146 }
147
148 /*
149  * md5_lookup:  lookup/create md5 entry
150  */
151
152 static MD5Node *
153 md5_lookup(const char *sfile)
154 {
155     MD5Node **pnode;
156     MD5Node *node;
157
158     for (pnode = &MD5Base; (node = *pnode) != NULL; pnode = &node->md_Next) {
159         if (strcmp(sfile, node->md_Name) == 0) {
160             break;
161         }
162     }
163     if (node == NULL) {
164
165         if ((node = *pnode = malloc(sizeof(MD5Node))) == NULL) {
166                 fprintf(stderr,"out of memory\n");
167                 exit(EXIT_FAILURE);
168         }
169
170         bzero(node, sizeof(MD5Node));
171         node->md_Name = strdup(sfile);
172     }
173     node->md_Accessed = 1;
174     return(node);
175 }
176
177 /*
178  * md5_check:  check MD5 against file
179  *
180  *      Return -1 if check failed
181  *      Return 0  if check succeeded
182  *
183  * dpath can be NULL, in which case we are force-updating
184  * the source MD5.
185  */
186 int
187 md5_check(const char *spath, const char *dpath)
188 {
189     const char *sfile;
190     char *dcode;
191     int sdirlen;
192     int r;
193     MD5Node *node;
194
195     r = -1;
196
197     if ((sfile = strrchr(spath, '/')) != NULL)
198         ++sfile;
199     else
200         sfile = spath;
201     sdirlen = sfile - spath;
202
203     md5_cache(spath, sdirlen);
204
205     node = md5_lookup(sfile);
206
207     /*
208      * If dpath == NULL, we are force-updating the source .MD5* files
209      */
210
211     if (dpath == NULL) {
212         char *scode = doMD5File(spath, NULL, 0);
213
214         r = 0;
215         if (node->md_Code == NULL) {
216             r = -1;
217             node->md_Code = scode;
218             MD5SCacheDirty = 1;
219         } else if (strcmp(scode, node->md_Code) != 0) {
220             r = -1;
221             free(node->md_Code);
222             node->md_Code = scode;
223             MD5SCacheDirty = 1;
224         } else {
225             free(scode);
226         }
227         return(r);
228     }
229
230     /*
231      * Otherwise the .MD5* file is used as a cache.
232      */
233
234     if (node->md_Code == NULL) {
235         node->md_Code = doMD5File(spath, NULL, 0);
236         MD5SCacheDirty = 1;
237     }
238
239     dcode = doMD5File(dpath, NULL, 1);
240     if (dcode) {
241         if (strcmp(node->md_Code, dcode) == 0) {
242             r = 0;
243         } else {
244             char *scode = doMD5File(spath, NULL, 0);
245
246             if (strcmp(node->md_Code, scode) == 0) {
247                     free(scode);
248             } else {
249                     free(node->md_Code);
250                     node->md_Code = scode;
251                     MD5SCacheDirty = 1;
252                     if (strcmp(node->md_Code, dcode) == 0)
253                         r = 0;
254             }
255         }
256         free(dcode);
257     }
258     return(r);
259 }
260
261 #ifndef WITH_LIBMD
262 static char *
263 md5_file(const char *filename, char *buf)
264 {
265     unsigned char digest[MD5_DIGEST_LENGTH];
266     static const char hex[]="0123456789abcdef";
267     MD5_CTX ctx;
268     unsigned char buffer[4096];
269     struct stat st;
270     off_t size;
271     int fd, bytes, i;
272
273     fd = open(filename, O_RDONLY);
274     if (fd < 0)
275         return NULL;
276     if (fstat(fd, &st) < 0) {
277         bytes = -1;
278         goto err;
279     }
280
281     MD5_Init(&ctx);
282     size = st.st_size;
283     bytes = 0;
284     while (size > 0) {
285         if ((size_t)size > sizeof(buffer))
286              bytes = read(fd, buffer, sizeof(buffer));
287         else
288              bytes = read(fd, buffer, size);
289         if (bytes < 0)
290              break;
291         MD5_Update(&ctx, buffer, bytes);
292         size -= bytes;
293     }
294
295 err:
296     close(fd);
297     if (bytes < 0)
298         return NULL;
299
300     if (!buf)
301         buf = malloc(MD5_DIGEST_LENGTH * 2 + 1);
302     if (!buf)
303         return NULL;
304
305     MD5_Final(digest, &ctx);
306     for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
307         buf[2*i] = hex[digest[i] >> 4];
308         buf[2*i+1] = hex[digest[i] & 0x0f];
309     }
310     buf[MD5_DIGEST_LENGTH * 2] = '\0';
311
312     return buf;
313 }
314 #endif
315
316 char *
317 doMD5File(const char *filename, char *buf, int is_target)
318 {
319     if (SummaryOpt) {
320         struct stat st;
321         if (stat(filename, &st) == 0) {
322             uint64_t size = st.st_size;
323             if (is_target)
324                     CountTargetReadBytes += size;
325             else
326                     CountSourceReadBytes += size;
327         }
328     }
329 #ifdef WITH_LIBMD
330     return MD5File(filename, buf);
331 #else
332     return md5_file(filename, buf);
333 #endif
334 }
335