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