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