Updated package version
[lms] / lightmediascanner / src / lib / lightmediascanner_process.c
1 /**
2  * Copyright (C) 2007 by INdT
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #define _GNU_SOURCE
26 #include <sys/wait.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <dirent.h>
30 #include <signal.h>
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "lightmediascanner.h"
37 #include "lightmediascanner_private.h"
38 #include "lightmediascanner_db_private.h"
39
40 struct db {
41     sqlite3 *handle;
42     sqlite3_stmt *transaction_begin;
43     sqlite3_stmt *transaction_commit;
44     sqlite3_stmt *get_file_info;
45     sqlite3_stmt *insert_file_info;
46     sqlite3_stmt *update_file_info;
47     sqlite3_stmt *delete_file_info;
48     sqlite3_stmt *set_file_dtime;
49 };
50
51 /***********************************************************************
52  * Master-Slave communication.
53  ***********************************************************************/
54
55 static int
56 _master_send_path(const struct fds *master, int plen, int dlen, const char *p)
57 {
58     int lengths[2];
59
60     lengths[0] = plen;
61     lengths[1] = dlen;
62
63     if (write(master->w, lengths, sizeof(lengths)) < 0) {
64         perror("write");
65         return -1;
66     }
67
68     if (write(master->w, p, plen) < 0) {
69         perror("write");
70         return -1;
71     }
72
73     return 0;
74 }
75
76 static int
77 _master_send_finish(const struct fds *master)
78 {
79     const int lengths[2] = {-1, -1};
80
81     if (write(master->w, lengths, sizeof(lengths)) < 0) {
82         perror("write");
83         return -1;
84     }
85     return 0;
86 }
87
88 static int
89 _master_recv_reply(const struct fds *master, struct pollfd *pfd, int *reply, int timeout)
90 {
91     int r;
92
93     r = poll(pfd, 1, timeout);
94     if (r < 0) {
95         perror("poll");
96         return -1;
97     }
98
99     if (r == 0)
100         return 1;
101
102     if (read(master->r, reply, sizeof(*reply)) != sizeof(*reply)) {
103         perror("read");
104         return -2;
105     }
106
107     return 0;
108 }
109
110 static int
111 _slave_send_reply(const struct fds *slave, int reply)
112 {
113     if (write(slave->w, &reply, sizeof(reply)) == 0) {
114         perror("write");
115         return -1;
116     }
117     return 0;
118 }
119
120 static int
121 _slave_recv_path(const struct fds *slave, int *plen, int *dlen, char *path)
122 {
123     int lengths[2], r;
124
125     r = read(slave->r, lengths, sizeof(lengths));
126     if (r != sizeof(lengths)) {
127         perror("read");
128         return -1;
129     }
130     *plen = lengths[0];
131     *dlen = lengths[1];
132
133     if (*plen == -1)
134         return 0;
135
136     if (*plen > PATH_SIZE) {
137         fprintf(stderr, "ERROR: path too long (%d/%d)\n", *plen, PATH_SIZE);
138         return -2;
139     }
140
141     r = read(slave->r, path, *plen);
142     if (r != *plen) {
143         fprintf(stderr, "ERROR: could not read whole path %d/%d\n", r, *plen);
144         return -3;
145     }
146
147     path[*plen] = 0;
148     return 0;
149 }
150
151
152 /***********************************************************************
153  * Slave-side.
154  ***********************************************************************/
155
156 static int
157 _db_compile_all_stmts(struct db *db)
158 {
159     sqlite3 *handle;
160
161     handle = db->handle;
162     db->transaction_begin = lms_db_compile_stmt_begin_transaction(handle);
163     if (!db->transaction_begin)
164         return -1;
165
166     db->transaction_commit = lms_db_compile_stmt_end_transaction(handle);
167     if (!db->transaction_commit)
168         return -2;
169
170     db->get_file_info = lms_db_compile_stmt_get_file_info(handle);
171     if (!db->get_file_info)
172         return -4;
173
174     db->insert_file_info = lms_db_compile_stmt_insert_file_info(handle);
175     if (!db->insert_file_info)
176         return -5;
177
178     db->update_file_info = lms_db_compile_stmt_update_file_info(handle);
179     if (!db->update_file_info)
180         return -6;
181
182     db->delete_file_info = lms_db_compile_stmt_delete_file_info(handle);
183     if (!db->delete_file_info)
184         return -6;
185
186     db->set_file_dtime = lms_db_compile_stmt_set_file_dtime(handle);
187     if (!db->set_file_dtime)
188         return -7;
189
190     return 0;
191 }
192
193 static struct db *
194 _db_open(const char *db_path)
195 {
196     struct db *db;
197
198     db = calloc(1, sizeof(*db));
199     if (!db) {
200         perror("calloc");
201         return NULL;
202     }
203
204     if (sqlite3_open(db_path, &db->handle) != SQLITE_OK) {
205         fprintf(stderr, "ERROR: could not open DB \"%s\": %s\n",
206                 db_path, sqlite3_errmsg(db->handle));
207         goto error;
208     }
209
210     if (lms_db_create_core_tables_if_required(db->handle) != 0) {
211         fprintf(stderr, "ERROR: could not setup tables and indexes.\n");
212         goto error;
213     }
214
215     return db;
216
217   error:
218     sqlite3_close(db->handle);
219     free(db);
220     return NULL;
221 }
222
223 static int
224 _db_close(struct db *db)
225 {
226     if (db->transaction_begin)
227         lms_db_finalize_stmt(db->transaction_begin, "transaction_begin");
228
229     if (db->transaction_commit)
230         lms_db_finalize_stmt(db->transaction_commit, "transaction_commit");
231
232     if (db->get_file_info)
233         lms_db_finalize_stmt(db->get_file_info, "get_file_info");
234
235     if (db->insert_file_info)
236         lms_db_finalize_stmt(db->insert_file_info, "insert_file_info");
237
238     if (db->update_file_info)
239         lms_db_finalize_stmt(db->update_file_info, "update_file_info");
240
241     if (db->delete_file_info)
242         lms_db_finalize_stmt(db->delete_file_info, "delete_file_info");
243
244     if (db->set_file_dtime)
245         lms_db_finalize_stmt(db->set_file_dtime, "set_file_dtime");
246
247     if (sqlite3_close(db->handle) != SQLITE_OK) {
248         fprintf(stderr, "ERROR: clould not close DB: %s\n",
249                 sqlite3_errmsg(db->handle));
250         return -1;
251     }
252     free(db);
253
254     return 0;
255 }
256
257 static int
258 _retrieve_file_status(struct db *db, struct lms_file_info *finfo)
259 {
260     struct stat st;
261     int r;
262
263     if (stat(finfo->path, &st) != 0) {
264         perror("stat");
265         return -1;
266     }
267
268     r = lms_db_get_file_info(db->get_file_info, finfo);
269     if (r == 0) {
270         if (st.st_mtime <= finfo->mtime && finfo->size == st.st_size)
271             return 0;
272         else {
273             finfo->mtime = st.st_mtime;
274             finfo->size = st.st_size;
275             return 1;
276         }
277     } else if (r == 1) {
278         finfo->mtime = st.st_mtime;
279         finfo->size = st.st_size;
280         return 1;
281     } else
282         return -2;
283 }
284
285 static void
286 _ctxt_init(struct lms_context *ctxt, const lms_t *lms, sqlite3 *db)
287 {
288     ctxt->cs_conv = lms->cs_conv;
289     ctxt->db = db;
290 }
291
292 int
293 lms_parsers_setup(lms_t *lms, sqlite3 *db)
294 {
295     struct lms_context ctxt;
296     int i;
297
298     _ctxt_init(&ctxt, lms, db);
299
300     for (i = 0; i < lms->n_parsers; i++) {
301         lms_plugin_t *plugin;
302         int r;
303
304         plugin = lms->parsers[i].plugin;
305         r = plugin->setup(plugin, &ctxt);
306         if (r != 0) {
307             fprintf(stderr, "ERROR: parser \"%s\" failed to setup: %d.\n",
308                     plugin->name, r);
309             plugin->finish(plugin, &ctxt);
310             lms_parser_del_int(lms, i);
311             i--; /* cancel i++ */
312         }
313     }
314
315     return 0;
316 }
317
318 int
319 lms_parsers_start(lms_t *lms, sqlite3 *db)
320 {
321     struct lms_context ctxt;
322     int i;
323
324     _ctxt_init(&ctxt, lms, db);
325
326     for (i = 0; i < lms->n_parsers; i++) {
327         lms_plugin_t *plugin;
328         int r;
329
330         plugin = lms->parsers[i].plugin;
331         r = plugin->start(plugin, &ctxt);
332         if (r != 0) {
333             fprintf(stderr, "ERROR: parser \"%s\" failed to start: %d.\n",
334                     plugin->name, r);
335             plugin->finish(plugin, &ctxt);
336             lms_parser_del_int(lms, i);
337             i--; /* cancel i++ */
338         }
339     }
340
341     return 0;
342 }
343
344 int
345 lms_parsers_finish(lms_t *lms, sqlite3 *db)
346 {
347     struct lms_context ctxt;
348     int i;
349
350     _ctxt_init(&ctxt, lms, db);
351
352     for (i = 0; i < lms->n_parsers; i++) {
353         lms_plugin_t *plugin;
354         int r;
355
356         plugin = lms->parsers[i].plugin;
357         r = plugin->finish(plugin, &ctxt);
358         if (r != 0)
359             fprintf(stderr, "ERROR: parser \"%s\" failed to finish: %d.\n",
360                     plugin->name, r);
361     }
362
363     return 0;
364 }
365
366 int
367 lms_parsers_check_using(lms_t *lms, void **parser_match, struct lms_file_info *finfo)
368 {
369     int used, i;
370
371     used = 0;
372     for (i = 0; i < lms->n_parsers; i++) {
373         lms_plugin_t *plugin;
374         void *r;
375
376         plugin = lms->parsers[i].plugin;
377         r = plugin->match(plugin, finfo->path, finfo->path_len, finfo->base);
378         parser_match[i] = r;
379         if (r)
380             used = 1;
381     }
382
383     return used;
384 }
385
386 int
387 lms_parsers_run(lms_t *lms, sqlite3 *db, void **parser_match, struct lms_file_info *finfo)
388 {
389     struct lms_context ctxt;
390     int i, failed, available;
391
392     _ctxt_init(&ctxt, lms, db);
393
394     failed = 0;
395     available = 0;
396     for (i = 0; i < lms->n_parsers; i++) {
397         lms_plugin_t *plugin;
398
399         plugin = lms->parsers[i].plugin;
400         if (parser_match[i]) {
401             int r;
402
403             available++;
404             r = plugin->parse(plugin, &ctxt, finfo, parser_match[i]);
405             if (r != 0)
406                 failed++;
407         }
408     }
409
410     if (!failed)
411         return 0;
412     else if (failed == available)
413         return -1;
414     else
415         return 1; /* non critical */
416 }
417
418 static int
419 _slave_work(lms_t *lms, struct fds *fds)
420 {
421     int r, len, base, counter;
422     char path[PATH_SIZE];
423     void **parser_match;
424     struct db *db;
425
426     db = _db_open(lms->db_path);
427     if (!db)
428         return -1;
429
430     if (lms_parsers_setup(lms, db->handle) != 0) {
431         fprintf(stderr, "ERROR: could not setup parsers.\n");
432         r = -2;
433         goto end;
434     }
435
436     if (_db_compile_all_stmts(db) != 0) {
437         fprintf(stderr, "ERROR: could not compile statements.\n");
438         r = -3;
439         goto end;
440     }
441
442     if (lms_parsers_start(lms, db->handle) != 0) {
443         fprintf(stderr, "ERROR: could not start parsers.\n");
444         r = -4;
445         goto end;
446     }
447     if (lms->n_parsers < 1) {
448         fprintf(stderr, "ERROR: no parser could be started, exit.\n");
449         r = -5;
450         goto end;
451     }
452
453     parser_match = malloc(lms->n_parsers * sizeof(*parser_match));
454     if (!parser_match) {
455         perror("malloc");
456         r = -6;
457         goto end;
458     }
459
460     counter = 0;
461     lms_db_begin_transaction(db->transaction_begin);
462
463     while (((r = _slave_recv_path(fds, &len, &base, path)) == 0) && len > 0) {
464         struct lms_file_info finfo;
465         int used, r;
466
467         finfo.path = path;
468         finfo.path_len = len;
469         finfo.base = base;
470
471         r = _retrieve_file_status(db, &finfo);
472         if (r == 0) {
473             if (finfo.dtime) {
474                 finfo.dtime = 0;
475                 lms_db_set_file_dtime(db->set_file_dtime, &finfo);
476             }
477             goto inform_end;
478         } else if (r < 0) {
479             fprintf(stderr, "ERROR: could not detect file status.\n");
480             goto inform_end;
481         }
482
483         used = lms_parsers_check_using(lms, parser_match, &finfo);
484         if (!used)
485             goto inform_end;
486
487         finfo.dtime = 0;
488         if (finfo.id > 0)
489             r = lms_db_update_file_info(db->update_file_info, &finfo);
490         else
491             r = lms_db_insert_file_info(db->insert_file_info, &finfo);
492         if (r < 0) {
493             fprintf(stderr, "ERROR: could not register path in DB\n");
494             goto inform_end;
495         }
496
497         r = lms_parsers_run(lms, db->handle, parser_match, &finfo);
498         if (r < 0) {
499             fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
500                     getpid(), finfo.path);
501             lms_db_delete_file_info(db->delete_file_info, &finfo);
502         }
503
504       inform_end:
505         _slave_send_reply(fds, r);
506         counter++;
507         if (counter > lms->commit_interval) {
508             lms_db_end_transaction(db->transaction_commit);
509             lms_db_begin_transaction(db->transaction_begin);
510             counter = 0;
511         }
512     }
513
514     free(parser_match);
515     lms_db_end_transaction(db->transaction_commit);
516   end:
517     lms_parsers_finish(lms, db->handle);
518     _db_close(db);
519
520     return r;
521 }
522
523
524 /***********************************************************************
525  * Master-side.
526  ***********************************************************************/
527
528 static int
529 _consume_garbage(struct pollfd *pfd)
530 {
531     int r;
532
533     while ((r = poll(pfd, 1, 0)) > 0) {
534         if (pfd->revents & (POLLERR | POLLHUP | POLLNVAL))
535             return 0;
536         else if (pfd->revents & POLLIN) {
537             char c;
538
539             read(pfd->fd, &c, sizeof(c));
540         }
541     }
542
543     return r;
544 }
545
546 static int
547 _close_fds(struct fds *fds)
548 {
549     int r;
550
551     r = 0;
552     if (close(fds->r) != 0) {
553         r--;
554         perror("close");
555     }
556
557     if (close(fds->w) != 0) {
558         r--;
559         perror("close");
560     }
561
562     return r;
563 }
564
565 int
566 lms_close_pipes(struct pinfo *pinfo)
567 {
568     int r;
569
570     r = _close_fds(&pinfo->master);
571     r += _close_fds(&pinfo->slave);
572
573     return r;
574 }
575
576 int
577 lms_create_pipes(struct pinfo *pinfo)
578 {
579     int fds[2];
580
581     if (pipe(fds) != 0) {
582         perror("pipe");
583         return -1;
584     }
585     pinfo->master.r = fds[0];
586     pinfo->slave.w = fds[1];
587
588     if (pipe(fds) != 0) {
589         perror("pipe");
590         close(pinfo->master.r);
591         close(pinfo->slave.w);
592         return -1;
593     }
594     pinfo->slave.r = fds[0];
595     pinfo->master.w = fds[1];
596
597     pinfo->poll.fd = pinfo->master.r;
598     pinfo->poll.events = POLLIN;
599
600     return 0;
601 }
602
603 int
604 lms_create_slave(struct pinfo *pinfo, int (*work)(lms_t *lms, struct fds *fds))
605 {
606     int r;
607
608     pinfo->child = fork();
609     if (pinfo->child == -1) {
610         perror("fork");
611         return -1;
612     }
613
614     if (pinfo->child > 0)
615         return 0;
616
617     _close_fds(&pinfo->master);
618     nice(19);
619     r = work(pinfo->lms, &pinfo->slave);
620     lms_free(pinfo->lms);
621     _exit(r);
622     return r; /* shouldn't reach anyway... */
623 }
624
625 static int
626 _waitpid(pid_t pid)
627 {
628     int status;
629     pid_t r;
630
631     r = waitpid(pid, &status, 0);
632     if (r > -1)
633         return 0;
634     else
635         perror("waitpid");
636
637     return r;
638 }
639
640 int
641 lms_finish_slave(struct pinfo *pinfo, int (*finish)(const struct fds *fds))
642 {
643     int r;
644
645     if (pinfo->child <= 0)
646         return 0;
647
648     r = finish(&pinfo->master);
649     if (r == 0)
650         r = _waitpid(pinfo->child);
651     else {
652         r = kill(pinfo->child, SIGKILL);
653         if (r < 0)
654             perror("kill");
655         else
656             r =_waitpid(pinfo->child);
657     }
658     pinfo->child = 0;
659
660     return r;
661 }
662
663 int
664 lms_restart_slave(struct pinfo *pinfo, int (*work)(lms_t *lms, struct fds *fds))
665 {
666     int status;
667
668     if (waitpid(pinfo->child, &status, WNOHANG) > 0) {
669         if (WIFEXITED(status)) {
670             int code;
671
672             code = WEXITSTATUS(status);
673             if (code != 0) {
674                 fprintf(stderr, "ERROR: slave returned %d, exit.\n", code);
675                 pinfo->child = 0;
676                 return -1;
677             }
678         } else {
679             if (WIFSIGNALED(status)) {
680                 int code;
681
682                 code = WTERMSIG(status);
683                 fprintf(stderr, "ERROR: slave was terminated by signal %d.\n",
684                         code);
685             }
686             pinfo->child = 0;
687             return -1;
688         }
689     }
690
691     if (kill(pinfo->child, SIGKILL))
692         perror("kill");
693
694     if (waitpid(pinfo->child, &status, 0) < 0)
695         perror("waitpid");
696
697     _consume_garbage(&pinfo->poll);
698     return lms_create_slave(pinfo, work);
699 }
700
701 static int
702 _strcat(int base, char *path, const char *name)
703 {
704     int new_len, name_len;
705
706     name_len = strlen(name);
707     new_len = base + name_len;
708
709     if (new_len >= PATH_SIZE) {
710         path[base] = '\0';
711         fprintf(stderr,
712                 "ERROR: path concatenation too long %d of %d "
713                 "available: \"%s\" + \"%s\"\n", new_len, PATH_SIZE,
714                 path, name);
715         return -1;
716     }
717
718     memcpy(path + base, name, name_len + 1);
719
720     return new_len;
721 }
722
723 static int
724 _process_file(struct pinfo *pinfo, int base, char *path, const char *name)
725 {
726     int new_len, reply, r;
727
728     new_len = _strcat(base, path, name);
729     if (new_len < 0)
730         return -1;
731
732     if (_master_send_path(&pinfo->master, new_len, base, path) != 0)
733         return -2;
734
735     r = _master_recv_reply(&pinfo->master, &pinfo->poll, &reply,
736                            pinfo->lms->slave_timeout);
737     if (r < 0)
738         return -3;
739     else if (r == 1) {
740         fprintf(stderr, "ERROR: slave took too long, restart %d\n",
741                 pinfo->child);
742         if (lms_restart_slave(pinfo, _slave_work) != 0)
743             return -4;
744         return 1;
745     } else {
746         if (reply < 0) {
747             /* XXX callback library users to inform error. */
748             fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
749                     getpid(), path);
750             return (-reply) << 8;
751         } else
752             return reply;
753     }
754 }
755
756 static int
757 _process_dir(struct pinfo *pinfo, int base, char *path, const char *name)
758 {
759     DIR *dir;
760     struct dirent *de;
761     lms_t *lms;
762     int new_len, r;
763
764     new_len = _strcat(base, path, name);
765     if (new_len < 0)
766         return -1;
767     else if (new_len + 1 >= PATH_SIZE) {
768         fprintf(stderr, "ERROR: path too long\n");
769         return 2;
770     }
771
772     dir = opendir(path);
773     if (dir == NULL) {
774         perror("opendir");
775         return 3;
776     }
777
778     path[new_len] = '/';
779     new_len++;
780
781     r = 0;
782     lms = pinfo->lms;
783     while ((de = readdir(dir)) != NULL && !lms->stop_processing) {
784         if (de->d_name[0] == '.')
785             continue;
786         if (de->d_type == DT_REG) {
787             if (_process_file(pinfo, new_len, path, de->d_name) < 0) {
788                 fprintf(stderr,
789                         "ERROR: unrecoverable error parsing file, "
790                         "exit \"%s\".\n", path);
791                 path[new_len - 1] = '\0';
792                 r = -4;
793                 goto end;
794             }
795         } else if (de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) {
796             if (_process_dir(pinfo, new_len, path, de->d_name) < 0) {
797                 fprintf(stderr,
798                         "ERROR: unrecoverable error parsing dir, "
799                         "exit \"%s\".\n", path);
800                 path[new_len - 1] = '\0';
801                 r = -5;
802                 goto end;
803             }
804         }
805     }
806
807   end:
808     closedir(dir);
809     return r;
810 }
811
812 /**
813  * Process the given directory.
814  *
815  * This will add or update media found in the given directory or its children.
816  *
817  * @param lms previously allocated Light Media Scanner instance.
818  * @param top_path top directory to scan.
819  *
820  * @return On success 0 is returned.
821  */
822 int
823 lms_process(lms_t *lms, const char *top_path)
824 {
825     struct pinfo pinfo;
826     int r, len;
827     char path[PATH_SIZE], *bname;
828
829     if (!lms) {
830         r = -1;
831         goto end;
832     }
833
834     if (!top_path) {
835         r = -2;
836         goto end;
837     }
838
839     if (lms->is_processing) {
840         fprintf(stderr, "ERROR: is already processing.\n");
841         r = -3;
842         goto end;
843     }
844
845     if (!lms->parsers) {
846         fprintf(stderr, "ERROR: no plugins registered.\n");
847         r = -4;
848         goto end;
849     }
850
851     pinfo.lms = lms;
852
853     if (lms_create_pipes(&pinfo) != 0) {
854         r = -5;
855         goto end;
856     }
857
858     if (lms_create_slave(&pinfo, _slave_work) != 0) {
859         r = -6;
860         goto close_pipes;
861     }
862
863     if (realpath(top_path, path) == NULL) {
864         perror("realpath");
865         r = -7;
866         goto finish_slave;
867     }
868
869     /* search '/' backwards, split dirname and basename, note realpath usage */
870     len = strlen(path);
871     for (; len >= 0 && path[len] != '/'; len--);
872     len++;
873     bname = strdup(path + len);
874     if (bname == NULL) {
875         perror("strdup");
876         r = -8;
877         goto finish_slave;
878     }
879
880     lms->is_processing = 1;
881     lms->stop_processing = 0;
882     r = _process_dir(&pinfo, len, path, bname);
883     lms->is_processing = 0;
884     lms->stop_processing = 0;
885     free(bname);
886
887   finish_slave:
888     lms_finish_slave(&pinfo, _master_send_finish);
889   close_pipes:
890     lms_close_pipes(&pinfo);
891   end:
892     return r;
893 }
894
895 void
896 lms_stop_processing(lms_t *lms)
897 {
898     if (!lms)
899         return;
900     if (!lms->is_processing)
901         return;
902
903     lms->stop_processing = 1;
904 }