Fixed up base64_dump() users and interfaces to fix memory leaks
[jigit.git] / jigdb-sql.c
1 #include <sqlite3.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <errno.h>
6 #include "jigdb.h"
7
8 typedef struct
9 {
10     sqlite3 *db;
11 } db_state_t;
12
13 enum result_type
14 {
15     RES_TEMPLATE,
16     RES_BLOCK,
17     RES_FILE,
18     RES_COMPRESSED
19 };
20
21 struct results
22 {
23     struct results *next;
24     struct results *prev;
25     enum result_type type;
26     union
27     {
28         db_template_entry_t template;
29         db_block_entry_t block;
30         db_file_entry_t file;
31         db_compressed_entry_t compressed;
32     } data;
33 };
34
35 struct results *res_head = NULL;
36 struct results *res_current = NULL;
37 struct results *res_tail = NULL;
38
39 char sql_command[2 * PATH_MAX];
40
41 static int db_create_templates_table(db_state_t *dbp)
42 {
43     int error = 0;
44     char *open_error;
45     
46     /* Delete the table and create new */
47     error = sqlite3_exec(dbp->db, "DROP TABLE templates;", NULL, NULL, NULL);
48     sprintf(sql_command, "CREATE TABLE templates ("
49             "template_size INTEGER,"
50             "image_size INTEGER,"
51             "template_mtime INTEGER,"
52             "template_name VARCHAR(%d),"
53             "template_md5 VARCHAR(32),"
54             "image_md5 VARCHAR(32));", PATH_MAX);
55     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
56     if (error)
57     {
58         fprintf(stderr, "db_create_templates_table: got error %d (%s) from create\n", error, open_error);
59         if (open_error)
60             sqlite3_free(open_error);
61         return error;
62     }
63     return 0;
64 }
65
66 static int db_create_blocks_table(db_state_t *dbp)
67 {
68     int error = 0;
69     char *open_error;
70     
71     /* Delete the table and create new */
72     error = sqlite3_exec(dbp->db, "DROP TABLE blocks;", NULL, NULL, NULL);
73     sprintf(sql_command, "CREATE TABLE blocks ("
74             "image_offset INTEGER,"
75             "size INTEGER,"
76             "uncomp_offset INTEGER,"
77             "type INTEGER,"
78             "template_id VARCHAR(32),"
79             "md5 VARCHAR(32));");
80     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
81     if (error)
82     {
83         fprintf(stderr, "db_create_blocks_table: got error %d (%s) from create\n", error, open_error);
84         if (open_error)
85             sqlite3_free(open_error);
86         return error;
87     }
88     /* Create indices */
89     sprintf(sql_command, "CREATE INDEX blocks_offset ON blocks (uncomp_offset);");
90     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
91     if (error)
92     {
93         fprintf(stderr, "db_create_blocks_table: got error %d (%s) from create index\n", error, open_error);
94         if (open_error)
95             sqlite3_free(open_error);
96         return error;
97     }                                 
98     sprintf(sql_command, "CREATE INDEX blocks_template ON blocks (template_id);");
99     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
100     if (error)
101     {
102         fprintf(stderr, "db_create_blocks_table: got error %d (%s) from create index\n", error, open_error);
103         if (open_error)
104             sqlite3_free(open_error);
105         return error;
106     }
107
108     return 0;
109 }
110
111 static int db_create_files_table(db_state_t *dbp)
112 {
113     int error = 0;
114     char *open_error;
115     
116     /* We can't access the table. Delete it and create new */
117     error = sqlite3_exec(dbp->db, "DROP TABLE files;", NULL, NULL, NULL);
118     sprintf(sql_command, "CREATE TABLE files ("
119             "size INTEGER,"
120             "mtime INTEGER,"
121             "time_added INTEGER,"
122             "filetype INTEGER,"
123             "md5 VARCHAR(32),"
124             "filename VARCHAR(%d),"
125             "extra VARCHAR(%d));", PATH_MAX, PATH_MAX);
126     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
127     if (error)
128     {
129         fprintf(stderr, "db_create_files_table: got error %d (%s) from create\n", error, open_error);
130         if (open_error)
131             sqlite3_free(open_error);
132         return error;
133     }
134     /* Create indices */
135     sprintf(sql_command, "CREATE INDEX files_md5 ON files (md5);");
136     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
137     if (error)
138     {
139         fprintf(stderr, "db_create_files_table: got error %d (%s) from create index\n", error, open_error);
140         if (open_error)
141             sqlite3_free(open_error);
142         return error;
143     }                                 
144     sprintf(sql_command, "CREATE INDEX files_name ON files (filename);");
145     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
146     if (error)
147     {
148         fprintf(stderr, "db_create_files_table: got error %d (%s) from create index\n", error, open_error);
149         if (open_error)
150             sqlite3_free(open_error);
151         return error;
152     }
153     sprintf(sql_command, "CREATE INDEX files_time_added ON files (time_added);");
154     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
155     if (error)
156     {
157         fprintf(stderr, "db_create_files_table: got error %d (%s) from create index\n", error, open_error);
158         if (open_error)
159             sqlite3_free(open_error);
160         return error;
161     }
162
163     return 0;
164 }
165
166 static int db_create_compressed_table(db_state_t *dbp)
167 {
168     int error = 0;
169     char *open_error;
170     
171     /* Delete the table and create new */
172     error = sqlite3_exec(dbp->db, "DROP TABLE compressed;", NULL, NULL, NULL);
173     sprintf(sql_command, "CREATE TABLE compressed ("
174             "comp_offset INTEGER,"
175             "uncomp_offset INTEGER,"
176             "comp_size INTEGER,"
177             "uncomp_size INTEGER,"
178             "comp_type INTEGER,"
179             "template_id VARCHAR(32));");
180     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
181     if (error)
182     {
183         fprintf(stderr, "db_create_compressed_table: got error %d (%s) from create\n", error, open_error);
184         if (open_error)
185             sqlite3_free(open_error);
186         return error;
187     }
188     /* Create indices */
189     sprintf(sql_command, "CREATE INDEX compressed_offset ON compressed (uncomp_offset);");
190     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
191     if (error)
192     {
193         fprintf(stderr, "db_create_compressed_table: got error %d (%s) from create index\n", error, open_error);
194         if (open_error)
195             sqlite3_free(open_error);
196         return error;
197     }                                 
198     sprintf(sql_command, "CREATE INDEX compressed_template ON compressed (template_id);");
199     error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
200     if (error)
201     {
202         fprintf(stderr, "db_create_compressed_table: got error %d (%s) from create index\n", error, open_error);
203         if (open_error)
204             sqlite3_free(open_error);
205         return error;
206     }
207
208     return 0;
209 }
210
211 JIGDB *db_open(char *db_name)
212 {
213     db_state_t *dbp = NULL;
214     int error = 0;            /* function return value */
215
216     /* Allocate state structure */
217     dbp = calloc(1, sizeof(*dbp));
218     if (dbp)
219     {
220         error = sqlite3_open(db_name, &dbp->db);
221         if (error)
222         {
223             fprintf(stderr, "Unable to open sqlite file %s: error %d\n", db_name, error);
224             errno = error;
225             return NULL;
226         }
227         
228         /* We have a database pointer open. Do we need to init the
229          * tables? Try to grab the first row of the templates table and
230          * see if we get an error. There has to be a better way than
231          * this! */
232         error = sqlite3_exec(dbp->db, "SELECT COUNT(*) FROM templates;", NULL, NULL, NULL);
233         if (SQLITE_ERROR == error)
234         {
235             /* No table found, so create new */
236             /* First, the template table */
237             error = db_create_templates_table(dbp);
238             if (error)
239             {
240                 sqlite3_close(dbp->db);
241                 errno = error;
242                 return NULL;
243             }
244
245             /* 2. The blocks table */
246             error = db_create_blocks_table(dbp);
247             if (error)
248             {
249                 sqlite3_close(dbp->db);
250                 errno = error;
251                 return NULL;
252             }
253
254             /* 3. The files table */
255             error = db_create_files_table(dbp);
256             if (error)
257             {
258                 sqlite3_close(dbp->db);
259                 errno = error;
260                 return NULL;
261             }
262
263             /* 4. The compressed blocks table */
264             error = db_create_compressed_table(dbp);
265             if (error)
266             {
267                 sqlite3_close(dbp->db);
268                 errno = error;
269                 return NULL;
270             }
271         }
272     }
273     
274     return dbp;
275 }
276
277 int db_close(JIGDB *dbp)
278 {
279     db_state_t *state = dbp;
280     /* When we're done with the database, close it. */
281     if (state->db)
282         sqlite3_close(state->db);
283     free(state);
284     return 0;
285 }
286
287 /* Delete ALL the template, block and compressed entries for a
288  * specified template file */
289 int db_delete_template_cache(JIGDB *dbp, char *template_name)
290 {
291     int error = 0;
292     db_state_t *state = dbp;
293     char *open_error;
294     unsigned char *template_id = NULL;
295     db_template_entry_t *template = NULL;
296     
297     error = db_lookup_template_by_path(dbp, template_name, &template);
298     if (!error)
299     {
300         template_id = template->template_md5;
301
302         sprintf(sql_command, "DELETE FROM blocks WHERE template_id == '%s';", template_id);
303         error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
304         if (error)
305             fprintf(stderr, "db_delete_template_cache: Failed to delete block entries, error %d (%s)\n", error, open_error);
306         else
307         {
308             sprintf(sql_command, "DELETE FROM compressed WHERE template_id == '%s';", template_id);
309             error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
310             if (error)
311                 fprintf(stderr, "db_delete_template_cache: Failed to delete compressed entries, error %d (%s)\n", error, open_error);
312             else
313             {
314                 sprintf(sql_command, "DELETE FROM templates WHERE template_id == '%s';", template_id);
315                 error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
316                 if (error)
317                     fprintf(stderr, "db_delete_template_cache: Failed to delete template entry, error %d (%s)\n", error, open_error);
318             }
319         }
320     }
321     return error;
322 }
323
324 /* Does nothing at the moment... */
325 int db_dump(JIGDB *dbp)
326 {
327     int error = 0;
328 /*    int num_records = 0;
329     db_file_entry_t *entry = NULL;
330     db_state_t *state = dbp; */
331
332     return error;
333 }
334
335 static void free_results(void)
336 {
337     struct results *entry = res_head;
338     struct results *current = res_head;
339     
340     while(entry)
341     {
342         entry = entry->next;
343         free(current);
344         current = entry;
345     }
346     res_head = NULL;
347     res_current = NULL;
348     res_tail = NULL;
349 }
350
351 static int results_callback(void *pArg, int argc, char **argv, char **columnNames)
352 {
353     struct results *entry = calloc(1, sizeof (*entry));
354     enum result_type *type = pArg;
355     
356     if (res_tail)
357         res_tail->next = entry;
358     if (!res_head)
359         res_head = entry;
360
361     entry->prev = res_tail;
362     res_tail = entry;
363
364     switch (*type)
365     {
366         case RES_TEMPLATE:
367             if (argv[0])
368                 entry->data.template.template_size = strtoull(argv[0], NULL, 10);
369             if (argv[1])
370                 entry->data.template.image_size = strtoull(argv[1], NULL, 10);
371             if (argv[2])
372                 entry->data.template.template_mtime = strtoul(argv[2], NULL, 10);
373             if (argv[3])
374                 strncpy(entry->data.template.template_name, argv[3],
375                         sizeof(entry->data.template.template_name));
376             if (argv[4])
377                 strncpy(entry->data.template.template_md5, argv[4],
378                         sizeof(entry->data.template.template_md5));
379             if (argv[5])
380                 strncpy(entry->data.template.image_md5, argv[5],
381                         sizeof(entry->data.template.image_md5));
382             break;
383         case RES_BLOCK:
384             if (argv[0])
385                 entry->data.block.image_offset = strtoull(argv[0], NULL, 10);
386             if (argv[1])
387                 entry->data.block.size = strtoull(argv[1], NULL, 10);
388             if (argv[2])
389                 entry->data.block.uncomp_offset = strtoull(argv[2], NULL, 10);
390             if (argv[3])
391                 entry->data.block.type = strtoul(argv[3], NULL, 10);
392             if (argv[4])
393                 strncpy(entry->data.block.template_id, argv[4], sizeof(entry->data.block.template_id));
394             if (argv[5])
395                 strncpy(entry->data.block.md5, argv[5], sizeof(entry->data.block.md5));
396             break;
397         case RES_FILE:
398             if (argv[0])
399                 entry->data.file.file_size = strtoull(argv[0], NULL, 10);
400             if (argv[1])
401                 entry->data.file.mtime = strtoul(argv[1], NULL, 10);
402             if (argv[2])
403                 entry->data.file.time_added = strtoul(argv[2], NULL, 10);
404             if (argv[3])
405                 entry->data.file.type = strtol(argv[3], NULL, 10);
406             if (argv[4])
407                 strncpy(entry->data.file.md5, argv[4], sizeof(entry->data.file.md5));
408             if (argv[5])
409                 strncpy(entry->data.file.filename, argv[5], sizeof(entry->data.file.filename));
410             if (argv[6])
411                 strncpy(entry->data.file.extra, argv[6], sizeof(entry->data.file.extra));
412             break;
413         case RES_COMPRESSED:
414             if (argv[0])
415                 entry->data.compressed.comp_offset = strtoull(argv[0], NULL, 10);
416             if (argv[1])
417                 entry->data.compressed.uncomp_offset = strtoull(argv[1], NULL, 10);
418             if (argv[2])
419                 entry->data.compressed.comp_size = strtoull(argv[2], NULL, 10);
420             if (argv[3])
421                 entry->data.compressed.uncomp_size = strtoull(argv[3], NULL, 10);
422             if (argv[4])
423                 strncpy(entry->data.compressed.template_id, argv[4], sizeof(entry->data.compressed.template_id));
424             break;
425     }
426     
427     return 0;
428 }
429
430 int db_store_template(JIGDB *dbp, db_template_entry_t *entry)
431 {
432     int error = 0;
433     db_state_t *state = dbp;
434     char *open_error;
435
436     sprintf(sql_command, "INSERT INTO templates VALUES(%lld,%lld,%ld,'%s','%s','%s');",
437             entry->template_size, entry->image_size, entry->template_mtime,
438             entry->template_name, entry->template_md5, entry->image_md5);
439     error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
440     if (error)
441     {
442         fprintf(stderr, "db_store_template: Failed to write entry, error %d (%s)\n", error, open_error);
443         if (open_error)
444             sqlite3_free(open_error);
445         return error;
446     }
447     return error;
448 }
449     
450 int db_lookup_template_by_path(JIGDB *dbp, char *template_name, db_template_entry_t **out)
451 {
452     int error = 0;
453     db_state_t *state = dbp;
454     char *open_error;
455     int result_type = RES_TEMPLATE;
456
457     free_results();
458
459     sprintf(sql_command,
460             "SELECT * FROM templates WHERE template_name == '%s';",
461             template_name);
462     error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
463     if (error)
464     {
465         fprintf(stderr, "db_lookup_template_by_path: Failed to lookup, error %d (%s)\n", error, open_error);
466         return error;
467     }
468
469     res_current = res_head;
470     if (res_current)
471     {
472         *out = &res_current->data.template;
473         res_current = res_current->next;
474     }
475     else
476         error = ENOENT;
477
478     return error;
479 }    
480
481 int db_store_block(JIGDB *dbp, db_block_entry_t *entry)
482 {
483     int error = 0;
484     db_state_t *state = dbp;
485     char *open_error;
486
487     sprintf(sql_command, "INSERT INTO blocks VALUES(%lld,%lld,%lld,%d,'%s','%s');",
488             entry->image_offset, entry->size, entry->uncomp_offset, entry->type,
489             entry->template_id, entry->md5);
490     error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
491     if (error)
492     {
493         fprintf(stderr, "db_store_block: Failed to write entry, error %d (%s)\n", error, open_error);
494         if (open_error)
495             sqlite3_free(open_error);
496         return error;
497     }
498     return error;
499 }
500     
501 int db_lookup_block_by_offset(JIGDB *dbp, unsigned long long image_offset,
502                               unsigned char *template_id, db_block_entry_t **out)
503 {
504     int error = 0;
505     db_state_t *state = dbp;
506     char *open_error;
507     int result_type = RES_BLOCK;
508
509     free_results();
510
511     sprintf(sql_command,
512             "SELECT * FROM blocks WHERE template_id == '%s' "
513             "AND image_offset <= %lld "
514             "AND (image_offset + size) > %lld;", template_id, image_offset, image_offset);
515     error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
516     if (error)
517     {
518         fprintf(stderr, "db_lookup_block_by_offset: Failed to lookup, error %d (%s)\n", error, open_error);
519         return error;
520     }
521
522     res_current = res_head;
523     if (res_current)
524     {
525         *out = &res_current->data.block;
526         res_current = res_current->next;
527     }
528     else
529         error = ENOENT;
530
531     return error;
532 }
533
534 int db_store_file(JIGDB *dbp, db_file_entry_t *entry)
535 {
536     int error = 0;
537     db_state_t *state = dbp;
538     char *open_error;
539     
540     sprintf(sql_command, "INSERT INTO files VALUES(%lld,%ld,%ld,%d,'%s','%s','%s');",
541             entry->file_size,
542             entry->mtime,
543             entry->time_added,
544             entry->type,
545             entry->md5,
546             entry->filename,
547             entry->extra);
548     error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
549     if (error)
550     {
551         fprintf(stderr, "db_store_file: Failed to write entry, error %d (%s)\n", error, open_error);
552         if (open_error)
553             sqlite3_free(open_error);
554         return error;
555     }
556     return error;
557 }
558
559 int db_lookup_file_by_md5(JIGDB *dbp, char *md5, db_file_entry_t **out)
560 {
561     int error = 0;
562     db_state_t *state = dbp;
563     char *open_error;
564     int result_type = RES_FILE;
565
566     free_results();
567
568     sprintf(sql_command, "SELECT * FROM files WHERE md5 == '%s' ORDER BY filetype ASC;", md5);
569     error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
570     if (error)
571     {
572         fprintf(stderr, "db_lookup_file_by_md5: Failed to lookup, error %d (%s)\n", error, open_error);
573         return error;
574     }
575
576     res_current = res_head;
577     if (res_current)
578     {
579         *out = &res_current->data.file;
580         res_current = res_current->next;
581     }
582     else
583         error = ENOENT;
584
585     return error;
586 }
587
588 int db_lookup_file_by_name(JIGDB *dbp, char *filename, db_file_entry_t **out)
589 {
590     int error = 0;
591     db_state_t *state = dbp;
592     char *open_error;
593     int result_type = RES_FILE;
594
595     free_results();
596
597     sprintf(sql_command, "SELECT * FROM files WHERE filename == '%s';", filename);
598     error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
599     if (error)
600     {
601         fprintf(stderr, "db_lookup_file_by_name: Failed to lookup, error %d (%s)\n", error, open_error);
602         return error;
603     }
604
605     res_current = res_head;
606     if (res_current)
607     {
608         *out = &res_current->data.file;
609         res_current = res_current->next;
610     }
611     else
612         error = ENOENT;
613
614     return error;
615 }
616
617 int db_delete_file_by_name(JIGDB *dbp, char *md5, enum filetype type, char *filename)
618 {
619     int error = 0;
620     db_state_t *state = dbp;
621     char *open_error;
622
623     sprintf(sql_command, "DELETE FROM files WHERE md5 == '%s' AND filetype == '%d' AND filename == '%s';", md5, type, filename);
624     error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
625     if (error)
626         fprintf(stderr, "db_delete_file: Failed to delete, error %d (%s)\n", error, open_error);
627
628     return error;
629 }
630
631 /* Delete all file records older than the specified date */
632 int db_delete_files_by_age(JIGDB *dbp, time_t date)
633 {
634     int error = 0;
635     db_state_t *state = dbp;
636     char *open_error;
637
638     sprintf(sql_command, "DELETE FROM files WHERE time_added < %ld", date);
639     error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
640     if (error)
641         fprintf(stderr, "db_delete_file: Failed to delete, error %d (%s)\n", error, open_error);
642
643     return error;
644 }
645
646 int db_store_compressed(JIGDB *dbp, db_compressed_entry_t *entry)
647 {
648     int error = 0;
649     db_state_t *state = dbp;
650     char *open_error;
651
652     sprintf(sql_command, "INSERT INTO compressed VALUES(%lld,%lld,%lld,%lld,%d,'%s');",
653             entry->comp_offset, entry->uncomp_offset, entry->comp_size,
654             entry->uncomp_size, entry->comp_type, entry->template_id);
655     error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
656     if (error)
657     {
658         fprintf(stderr, "db_store_compressed: Failed to write entry, error %d (%s)\n", error, open_error);
659         if (open_error)
660             sqlite3_free(open_error);
661         return error;
662     }
663     return error;
664 }
665
666 int db_lookup_compressed_by_offset(JIGDB *dbp, unsigned long uncomp_offset,
667                                    unsigned char *template_id, db_compressed_entry_t **out)
668 {
669     int error = 0;
670     db_state_t *state = dbp;
671     char *open_error;
672     int result_type = RES_COMPRESSED;
673
674     free_results();
675
676     sprintf(sql_command,
677             "SELECT * FROM compressed WHERE template_id == '%s' "
678             "AND uncomp_offset <= %ld "
679             "AND (uncomp_offset + size) > %ld;", template_id, uncomp_offset, uncomp_offset);
680     error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
681     if (error)
682     {
683         fprintf(stderr, "db_lookup_compressed_by_offset: Failed to lookup, error %d (%s)\n", error, open_error);
684         return error;
685     }
686
687     res_current = res_head;
688     if (res_current)
689     {
690         *out = &res_current->data.compressed;
691         res_current = res_current->next;
692     }
693     else
694         error = ENOENT;
695
696     return error;
697 }