xref: /dragonfly/bin/cpdup/md5.c (revision df49ec1e)
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 
336