Fixed HTTP-client query-string handling bug.
[doldaconnect.git] / daemon / client.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <wchar.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <dirent.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <signal.h>
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 #include "client.h"
34 #include "conf.h"
35 #include "log.h"
36 #include "utils.h"
37 #include "module.h"
38 #include "net.h"
39 #include "sysevents.h"
40 #include <tiger.h>
41
42 struct scanstate
43 {
44     struct scanstate *next;
45     struct sharecache *node;
46     DIR *dd;
47 };
48
49 struct scanqueue
50 {
51     struct scanqueue *next;
52     struct scanstate *state;
53 };
54
55 static int conf_share(int argc, wchar_t **argv);
56 static void freecache(struct sharecache *node);
57 static void checkhashes(void);
58 static void writehashcache(int now);
59
60 static struct configvar myvars[] =
61 {
62     /** The default nick name to use. The nickname can also be
63      * specified for individual hubs, overriding this setting. */
64     {CONF_VAR_STRING, "defnick", {.str = L"DoldaConnect user"}},
65     /** When scanning shares, this bitmask is consulted for every
66      * regular file. Unless the file's mode has the bits specified by
67      * this mask set, it will not be shared. */
68     {CONF_VAR_INT, "scanfilemask", {.num = 0004}},
69     /** When scanning shares, this bitmask is consulted for every
70      * directory encountered. Unless the directory's mode has the bits
71      * specified by this mask set, it will be ignored and any files
72      * under it will not be shared. */
73     {CONF_VAR_INT, "scandirmask", {.num = 0005}},
74     /** The filename to use for the hash cache (see the FILES section
75      * for more information). */
76     {CONF_VAR_STRING, "hashcache", {.str = L"dc-hashcache"}},
77     /** Writes of the hash cache and file lists are delayed for an
78      * amount of time, in order to minimize the time spent on I/O wait
79      * while hashing many small files. This variable sets the amount
80      * of time, in seconds. */
81     {CONF_VAR_INT, "hashwritedelay", {.num = 300}},
82     /** The amount of time, in seconds, to wait before automatically
83      * rescanning the shared directories for changes. Set to zero (the
84      * default) to disable automatic rescanning. (Broken shares are
85      * always rescanned upon detection, regardless of this
86      * setting.) */
87     {CONF_VAR_INT, "rescandelay", {.num = 0}},
88     {CONF_VAR_END}
89 };
90
91 static struct configcmd mycmds[] = 
92 {
93     {"share", conf_share},
94     {NULL}
95 };
96
97 static struct scanstate *scanjob = NULL;
98 static struct scanqueue *scanqueue = NULL;
99 static struct sharepoint *shares = NULL;
100 static struct hashcache *hashcache = NULL;
101 static struct timer *hashwritetimer = NULL;
102 /* Set initially to -1, but changed to 0 the first time run() is
103  * called. This is to avoid forking a hash job before daemonizing,
104  * since that would make the daemon unable to wait() for the hash
105  * job. */
106 static pid_t hashjob = -1;
107 struct sharecache *shareroot = NULL;
108 static struct timer *scantimer = NULL;
109 int sharedfiles = 0;
110 unsigned long long sharesize = 0;
111 GCBCHAIN(sharechangecb, unsigned long long);
112
113 static int conf_share(int argc, wchar_t **argv)
114 {
115     struct sharepoint *share;
116     char *b;
117     
118     if(argc < 3)
119     {
120         flog(LOG_WARNING, "not enough arguments given for share command");
121         return(1);
122     }
123     if((b = icwcstombs(argv[2], NULL)) == NULL)
124     {
125         flog(LOG_WARNING, "could not convert wcs path (%ls) to current locale's charset: %s", argv[2], strerror(errno));
126         return(1);
127     }
128     for(share = shares; share != NULL; share = share->next)
129     {
130         if(!strcmp(share->path, b) && !wcscmp(share->name, argv[1]))
131         {
132             share->delete = 0;
133             free(b);
134             return(0);
135         }
136     }
137     share = smalloc(sizeof(*share));
138     share->path = b;
139     share->delete = 0;
140     share->name = swcsdup(argv[1]);
141     share->next = shares;
142     share->prev = NULL;
143     if(shares != NULL)
144         shares->prev = share;
145     shares = share;
146     return(0);
147 }
148
149 static void dumpsharecache(struct sharecache *node, int l)
150 {
151     int i;
152     
153     for(; node != NULL; node = node->next)
154     {
155         for(i = 0; i < l; i++)
156             putc('\t', stdout);
157         printf("%ls\n", node->name);
158         if(node->f.b.type == FILE_DIR)
159             dumpsharecache(node->child, l + 1);
160     }
161 }
162
163 struct hash *newhash(wchar_t *algo, size_t len, char *buf)
164 {
165     struct hash *ret;
166     
167     ret = smalloc(sizeof(*ret));
168     memset(ret, 0, sizeof(*ret));
169     ret->algo = swcsdup(algo);
170     ret->len = len;
171     ret->buf = memcpy(smalloc(len), buf, len);
172     return(ret);
173 }
174
175 void freehash(struct hash *hash)
176 {
177     free(hash->algo);
178     free(hash->buf);
179     free(hash);
180 }
181
182 struct hash *duphash(struct hash *hash)
183 {
184     return(newhash(hash->algo, hash->len, hash->buf));
185 }
186
187 struct hash *parsehash(wchar_t *text)
188 {
189     wchar_t *p;
190     char *mbsbuf, *decbuf;
191     size_t buflen;
192     struct hash *ret;
193     
194     if((p = wcschr(text, L':')) == NULL)
195         return(NULL);
196     *(p++) = L'\0';
197     if((mbsbuf = icwcstombs(p, "US-ASCII")) == NULL)
198         return(NULL);
199     decbuf = base64decode(mbsbuf, &buflen);
200     free(mbsbuf);
201     if(decbuf == NULL)
202         return(NULL);
203     ret = newhash(text, buflen, decbuf);
204     free(decbuf);
205     return(ret);
206 }
207
208 wchar_t *unparsehash(struct hash *hash)
209 {
210     static wchar_t *buf = NULL;
211     wchar_t *whbuf;
212     char *hbuf;
213     size_t bufsize, bufdata;
214     
215     if(buf != NULL)
216         free(buf);
217     buf = NULL;
218     bufsize = bufdata = 0;
219     hbuf = base64encode(hash->buf, hash->len);
220     if((whbuf = icmbstowcs(hbuf, "US-ASCII")) == NULL)
221     {
222         flog(LOG_CRIT, "bug! could not convert base64 from us-ascii: %s", strerror(errno));
223         abort();
224     }
225     free(hbuf);
226     bufcat(buf, hash->algo, wcslen(hash->algo));
227     addtobuf(buf, ':');
228     bufcat(buf, whbuf, wcslen(whbuf));
229     free(whbuf);
230     addtobuf(buf, 0);
231     return(buf);
232 }
233
234 int hashcmp(struct hash *h1, struct hash *h2)
235 {
236     if(wcscmp(h1->algo, h2->algo))
237         return(0);
238     if(h1->len != h2->len)
239         return(0);
240     if(memcmp(h1->buf, h2->buf, h1->len))
241         return(0);
242     return(1);
243 }
244
245 static struct hashcache *newhashcache(void)
246 {
247     struct hashcache *new;
248     
249     new = smalloc(sizeof(*new));
250     memset(new, 0, sizeof(*new));
251     new->next = hashcache;
252     new->prev = NULL;
253     if(hashcache != NULL)
254         hashcache->prev = new;
255     hashcache = new;
256     return(new);
257 }
258
259 static void freehashcache(struct hashcache *hc)
260 {
261     if(hc->next != NULL)
262         hc->next->prev = hc->prev;
263     if(hc->prev != NULL)
264         hc->prev->next = hc->next;
265     if(hc == hashcache)
266         hashcache = hc->next;
267     free(hc);
268 }
269
270 static struct hashcache *findhashcache(dev_t dev, ino_t inode)
271 {
272     struct hashcache *hc;
273     
274     for(hc = hashcache; hc != NULL; hc = hc->next)
275     {
276         if((hc->dev == dev) && (hc->inode == inode))
277             return(hc);
278     }
279     return(NULL);
280 }
281
282 static void readhashcache(void)
283 {
284     int i, wc, line;
285     char *hcname;
286     FILE *stream;
287     char linebuf[256];
288     char *p, *p2, *wv[32], *hash;
289     struct hashcache *hc;
290     size_t len;
291     
292     if((hcname = findfile(icswcstombs(confgetstr("cli", "hashcache"), NULL, NULL), NULL, 0)) == NULL)
293         return;
294     if((stream = fopen(hcname, "r")) == NULL)
295     {
296         flog(LOG_WARNING, "could not open hash cache %s: %s", hcname, strerror(errno));
297         free(hcname);
298         return;
299     }
300     free(hcname);
301     while(hashcache != NULL)
302         freehashcache(hashcache);
303     line = 0;
304     while(!feof(stream))
305     {
306         fgets(linebuf, sizeof(linebuf), stream);
307         line++;
308         for(p = linebuf; *p; p++)
309         {
310             if(*p == '\n')
311                 *p = ' ';
312         }
313         if(linebuf[0] == '#')
314             continue;
315         for(wc = 0, p = linebuf; (wc < 32) && ((p2 = strchr(p, ' ')) != NULL); p = p2 + 1)
316         {
317             if(p2 == p)
318                 continue;
319             *p2 = 0;
320             wv[wc++] = p;
321         }
322         if(wc < 3)
323             continue;
324         hc = newhashcache();
325         hc->dev = strtoll(wv[0], NULL, 10);
326         hc->inode = strtoll(wv[1], NULL, 10);
327         hc->mtime = strtoll(wv[2], NULL, 10);
328         for(i = 3; i < wc; i++)
329         {
330             if(!strcmp(wv[i], "tth"))
331             {
332                 if(++i >= wc)
333                     continue;
334                 hash = base64decode(wv[i], &len);
335                 if(len != 24)
336                 {
337                     free(hash);
338                     continue;
339                 }
340                 memcpy(hc->tth, hash, 24);
341                 free(hash);
342             }
343         }
344     }
345     fclose(stream);
346 }
347
348 static void hashtimercb(int cancelled, void *uudata)
349 {
350     hashwritetimer = NULL;
351     if(!cancelled)
352         writehashcache(1);
353 }
354
355 static void writehashcache(int now)
356 {
357     char *buf;
358     char *hcname;
359     FILE *stream;
360     struct hashcache *hc;
361     
362     if(!now)
363     {
364         if(hashwritetimer == NULL)
365             hashwritetimer = timercallback(ntime() + confgetint("cli", "hashwritedelay"), (void (*)(int, void *))hashtimercb, NULL);
366         return;
367     }
368     if(hashwritetimer != NULL)
369         canceltimer(hashwritetimer);
370     hcname = findfile(icswcstombs(confgetstr("cli", "hashcache"), NULL, NULL), NULL, 1);
371     if((stream = fopen(hcname, "w")) == NULL)
372     {
373         flog(LOG_WARNING, "could not write hash cache %s: %s", hcname, strerror(errno));
374         free(hcname);
375         return;
376     }
377     free(hcname);
378     fprintf(stream, "# Dolda Connect hash cache file\n");
379     fprintf(stream, "# Generated automatically, do not edit\n");
380     fprintf(stream, "# Format: DEVICE INODE MTIME [HASH...]\n");
381     fprintf(stream, "# HASH := HASHTYPE HASHVAL\n");
382     fprintf(stream, "# HASHTYPE can currently only be `tth'\n");
383     for(hc = hashcache; hc != NULL; hc = hc->next)
384     {
385         buf = base64encode(hc->tth, 24);
386         fprintf(stream, "%lli %lli %li tth %s\n", (long long)hc->dev, (long long)hc->inode, hc->mtime, buf);
387         free(buf);
388     }
389     fclose(stream);
390 }
391
392 static void hashread(struct socket *sk, void *uudata)
393 {
394     static char *hashbuf;
395     static size_t hashbufsize = 0, hashbufdata = 0;
396     char *buf, *p, *p2, *lp;
397     size_t bufsize;
398     char *wv[32];
399     int wc;
400     dev_t dev;
401     ino_t inode;
402     time_t mtime;
403     struct hashcache *hc;
404     
405     if((buf = sockgetinbuf(sk, &bufsize)) == NULL)
406         return;
407     bufcat(hashbuf, buf, bufsize);
408     free(buf);
409     while((lp = memchr(hashbuf, '\n', hashbufdata)) != NULL)
410     {
411         *(lp++) = 0;
412         wc = 0;
413         p = hashbuf;
414         while(1)
415         {
416             while((p2 = strchr(p, ' ')) == p)
417                 p++;
418             wv[wc++] = p;
419             if(p2 == NULL)
420             {
421                 break;
422             } else {
423                 *p2 = 0;
424                 p = p2 + 1;
425             }
426         }
427         if(wc != 4)
428         {
429             flog(LOG_ERR, "BUG: unexpected number of words (%i) arrived from hashing process", wc);
430         } else {
431             dev = strtoll(wv[0], NULL, 10);
432             inode = strtoll(wv[1], NULL, 10);
433             mtime = strtol(wv[2], NULL, 10);
434             if((hc = findhashcache(dev, inode)) == NULL)
435             {
436                 hc = newhashcache();
437                 hc->dev = dev;
438                 hc->inode = inode;
439             }
440             hc->mtime = mtime;
441             buf = base64decode(wv[3], NULL);
442             memcpy(hc->tth, buf, 24);
443             free(buf);
444             writehashcache(0);
445         }
446         memmove(hashbuf, lp, hashbufdata -= (lp - hashbuf));
447     }
448 }
449
450 static void hashexit(pid_t pid, int status, struct socket *outsock)
451 {
452     if(pid != hashjob)
453         flog(LOG_ERR, "BUG: hashing process changed PID?! old: %i new %i", hashjob, pid);
454     if(status)
455         flog(LOG_WARNING, "hashing process exited with non-zero status: %i", status);
456     hashjob = 0;
457     checkhashes();
458     putsock(outsock);
459 }
460
461 static int hashfile(char *path)
462 {
463     int i, ret;
464     int fd;
465     int pfd[2];
466     char buf[4096];
467     struct stat sb;
468     struct tigertreehash tth;
469     char digest[24];
470     struct socket *outsock;
471     
472     if((fd = open(path, O_RDONLY)) < 0)
473     {
474         flog(LOG_WARNING, "could not open %s for hashing: %s", path, strerror(errno));
475         return(1);
476     }
477     if(fstat(fd, &sb) < 0)
478     {
479         flog(LOG_WARNING, "could not stat %s while hashing: %s", path, strerror(errno));
480         close(fd);
481         return(1);
482     }
483     if(pipe(pfd) < 0)
484     {
485         flog(LOG_WARNING, "could not create pipe(!): %s", strerror(errno));
486         close(fd);
487         return(1);
488     }
489     hashjob = fork();
490     if(hashjob < 0)
491     {
492         flog(LOG_WARNING, "could not fork(!) hashing process: %s", strerror(errno));
493         close(fd);
494         close(pfd[0]);
495         close(pfd[1]);
496         return(1);
497     }
498     if(hashjob == 0)
499     {
500         nice(10);
501         signal(SIGHUP, SIG_DFL);
502         fd = dup2(fd, 4);
503         pfd[1] = dup2(pfd[1], 3);
504         dup2(fd, 0);
505         dup2(pfd[1], 1);
506         for(i = 3; i < FD_SETSIZE; i++)
507             close(i);
508         initlog();
509         inittigertree(&tth);
510         while((ret = read(0, buf, 4096)) > 0)
511             dotigertree(&tth, buf, ret);
512         if(ret < 0)
513         {
514             flog(LOG_WARNING, "could not read from %s while hashing: %s", path, strerror(errno));
515             exit(1);
516         }
517         synctigertree(&tth);
518         restigertree(&tth, digest);
519         ret = snprintf(buf, sizeof(buf), "%lli %lli %li %s\n", (long long)sb.st_dev, (long long)sb.st_ino, sb.st_mtime, base64encode(digest, 24));
520         write(1, buf, ret);
521         exit(0);
522     }
523     close(fd);
524     close(pfd[1]);
525     outsock = wrapsock(pfd[0]);
526     outsock->readcb = hashread;
527     childcallback(hashjob, (void (*)(pid_t, int, void *))hashexit, outsock);
528     return(0);
529 }
530
531 /*
532  * Call only when hashjob == 0
533  */
534 static void checkhashes(void)
535 {
536     struct sharecache *node, *next;
537     struct hashcache *hc;
538     char *path;
539     
540     node = shareroot->child;
541     for(node = shareroot->child; node != NULL; node = next)
542     {
543         next = nextscnode(node);
544         if(node->f.b.type != FILE_REG)
545             continue;
546         if(!node->f.b.hastth)
547         {
548             if(((hc = findhashcache(node->dev, node->inode)) != NULL) && (hc->mtime == node->mtime))
549             {
550                 memcpy(node->hashtth, hc->tth, 24);
551                 node->f.b.hastth = 1;
552                 GCBCHAINDOCB(sharechangecb, sharesize);
553             } else {
554                 path = getfspath(node);
555                 if(hashfile(path))
556                 {
557                     flog(LOG_WARNING, "could not hash %s, unsharing it", path);
558                     freecache(node);
559                     free(path);
560                     flog(LOG_INFO, "sharing %lli bytes", sharesize);
561                     continue;
562                 }
563                 free(path);
564                 return;
565             }
566         }
567     }
568 }
569
570 struct sharecache *nextscnode(struct sharecache *node)
571 {
572     if(node->child != NULL)
573         return(node->child);
574     while(node->next == NULL)
575     {
576         node = node->parent;
577         if(node == shareroot)
578             return(NULL);
579     }
580     return(node->next);
581 }
582
583 static void freescan(struct scanstate *job)
584 {
585     if(job->dd != NULL)
586         closedir(job->dd);
587     free(job);
588 }
589
590 /* No need for optimization; lookup isn't really that common */
591 struct sharecache *findcache(struct sharecache *parent, wchar_t *name)
592 {
593     struct sharecache *node;
594     
595     for(node = parent->child; node != NULL; node = node->next)
596     {
597         if(!wcscmp(node->name, name))
598             return(node);
599     }
600     return(NULL);
601 }
602
603 static void attachcache(struct sharecache *parent, struct sharecache *node)
604 {
605     node->parent = parent;
606     node->next = parent->child;
607     if(parent->child != NULL)
608         parent->child->prev = node;
609     parent->child = node;
610 }
611
612 static void detachcache(struct sharecache *node)
613 {
614     if(node->next != NULL)
615         node->next->prev = node->prev;
616     if(node->prev != NULL)
617         node->prev->next = node->next;
618     if((node->parent != NULL) && (node->parent->child == node))
619         node->parent->child = node->next;
620     node->parent = NULL;
621     node->next = NULL;
622     node->prev = NULL;
623 }
624
625 static void freecache(struct sharecache *node)
626 {
627     struct sharecache *cur, *next;
628     struct scanqueue *q, *nq, **fq;
629     
630     detachcache(node);
631     fq = &scanqueue;
632     for(q = scanqueue; q != NULL; q = nq)
633     {
634         nq = q->next;
635         if(q->state->node == node)
636         {
637             flog(LOG_DEBUG, "freed node %ls cancelled queued scan", node->name);
638             freescan(q->state);
639             *fq = q->next;
640             free(q);
641             continue;
642         }
643         fq = &q->next;
644     }
645     if(node->child != NULL)
646     {
647         for(cur = node->child; cur != NULL; cur = next)
648         {
649             next = cur->next;
650             freecache(cur);
651         }
652     }
653     CBCHAINDOCB(node, share_delete, node);
654     CBCHAINFREE(node, share_delete);
655     sharesize -= node->size;
656     if(node->f.b.type == FILE_REG)
657         sharedfiles--;
658     if(node->path != NULL)
659         free(node->path);
660     if(node->name != NULL)
661         free(node->name);
662     free(node);
663 }
664
665 static void freesharepoint(struct sharepoint *share)
666 {
667     struct sharecache *node;
668     
669     if(share->next != NULL)
670         share->next->prev = share->prev;
671     if(share->prev != NULL)
672         share->prev->next = share->next;
673     if(share == shares)
674         shares = share->next;
675     if((node = findcache(shareroot, share->name)) != NULL)
676         freecache(node);
677     free(share->path);
678     free(share->name);
679     free(share);
680 }
681
682 static struct sharecache *newcache(void)
683 {
684     struct sharecache *new;
685     
686     new = smalloc(sizeof(*new));
687     memset(new, 0, sizeof(*new));
688     CBCHAININIT(new, share_delete);
689     return(new);
690 }
691
692 char *getfspath(struct sharecache *node)
693 {
694     char *buf, *mbsname;
695     size_t bufsize;
696     
697     buf = smalloc(bufsize = 64);
698     *buf = 0;
699     while(node != NULL)
700     {
701         if(node->path != NULL)
702         {
703             if(bufsize < strlen(node->path) + strlen(buf) + 1)
704                 buf = srealloc(buf, strlen(node->path) + strlen(buf) + 1);
705             memmove(buf + strlen(node->path), buf, strlen(buf) + 1);
706             memcpy(buf, node->path, strlen(node->path));
707             return(buf);
708         }
709         if((mbsname = icwcstombs(node->name, NULL)) == NULL)
710         {
711             flog(LOG_WARNING, "could not map unicode share name (%ls) into filesystem charset: %s", node->name, strerror(errno));
712             free(buf);
713             return(NULL);
714         }
715         while(bufsize < strlen(mbsname) + 1 + strlen(buf) + 1)
716             buf = srealloc(buf, bufsize *= 2);
717         memmove(buf + strlen(mbsname) + 1, buf, strlen(buf) + 1);
718         memcpy(buf + 1, mbsname, strlen(mbsname));
719         *buf = '/';
720         free(mbsname);
721         node = node->parent;
722     }
723     buf = srealloc(buf, strlen(buf) + 1);
724     return(buf);
725 }
726
727 static int checknode(struct sharecache *node)
728 {
729     char *path;
730     struct stat sb;
731     
732     if(node->parent == NULL)
733     {
734         return(1);
735     } else {
736         if(!checknode(node->parent))
737             return(0);
738         path = getfspath(node);
739         if(stat(path, &sb) < 0)
740         {
741             flog(LOG_INFO, "%s was found to be broken (%s); scheduling rescan of parent", path, strerror(errno));
742             queuescan(node->parent);
743             return(0);
744         } else {
745             return(1);
746         }
747     }
748 }
749
750 int opensharecache(struct sharecache *node)
751 {
752     char *path;
753     int fd, errbak;
754     
755     path = getfspath(node);
756     fd = open(path, O_RDONLY);
757     errbak = errno;
758     if(fd < 0)
759     {
760         flog(LOG_WARNING, "could not open %s: %s", path, strerror(errbak));
761         checknode(node);
762     }
763     free(path);
764     errno = errbak;
765     return(fd);
766 }
767
768 static struct scanstate *newscan(struct sharecache *node)
769 {
770     struct scanstate *new;
771     
772     new = smalloc(sizeof(*new));
773     new->next = NULL;
774     new->node = node;
775     new->dd = NULL;
776     return(new);
777 }
778
779 void queuescan(struct sharecache *node)
780 {
781     struct scanqueue *new;
782     
783     new = smalloc(sizeof(*new));
784     new->state = newscan(node);
785     new->next = scanqueue;
786     scanqueue = new;
787 }
788
789 /* For internal use in doscan() */
790 static void removestale(struct sharecache *node)
791 {
792     struct sharecache *cur, *next;
793     
794     for(cur = node->child; cur != NULL; cur = next)
795     {
796         next = cur->next;
797         if(!cur->f.b.found)
798             freecache(cur);
799     }
800 }
801
802 /* For internal use in doscan() */
803 static void jobdone(void)
804 {
805     struct scanstate *jbuf;
806     
807     jbuf = scanjob;
808     scanjob = jbuf->next;
809     freescan(jbuf);
810     if(scanjob != NULL)
811         fchdir(dirfd(scanjob->dd));
812 }
813
814 int doscan(int quantum)
815 {
816     char *path;
817     wchar_t *wcs;
818     int type;
819     struct sharecache *n;
820     struct scanstate *jbuf;
821     struct scanqueue *qbuf;
822     struct dirent *de;
823     struct stat sb;
824     struct hashcache *hc;
825     int dmask, fmask;
826     static int busybefore = 0;
827     
828     dmask = confgetint("cli", "scandirmask");
829     fmask = confgetint("cli", "scanfilemask");
830     if((scanjob != NULL) && (scanjob->dd != NULL))
831     {
832         while(fchdir(dirfd(scanjob->dd)) < 0)
833         {
834             flog(LOG_WARNING, "could not fchdir to fd %i: %s", dirfd(scanjob->dd), strerror(errno));
835             removestale(scanjob->node);
836             jobdone();
837         }
838     }
839     while(quantum-- > 0)
840     {
841         if(scanjob != NULL)
842         {
843             busybefore = 1;
844         } else {
845             while(scanjob == NULL)
846             {
847                 if(scanqueue == NULL)
848                 {
849                     if(busybefore)
850                     {
851                         flog(LOG_INFO, "sharing %lli bytes", sharesize);
852                         busybefore = 0;
853                         GCBCHAINDOCB(sharechangecb, sharesize);
854                         if(hashjob == 0)
855                             checkhashes();
856                     }
857                     return(0);
858                 }
859                 busybefore = 1;
860                 scanjob = scanqueue->state;
861                 qbuf = scanqueue;
862                 scanqueue = qbuf->next;
863                 free(qbuf);
864                 for(n = scanjob->node->child; n != NULL; n = n->next)
865                     n->f.b.found = 0;
866             }
867         }
868         if(scanjob->dd == NULL)
869         {
870             path = getfspath(scanjob->node);
871             if((scanjob->dd = opendir(path)) == NULL)
872             {
873                 flog(LOG_WARNING, "cannot open directory %s for scanning: %s, deleting from share", path, strerror(errno));
874                 freecache(scanjob->node);
875                 free(path);
876                 jobdone();
877                 continue;
878             }
879             free(path);
880             if(fchdir(dirfd(scanjob->dd)) < 0)
881             {
882                 flog(LOG_WARNING, "could not fchdir to fd %i: %s", dirfd(scanjob->dd), strerror(errno));
883                 jobdone();
884                 continue;
885             }
886         }
887         if((de = readdir(scanjob->dd)) == NULL)
888         {
889             removestale(scanjob->node);
890             jobdone();
891             continue;
892         }
893         if(*de->d_name == '.')
894             continue;
895         if((wcs = icmbstowcs(de->d_name, NULL)) == NULL)
896         {
897             flog(LOG_WARNING, "file name %s has cannot be converted to wchar: %s", de->d_name, strerror(errno));
898             continue;
899         }
900         n = findcache(scanjob->node, wcs);
901         if(stat(de->d_name, &sb) < 0)
902         {
903             free(wcs);
904             if(n != NULL)
905             {
906                 flog(LOG_WARNING, "could not stat %s: %s, deleting from share", de->d_name, strerror(errno));
907                 freecache(n);
908             } else {
909                 flog(LOG_WARNING, "could not stat %s: %s", de->d_name, strerror(errno));
910             }
911             continue;
912         }
913         if(S_ISDIR(sb.st_mode))
914         {
915             if(~sb.st_mode & dmask)
916             {
917                 free(wcs);
918                 continue;
919             }
920             type = FILE_DIR;
921         } else if(S_ISREG(sb.st_mode)) {
922             if(~sb.st_mode & fmask)
923             {
924                 free(wcs);
925                 continue;
926             }
927             type = FILE_REG;
928         } else {
929             flog(LOG_WARNING, "unhandled file type: 0%o", sb.st_mode);
930             free(wcs);
931             continue;
932         }
933         if(n != NULL)
934         {
935             if((n->f.b.type != type) || (n->mtime != sb.st_mtime) || ((type == FILE_REG) && (n->size != sb.st_size)))
936             {
937                 freecache(n);
938                 n = NULL;
939             }
940         }
941         if(n == NULL)
942         {
943             n = newcache();
944             n->name = wcs;
945             if(S_ISREG(sb.st_mode))
946             {
947                 sharesize += (n->size = sb.st_size);
948                 sharedfiles++;
949             } else {
950                 n->size = 0;
951             }
952             n->mtime = sb.st_mtime;
953             n->dev = sb.st_dev;
954             n->inode = sb.st_ino;
955             n->f.b.type = type;
956             attachcache(scanjob->node, n);
957         } else {
958             free(wcs);
959         }
960         n->f.b.found = 1;
961         if(n->f.b.type == FILE_DIR)
962         {
963             jbuf = newscan(n);
964             jbuf->next = scanjob;
965             scanjob = jbuf;
966         } else if(n->f.b.type == FILE_REG) {
967             if(n->f.b.hastth && (n->mtime != sb.st_mtime))
968                 n->f.b.hastth = 0;
969             if(!n->f.b.hastth)
970             {
971                 if((hc = findhashcache(sb.st_dev, sb.st_ino)) != NULL)
972                 {
973                     if(hc->mtime == n->mtime)
974                     {
975                         n->f.b.hastth = 1;
976                         memcpy(n->hashtth, hc->tth, 24);
977                     } else {
978                         freehashcache(hc);
979                     }
980                 }
981             }
982         }
983     }
984     return(1);
985 }
986
987 static void rescancb(int cancelled, void *uudata)
988 {
989     scantimer = NULL;
990     if(!cancelled)
991     {
992         if(scanqueue == NULL)
993             scanshares();
994         else if(confgetint("cli", "rescandelay") > 0)
995             scantimer = timercallback(ntime() + confgetint("cli", "rescandelay"), (void (*)(int, void *))rescancb, NULL);
996     }
997 }
998
999 void scanshares(void)
1000 {
1001     struct sharepoint *cur;
1002     struct sharecache *node;
1003     struct stat sb;
1004     
1005     for(cur = shares; cur != NULL; cur = cur->next)
1006     {
1007         if((node = findcache(shareroot, cur->name)) == NULL)
1008         {
1009             if(stat(cur->path, &sb))
1010             {
1011                 flog(LOG_WARNING, "could not stat share \"%ls\": %s", cur->name, strerror(errno));
1012                 continue;
1013             }
1014             if(!S_ISDIR(sb.st_mode))
1015             {
1016                 flog(LOG_WARNING, "%s is not a directory; won't share it", cur->path);
1017                 continue;
1018             }
1019             node = newcache();
1020             node->name = swcsdup(cur->name);
1021             node->path = sstrdup(cur->path);
1022             if(node->path[strlen(node->path) - 1] == '/')
1023                 node->path[strlen(node->path) - 1] = 0;
1024             node->f.b.type = FILE_DIR;
1025             attachcache(shareroot, node);
1026         }
1027         queuescan(node);
1028     }
1029     if(scantimer != NULL)
1030         canceltimer(scantimer);
1031     if(confgetint("cli", "rescandelay") > 0)
1032         scantimer = timercallback(ntime() + confgetint("cli", "rescandelay"), (void (*)(int, void *))rescancb, NULL);
1033 }
1034
1035 static void preinit(int hup)
1036 {
1037     struct sharepoint *cur;
1038     
1039     if(hup)
1040     {
1041         for(cur = shares; cur != NULL; cur = cur->next)
1042             cur->delete = 1;
1043     } else {
1044         shareroot = newcache();
1045         shareroot->name = swcsdup(L"");
1046         shareroot->f.b.type = FILE_DIR;
1047     }
1048 }
1049
1050 static int rsdelayupdate(struct configvar *var, void *uudata)
1051 {
1052     if(scantimer != NULL)
1053         canceltimer(scantimer);
1054     if(confgetint("cli", "rescandelay") > 0)
1055         scantimer = timercallback(ntime() + var->val.num, (void (*)(int, void *))rescancb, NULL);
1056     return(0);
1057 }
1058
1059 static int init(int hup)
1060 {
1061     struct sharepoint *cur, *next;
1062     
1063     readhashcache();
1064     for(cur = shares; cur != NULL; cur = next)
1065     {
1066         next = cur->next;
1067         if(cur->delete)
1068             freesharepoint(cur);
1069     }
1070     scanshares();
1071     if(!hup)
1072     {
1073         while(doscan(100));
1074         CBREG(confgetvar("cli", "rescandelay"), conf_update, rsdelayupdate, NULL, NULL);
1075     }
1076     return(0);
1077 }
1078
1079 static int run(void)
1080 {
1081     if(hashjob == -1)
1082     {
1083         hashjob = 0;
1084         checkhashes();
1085     }
1086     return(doscan(10));
1087 }
1088
1089 static void terminate(void)
1090 {
1091     if(hashjob != 0)
1092         kill(hashjob, SIGHUP);
1093     if(hashwritetimer != NULL)
1094         writehashcache(1);
1095     while(shares != NULL)
1096         freesharepoint(shares);
1097     freecache(shareroot);
1098 }
1099
1100 static struct module me =
1101 {
1102     .name = "cli",
1103     .conf =
1104     {
1105         .vars = myvars,
1106         .cmds = mycmds
1107     },
1108     .preinit = preinit,
1109     .init = init,
1110     .run = run,
1111     .terminate = terminate
1112 };
1113
1114 MODULE(me)