Use the new DB cache
[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 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 "md5.h"
21 #include "jigdb.h"
22
23 #define BUF_SIZE 65536
24
25 #ifndef MIN
26 #define MIN(x,y)        ( ((x) < (y)) ? (x) : (y))
27 #endif
28
29 JIGDB *database = NULL;
30
31 static int check_cache(char *filename, struct stat *sb, char **base64_md5)
32 {
33     int error = 0;
34     db_entry_t *entry;
35     
36     error = db_lookup_by_name(database, filename, &entry);
37     if (!error)
38     {
39         if ( (sb->st_mtime <= entry->mtime) &&
40              (sb->st_size == entry->file_size) )
41             /* We have a cache entry already; simply return
42              * the cached sum */
43         {
44             *base64_md5 = entry->md5;
45             return 1;
46         }
47         else
48         {
49             /* We have an entry for this file, but the mtime or size
50              * has changed. Delete the old entry and replace it later
51              * on */
52             error = db_delete(database, entry->md5);
53             if (error)
54                 printf("check_cache: unable to delete old entry for file %s\n", filename);
55         }
56     }
57     return 0;
58 }
59
60 static unsigned long long calculate_md5(char *filename, FILE *file, char **base64_md5)
61 {
62     char buf[BUF_SIZE];
63     unsigned char file_md5[16] = {0};
64     int done = 0;
65     struct mk_MD5Context file_context;
66     unsigned long long bytes_read = 0;
67     
68     mk_MD5Init(&file_context);
69     while (!done)
70     {
71         int used = 0;
72         memset(buf, 0, BUF_SIZE);
73
74         used = fread(buf, 1, BUF_SIZE, file);
75         bytes_read += used;
76         if (used)
77             mk_MD5Update(&file_context, (unsigned char *)buf, used);
78         else
79         {
80             if (ferror(file) && (EISDIR != errno))
81                 fprintf(stderr, "Unable to read from file %s; error %d\n",
82                         filename, errno);
83             break;
84         }
85     }    
86     mk_MD5Final(file_md5, &file_context);
87     *base64_md5 = base64_dump(file_md5, 16);
88     return bytes_read;
89 }
90
91 static int md5_file(char *filename)
92 {
93     FILE *file = NULL;
94     char *base64_md5 = NULL;
95     unsigned long long bytes_read = 0;
96     db_entry_t entry;
97     struct stat sb;
98     int found_in_db = 0;
99     int error = 0;
100     char buf[PATH_MAX];
101     char *fullpath = NULL;
102
103     /* Check if we're reading from stdin */
104     if (!strcmp("-", filename))
105     {
106         (void)calculate_md5("<STDIN>", stdin, &base64_md5);
107         printf("%s\n", base64_md5);
108         fflush(stdout);
109         return 0;
110     }
111
112     /* Make an absolute pathname if necessary */
113     if (filename[0] == '/')
114         fullpath = filename;
115     else
116     {
117         size_t wdlen = 0;
118         if (buf != getcwd(buf, sizeof(buf)))
119         {
120             fprintf(stderr, "md5_file: Unable to get CWD!; giving up on file %s, error %d\n",
121                     filename, errno);
122             return errno;
123         }
124         wdlen = strlen(buf);
125         strcpy(buf + wdlen, "/");
126         strcpy(buf + wdlen + 1, filename);
127         fullpath = buf;
128     }
129
130     /* Check the DB to see if we already have a checksum for this file */
131     error = stat(fullpath, &sb);
132     if (error)
133     {
134         fprintf(stderr, "md5_file: Unable to stat file %s, error %d\n", fullpath, errno);
135         return errno;
136     }
137     found_in_db = check_cache(fullpath, &sb, &base64_md5);
138     if (!found_in_db)
139     {
140         file = fopen(fullpath, "rb");
141         if (!file)
142         {
143             switch (errno)
144             {
145                 case EACCES:
146                 case EISDIR:
147                     break;
148                 default:
149                     fprintf(stderr, "Unable to open file %s; error %d\n", fullpath, errno);
150                     break;
151             }
152             return errno;
153         }
154         bytes_read = calculate_md5(fullpath, file, &base64_md5);
155         fclose(file);
156         memset(&entry, 0, sizeof(entry));
157         strncpy(&entry.md5[0], base64_md5, sizeof(entry.md5));
158         entry.type = FT_LOCAL;
159         entry.mtime = sb.st_mtime;
160         entry.age = UINT_MAX - time(NULL);
161         entry.file_size = bytes_read;
162         strncpy(&entry.filename[0], fullpath, sizeof(entry.filename));
163         error = db_store(database, &entry);
164         if (error)
165             fprintf(stderr, "Unable to write database entry; error %d\n", error);
166     }
167
168     printf("%s  %s\n", base64_md5, fullpath);
169     fflush(stdout);
170     return 0;
171 }
172
173 /* Walk through the database deleting entries more than <n> seconds old */
174 static void jigsum_db_cleanup(int delay)
175 {
176     time_t delete_time = UINT_MAX - time(NULL);
177     int error = 0;
178     db_entry_t *entry = NULL;
179
180     delete_time += delay;
181
182     printf("Time now %X; deleting records older than %X\n",
183            UINT_MAX - (unsigned int)time(NULL), (unsigned int)delete_time);
184     while (!error)
185     {
186         error = db_lookup_by_age(database, delete_time, &entry);
187         if (error)
188         {
189             printf("jigsum_db_cleanup: error %d from db_lookup_by_age call\n", error);
190             break;
191         }
192         printf("jigsum_db_cleanup: deleting entry %s (time %X)\n",
193                entry->filename, (unsigned int)entry->age);
194         error = db_delete(database, entry->md5);
195         if (error)
196         {
197             printf("jigsum_db_cleanup: error %d from delete call\n", error);
198             break;
199         }
200     }
201 }
202
203 int main(int argc, char **argv)
204 {
205     int i = 0;
206
207     database = db_open();
208     if (!database)
209     {
210         fprintf(stderr, "Unable to open database, error %d\n", errno);
211         return errno;
212     }                
213
214     /* Clear out old records */
215     jigsum_db_cleanup(20);
216     
217     for (i = 1; i < argc; i++)
218         (void) md5_file(argv[i]);
219
220     db_dump(database);
221
222     db_close(database);
223
224     return 0;
225 }
226