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