]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/upas/fs/fs.c
upas/fs: remove checkmboxrefs() debugging code, properly handle errors in 9p loop
[plan9front.git] / sys / src / cmd / upas / fs / fs.c
1 #include "common.h"
2 #include <auth.h>
3 #include <fcall.h>
4 #include <libsec.h>
5 #include <ctype.h>
6 #include "dat.h"
7
8 enum
9 {
10         OPERM   = 0x3,          // mask of all permission types in open mode
11 };
12
13 typedef struct Fid Fid;
14
15 struct Fid
16 {
17         Qid     qid;
18         short   busy;
19         short   open;
20         int     fid;
21         Fid     *next;
22         Mailbox *mb;
23         Message *m;
24         Message *mtop;          // top level message
25
26         //finger pointers to speed up reads of large directories
27         long    foff;   // offset/DIRLEN of finger
28         Message *fptr;  // pointer to message at off
29         int     fvers;  // mailbox version when finger was saved
30 };
31
32 ulong   path;           // incremented for each new file
33 Fid     *fids;
34 int     mfd[2];
35 char    user[Elemlen];
36 int     messagesize = 4*1024+IOHDRSZ;
37 uchar   mdata[8*1024+IOHDRSZ];
38 uchar   mbuf[8*1024+IOHDRSZ];
39 Fcall   thdr;
40 Fcall   rhdr;
41 int     fflg;
42 char    *mntpt;
43 int     biffing;
44 int     plumbing = 1;
45
46 QLock   mbllock;
47 Mailbox *mbl;
48
49 Fid             *newfid(int);
50 void            error(char*);
51 void            io(void);
52 void            *erealloc(void*, ulong);
53 void            *emalloc(ulong);
54 void            usage(void);
55 void            reader(void);
56 int             readheader(Message*, char*, int, int);
57 int             cistrncmp(char*, char*, int);
58 int             tokenconvert(String*, char*, int);
59 String*         stringconvert(String*, char*, int);
60 void            post(char*, char*, int);
61
62 char    *rflush(Fid*), *rauth(Fid*),
63         *rattach(Fid*), *rwalk(Fid*),
64         *ropen(Fid*), *rcreate(Fid*),
65         *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
66         *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
67         *rversion(Fid*);
68
69 char    *(*fcalls[])(Fid*) = {
70         [Tflush]        rflush,
71         [Tversion]      rversion,
72         [Tauth] rauth,
73         [Tattach]       rattach,
74         [Twalk]         rwalk,
75         [Topen]         ropen,
76         [Tcreate]       rcreate,
77         [Tread]         rread,
78         [Twrite]        rwrite,
79         [Tclunk]        rclunk,
80         [Tremove]       rremove,
81         [Tstat]         rstat,
82         [Twstat]        rwstat,
83 };
84
85 char    Eperm[] =       "permission denied";
86 char    Enotdir[] =     "not a directory";
87 char    Enoauth[] =     "upas/fs: authentication not required";
88 char    Enotexist[] =   "file does not exist";
89 char    Einuse[] =      "file in use";
90 char    Eexist[] =      "file exists";
91 char    Enotowner[] =   "not owner";
92 char    Eisopen[] =     "file already open for I/O";
93 char    Excl[] =        "exclusive use file already open";
94 char    Ename[] =       "illegal name";
95 char    Ebadctl[] =     "unknown control message";
96
97 char *dirtab[] =
98 {
99 [Qdir]          ".",
100 [Qbody]         "body",
101 [Qbcc]          "bcc",
102 [Qcc]           "cc",
103 [Qdate]         "date",
104 [Qdigest]       "digest",
105 [Qdisposition]  "disposition",
106 [Qfilename]     "filename",
107 [Qfrom]         "from",
108 [Qheader]       "header",
109 [Qinfo]         "info",
110 [Qinreplyto]    "inreplyto",
111 [Qlines]        "lines",
112 [Qmimeheader]   "mimeheader",
113 [Qmessageid]    "messageid",
114 [Qraw]          "raw",
115 [Qrawunix]      "rawunix",
116 [Qrawbody]      "rawbody",
117 [Qrawheader]    "rawheader",
118 [Qreplyto]      "replyto",
119 [Qsender]       "sender",
120 [Qsubject]      "subject",
121 [Qto]           "to",
122 [Qtype]         "type",
123 [Qunixdate]     "unixdate",
124 [Qunixheader]   "unixheader",
125 [Qctl]          "ctl",
126 [Qmboxctl]      "ctl",
127 };
128
129 enum
130 {
131         Hsize=  1277,
132 };
133
134 Hash    *htab[Hsize];
135
136 int     debug;
137 int     fflag;
138 int     logging;
139
140 void
141 usage(void)
142 {
143         fprint(2, "usage: upas/fs [-bdlnps] [-f mboxfile] [-m mountpoint]\n");
144         exits("usage");
145 }
146
147 void
148 notifyf(void *, char *s)
149 {
150         if(strstr(s, "alarm") || strstr(s, "interrupt"))
151                 noted(NCONT);
152         noted(NDFLT);
153 }
154
155 void
156 main(int argc, char *argv[])
157 {
158         int p[2], std, nodflt;
159         char maildir[128];
160         char mbox[128];
161         char *mboxfile, *err;
162         char srvfile[64];
163         int srvpost;
164
165         rfork(RFNOTEG);
166         mntpt = nil;
167         fflag = 0;
168         mboxfile = nil;
169         std = 0;
170         nodflt = 0;
171         srvpost = 0;
172
173         ARGBEGIN{
174         case 'b':
175                 biffing = 1;
176                 break;
177         case 'f':
178                 fflag = 1;
179                 mboxfile = EARGF(usage());
180                 break;
181         case 'm':
182                 mntpt = EARGF(usage());
183                 break;
184         case 'd':
185                 debug = 1;
186                 break;
187         case 'p':
188                 plumbing = 0;
189                 break;
190         case 's':
191                 srvpost = 1;
192                 break;
193         case 'l':
194                 logging = 1;
195                 break;
196         case 'n':
197                 nodflt = 1;
198                 break;
199         default:
200                 usage();
201         }ARGEND
202
203         if(argc)
204                 usage();
205         if(pipe(p) < 0)
206                 error("pipe failed");
207         mfd[0] = p[0];
208         mfd[1] = p[0];
209
210         notify(notifyf);
211         strcpy(user, getuser());
212         if(mntpt == nil){
213                 snprint(maildir, sizeof(maildir), "/mail/fs");
214                 mntpt = maildir;
215         }
216         if(mboxfile == nil && !nodflt){
217                 snprint(mbox, sizeof(mbox), "/mail/box/%s/mbox", user);
218                 mboxfile = mbox;
219                 std = 1;
220         }
221
222         if(debug)
223                 fmtinstall('F', fcallfmt);
224
225         if(mboxfile != nil){
226                 err = newmbox(mboxfile, "mbox", std);
227                 if(err != nil)
228                         sysfatal("opening %s: %s", mboxfile, err);
229         }
230
231         switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
232         case -1:
233                 error("fork");
234         case 0:
235                 henter(PATH(0, Qtop), dirtab[Qctl],
236                         (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
237                 close(p[1]);
238                 io();
239                 postnote(PNGROUP, getpid(), "die yankee pig dog");
240                 break;
241         default:
242                 close(p[0]);    /* don't deadlock if child fails */
243                 if(srvpost){
244                         sprint(srvfile, "/srv/upasfs.%s", user);
245                         post(srvfile, "upasfs", p[1]);
246                 } else {
247                         if(mount(p[1], -1, mntpt, MREPL, "") < 0)
248                                 error("mount failed");
249                 }
250         }
251         exits(0);
252 }
253
254 static int
255 fileinfo(Message *m, int t, char **pp)
256 {
257         char *p;
258         int len;
259
260         p = "";
261         len = 0;
262         switch(t){
263         case Qbody:
264                 p = m->body;
265                 len = m->bend - m->body;
266                 break;
267         case Qbcc:
268                 if(m->bcc822){
269                         p = s_to_c(m->bcc822);
270                         len = strlen(p);
271                 }
272                 break;
273         case Qcc:
274                 if(m->cc822){
275                         p = s_to_c(m->cc822);
276                         len = strlen(p);
277                 }
278                 break;
279         case Qdisposition:
280                 switch(m->disposition){
281                 case Dinline:
282                         p = "inline";
283                         break;
284                 case Dfile:
285                         p = "file";
286                         break;
287                 }
288                 len = strlen(p);
289                 break;
290         case Qdate:
291                 if(m->date822){
292                         p = s_to_c(m->date822);
293                         len = strlen(p);
294                 } else if(m->unixdate != nil){
295                         p = s_to_c(m->unixdate);
296                         len = strlen(p);
297                 }
298                 break;
299         case Qfilename:
300                 if(m->filename){
301                         p = s_to_c(m->filename);
302                         len = strlen(p);
303                 }
304                 break;
305         case Qinreplyto:
306                 if(m->inreplyto822){
307                         p = s_to_c(m->inreplyto822);
308                         len = strlen(p);
309                 }
310                 break;
311         case Qmessageid:
312                 if(m->messageid822){
313                         p = s_to_c(m->messageid822);
314                         len = strlen(p);
315                 }
316                 break;
317         case Qfrom:
318                 if(m->from822){
319                         p = s_to_c(m->from822);
320                         len = strlen(p);
321                 } else if(m->unixfrom != nil){
322                         p = s_to_c(m->unixfrom);
323                         len = strlen(p);
324                 }
325                 break;
326         case Qheader:
327                 p = m->header;
328                 len = headerlen(m);
329                 break;
330         case Qlines:
331                 p = m->lines;
332                 if(*p == 0)
333                         countlines(m);
334                 len = strlen(m->lines);
335                 break;
336         case Qraw:
337                 p = m->start;
338                 if(strncmp(m->start, "From ", 5) == 0){
339                         p = strchr(p, '\n');
340                         if(p == nil)
341                                 p = m->start;
342                         else
343                                 p++;
344                 }
345                 len = m->end - p;
346                 break;
347         case Qrawunix:
348                 p = m->start;
349                 len = m->end - p;
350                 break;
351         case Qrawbody:
352                 p = m->rbody;
353                 len = m->rbend - p;
354                 break;
355         case Qrawheader:
356                 p = m->header;
357                 len = m->hend - p;
358                 break;
359         case Qmimeheader:
360                 p = m->mheader;
361                 len = m->mhend - p;
362                 break;
363         case Qreplyto:
364                 p = nil;
365                 if(m->replyto822 != nil){
366                         p = s_to_c(m->replyto822);
367                         len = strlen(p);
368                 } else if(m->from822 != nil){
369                         p = s_to_c(m->from822);
370                         len = strlen(p);
371                 } else if(m->sender822 != nil){
372                         p = s_to_c(m->sender822);
373                         len = strlen(p);
374                 } else if(m->unixfrom != nil){
375                         p = s_to_c(m->unixfrom);
376                         len = strlen(p);
377                 }
378                 break;
379         case Qsender:
380                 if(m->sender822){
381                         p = s_to_c(m->sender822);
382                         len = strlen(p);
383                 }
384                 break;
385         case Qsubject:
386                 p = nil;
387                 if(m->subject822){
388                         p = s_to_c(m->subject822);
389                         len = strlen(p);
390                 }
391                 break;
392         case Qto:
393                 if(m->to822){
394                         p = s_to_c(m->to822);
395                         len = strlen(p);
396                 }
397                 break;
398         case Qtype:
399                 if(m->type){
400                         p = s_to_c(m->type);
401                         len = strlen(p);
402                 }
403                 break;
404         case Qunixdate:
405                 if(m->unixdate){
406                         p = s_to_c(m->unixdate);
407                         len = strlen(p);
408                 }
409                 break;
410         case Qunixheader:
411                 if(m->unixheader){
412                         p = s_to_c(m->unixheader);
413                         len = s_len(m->unixheader);
414                 }
415                 break;
416         case Qdigest:
417                 if(m->sdigest){
418                         p = s_to_c(m->sdigest);
419                         len = strlen(p);
420                 }
421                 break;
422         }
423         *pp = p;
424         return len;
425 }
426
427 int infofields[] = {
428         Qfrom,
429         Qto,
430         Qcc,
431         Qreplyto,
432         Qunixdate,
433         Qsubject,
434         Qtype,
435         Qdisposition,
436         Qfilename,
437         Qdigest,
438         Qbcc,
439         Qinreplyto,
440         Qdate,
441         Qsender,
442         Qmessageid,
443         Qlines,
444         -1,
445 };
446
447 static int
448 readinfo(Message *m, char *buf, long off, int count)
449 {
450         char *p;
451         int len, i, n;
452         String *s;
453
454         s = s_new();
455         len = 0;
456         for(i = 0; len < count && infofields[i] >= 0; i++){
457                 n = fileinfo(m, infofields[i], &p);
458                 s = stringconvert(s, p, n);
459                 s_append(s, "\n");
460                 p = s_to_c(s);
461                 n = strlen(p);
462                 if(off > 0){
463                         if(off >= n){
464                                 off -= n;
465                                 continue;
466                         }
467                         p += off;
468                         n -= off;
469                         off = 0;
470                 }
471                 if(n > count - len)
472                         n = count - len;
473                 if(buf)
474                         memmove(buf+len, p, n);
475                 len += n;
476         }
477         s_free(s);
478         return len;
479 }
480
481 static void
482 mkstat(Dir *d, Mailbox *mb, Message *m, int t)
483 {
484         char *p;
485
486         d->uid = user;
487         d->gid = user;
488         d->muid = user;
489         d->mode = 0444;
490         d->qid.vers = 0;
491         d->qid.type = QTFILE;
492         d->type = 0;
493         d->dev = 0;
494         if(mb != nil && mb->d != nil){
495                 d->atime = mb->d->atime;
496                 d->mtime = mb->d->mtime;
497         } else {
498                 d->atime = time(0);
499                 d->mtime = d->atime;
500         }
501
502         switch(t){
503         case Qtop:
504                 d->name = ".";
505                 d->mode = DMDIR|0555;
506                 d->atime = d->mtime = time(0);
507                 d->length = 0;
508                 d->qid.path = PATH(0, Qtop);
509                 d->qid.type = QTDIR;
510                 break;
511         case Qmbox:
512                 d->name = mb->name;
513                 d->mode = DMDIR|0555;
514                 d->length = 0;
515                 d->qid.path = PATH(mb->id, Qmbox);
516                 d->qid.type = QTDIR;
517                 d->qid.vers = mb->vers;
518                 break;
519         case Qdir:
520                 d->name = m->name;
521                 d->mode = DMDIR|0555;
522                 d->length = 0;
523                 d->qid.path = PATH(m->id, Qdir);
524                 d->qid.type = QTDIR;
525                 break;
526         case Qctl:
527                 d->name = dirtab[t];
528                 d->mode = 0666;
529                 d->atime = d->mtime = time(0);
530                 d->length = 0;
531                 d->qid.path = PATH(0, Qctl);
532                 break;
533         case Qmboxctl:
534                 d->name = dirtab[t];
535                 d->mode = 0222;
536                 d->atime = d->mtime = time(0);
537                 d->length = 0;
538                 d->qid.path = PATH(mb->id, Qmboxctl);
539                 break;
540         case Qinfo:
541                 d->name = dirtab[t];
542                 d->length = readinfo(m, nil, 0, 1<<30);
543                 d->qid.path = PATH(m->id, t);
544                 break;
545         default:
546                 d->name = dirtab[t];
547                 d->length = fileinfo(m, t, &p);
548                 d->qid.path = PATH(m->id, t);
549                 break;
550         }
551 }
552
553 char*
554 rversion(Fid*)
555 {
556         Fid *f;
557
558         if(thdr.msize < 256)
559                 return "max messagesize too small";
560         if(thdr.msize < messagesize)
561                 messagesize = thdr.msize;
562         rhdr.msize = messagesize;
563         if(strncmp(thdr.version, "9P2000", 6) != 0)
564                 return "unknown 9P version";
565         else
566                 rhdr.version = "9P2000";
567         for(f = fids; f; f = f->next)
568                 if(f->busy)
569                         rclunk(f);
570         return nil;
571 }
572
573 char*
574 rauth(Fid*)
575 {
576         return Enoauth;
577 }
578
579 char*
580 rflush(Fid *f)
581 {
582         USED(f);
583         return 0;
584 }
585
586 char*
587 rattach(Fid *f)
588 {
589         f->busy = 1;
590         f->m = nil;
591         f->mb = nil;
592         f->qid.path = PATH(0, Qtop);
593         f->qid.type = QTDIR;
594         f->qid.vers = 0;
595         rhdr.qid = f->qid;
596         if(strcmp(thdr.uname, user) != 0)
597                 return Eperm;
598         return 0;
599 }
600
601 static Fid*
602 doclone(Fid *f, int nfid)
603 {
604         Fid *nf;
605
606         nf = newfid(nfid);
607         if(nf->busy)
608                 return nil;
609         nf->busy = 1;
610         nf->open = 0;
611         nf->m = f->m;
612         nf->mtop = f->mtop;
613         nf->mb = f->mb;
614         if(f->mb != nil)
615                 mboxincref(f->mb);
616         if(f->mtop != nil){
617                 qlock(f->mb);
618                 msgincref(f->mtop);
619                 qunlock(f->mb);
620         }
621         nf->qid = f->qid;
622         return nf;
623 }
624
625 char*
626 dowalk(Fid *f, char *name)
627 {
628         int t;
629         Mailbox *omb, *mb;
630         char *rv, *p;
631         Hash *h;
632
633         t = FILE(f->qid.path);
634
635         rv = Enotexist;
636
637         omb = f->mb;
638         if(omb)
639                 qlock(omb);
640         else
641                 qlock(&mbllock);
642
643         // this must catch everything except . and ..
644 retry:
645         h = hlook(f->qid.path, name);
646         if(h != nil){
647                 f->mb = h->mb;
648                 f->m = h->m;
649                 switch(t){
650                 case Qtop:
651                         if(f->mb != nil)
652                                 mboxincref(f->mb);
653                         break;
654                 case Qmbox:
655                         if(f->m){
656                                 msgincref(f->m);
657                                 f->mtop = f->m;
658                         }
659                         break;
660                 }
661                 f->qid = h->qid;
662                 rv = nil;
663         } else if((p = strchr(name, '.')) != nil && *name != '.'){
664                 *p = 0;
665                 goto retry;
666         }
667
668         if(omb)
669                 qunlock(omb);
670         else
671                 qunlock(&mbllock);
672         if(rv == nil)
673                 return rv;
674
675         if(strcmp(name, ".") == 0)
676                 return nil;
677
678         if(f->qid.type != QTDIR)
679                 return Enotdir;
680
681         if(strcmp(name, "..") == 0){
682                 switch(t){
683                 case Qtop:
684                         f->qid.path = PATH(0, Qtop);
685                         f->qid.type = QTDIR;
686                         f->qid.vers = 0;
687                         break;
688                 case Qmbox:
689                         f->qid.path = PATH(0, Qtop);
690                         f->qid.type = QTDIR;
691                         f->qid.vers = 0;
692                         qlock(&mbllock);
693                         mb = f->mb;
694                         f->mb = nil;
695                         mboxdecref(mb);
696                         qunlock(&mbllock);
697                         break;
698                 case Qdir:
699                         qlock(f->mb);
700                         if(f->m->whole == f->mb->root){
701                                 f->qid.path = PATH(f->mb->id, Qmbox);
702                                 f->qid.type = QTDIR;
703                                 f->qid.vers = f->mb->d->qid.vers;
704                                 msgdecref(f->mb, f->mtop);
705                                 f->m = f->mtop = nil;
706                         } else {
707                                 f->m = f->m->whole;
708                                 f->qid.path = PATH(f->m->id, Qdir);
709                                 f->qid.type = QTDIR;
710                         }
711                         qunlock(f->mb);
712                         break;
713                 }
714                 rv = nil;
715         }
716         return rv;
717 }
718
719 char*
720 rwalk(Fid *f)
721 {
722         Fid *nf;
723         char *rv;
724         int i;
725
726         if(f->open)
727                 return Eisopen;
728
729         rhdr.nwqid = 0;
730         nf = nil;
731
732         /* clone if requested */
733         if(thdr.newfid != thdr.fid){
734                 nf = doclone(f, thdr.newfid);
735                 if(nf == nil)
736                         return "new fid in use";
737                 f = nf;
738         }
739
740         /* if it's just a clone, return */
741         if(thdr.nwname == 0 && nf != nil)
742                 return nil;
743
744         /* walk each element */
745         rv = nil;
746         for(i = 0; i < thdr.nwname; i++){
747                 rv = dowalk(f, thdr.wname[i]);
748                 if(rv != nil){
749                         if(nf != nil)   
750                                 rclunk(nf);
751                         break;
752                 }
753                 rhdr.wqid[i] = f->qid;
754         }
755         rhdr.nwqid = i;
756
757         /* we only error out if no walk  */
758         if(i > 0)
759                 rv = nil;
760
761         return rv;
762 }
763
764 char *
765 ropen(Fid *f)
766 {
767         int file;
768
769         if(f->open)
770                 return Eisopen;
771
772         file = FILE(f->qid.path);
773         if(thdr.mode != OREAD)
774                 if(file != Qctl && file != Qmboxctl)
775                         return Eperm;
776
777         // make sure we've decoded
778         if(file == Qbody){
779                 if(f->m->decoded == 0)
780                         decode(f->m);
781                 if(f->m->converted == 0)
782                         convert(f->m);
783         }
784
785         rhdr.iounit = 0;
786         rhdr.qid = f->qid;
787         f->open = 1;
788         return 0;
789 }
790
791 char *
792 rcreate(Fid*)
793 {
794         return Eperm;
795 }
796
797 int
798 readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
799 {
800         Dir d;
801         int m, n;
802         long pos;
803         Mailbox *mb;
804
805         n = 0;
806         pos = 0;
807         mkstat(&d, nil, nil, Qctl);
808         m = convD2M(&d, &buf[n], blen);
809         if(off <= pos){
810                 if(m <= BIT16SZ || m > cnt)
811                         return 0;
812                 n += m;
813                 cnt -= m;
814         }
815         pos += m;
816                 
817         for(mb = mbl; mb != nil; mb = mb->next){
818                 mkstat(&d, mb, nil, Qmbox);
819                 m = convD2M(&d, &buf[n], blen-n);
820                 if(off <= pos){
821                         if(m <= BIT16SZ || m > cnt)
822                                 break;
823                         n += m;
824                         cnt -= m;
825                 }
826                 pos += m;
827         }
828         return n;
829 }
830
831 int
832 readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
833 {
834         Dir d;
835         int n, m;
836         long pos;
837         Message *msg;
838
839         n = 0;
840         if(f->mb->ctl){
841                 mkstat(&d, f->mb, nil, Qmboxctl);
842                 m = convD2M(&d, &buf[n], blen);
843                 if(off == 0){
844                         if(m <= BIT16SZ || m > cnt){
845                                 f->fptr = nil;
846                                 return 0;
847                         }
848                         n += m;
849                         cnt -= m;
850                 } else
851                         off -= m;
852         }
853
854         // to avoid n**2 reads of the directory, use a saved finger pointer
855         if(f->mb->vers == f->fvers && off >= f->foff && f->fptr != nil){
856                 msg = f->fptr;
857                 pos = f->foff;
858         } else {
859                 msg = f->mb->root->part;
860                 pos = 0;
861         } 
862
863         for(; cnt > 0 && msg != nil; msg = msg->next){
864                 // act like deleted files aren't there
865                 if(msg->deleted)
866                         continue;
867
868                 mkstat(&d, f->mb, msg, Qdir);
869                 m = convD2M(&d, &buf[n], blen-n);
870                 if(off <= pos){
871                         if(m <= BIT16SZ || m > cnt)
872                                 break;
873                         n += m;
874                         cnt -= m;
875                 }
876                 pos += m;
877         }
878
879         // save a finger pointer for next read of the mbox directory
880         f->foff = pos;
881         f->fptr = msg;
882         f->fvers = f->mb->vers;
883
884         return n;
885 }
886
887 int
888 readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
889 {
890         Dir d;
891         int i, n, m;
892         long pos;
893         Message *msg;
894
895         n = 0;
896         pos = 0;
897         for(i = 0; i < Qmax; i++){
898                 mkstat(&d, f->mb, f->m, i);
899                 m = convD2M(&d, &buf[n], blen-n);
900                 if(off <= pos){
901                         if(m <= BIT16SZ || m > cnt)
902                                 return n;
903                         n += m;
904                         cnt -= m;
905                 }
906                 pos += m;
907         }
908         for(msg = f->m->part; msg != nil; msg = msg->next){
909                 mkstat(&d, f->mb, msg, Qdir);
910                 m = convD2M(&d, &buf[n], blen-n);
911                 if(off <= pos){
912                         if(m <= BIT16SZ || m > cnt)
913                                 break;
914                         n += m;
915                         cnt -= m;
916                 }
917                 pos += m;
918         }
919
920         return n;
921 }
922
923 char*
924 rread(Fid *f)
925 {
926         long off;
927         int t, i, n, cnt;
928         char *p;
929
930         rhdr.count = 0;
931         off = thdr.offset;
932         cnt = thdr.count;
933
934         if(cnt > messagesize - IOHDRSZ)
935                 cnt = messagesize - IOHDRSZ;
936
937         rhdr.data = (char*)mbuf;
938
939         t = FILE(f->qid.path);
940         if(f->qid.type & QTDIR){
941                 if(t == Qtop) {
942                         qlock(&mbllock);
943                         n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
944                         qunlock(&mbllock);
945                 } else if(t == Qmbox) {
946                         qlock(f->mb);
947                         if(off == 0)
948                                 syncmbox(f->mb, 1);
949                         n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
950                         qunlock(f->mb);
951                 } else if(t == Qmboxctl) {
952                         n = 0;
953                 } else {
954                         n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
955                 }
956
957                 rhdr.count = n;
958                 return nil;
959         }
960
961         if(FILE(f->qid.path) == Qheader){
962                 rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
963                 return nil;
964         }
965
966         if(FILE(f->qid.path) == Qinfo){
967                 rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt);
968                 return nil;
969         }
970
971         i = fileinfo(f->m, FILE(f->qid.path), &p);
972         if(off < i){
973                 if((off + cnt) > i)
974                         cnt = i - off;
975                 memmove(mbuf, p + off, cnt);
976                 rhdr.count = cnt;
977         }
978         return nil;
979 }
980
981 char*
982 rwrite(Fid *f)
983 {
984         char *err;
985         char *token[1024];
986         int t, n;
987         String *file;
988
989         t = FILE(f->qid.path);
990         rhdr.count = thdr.count;
991         switch(t){
992         case Qctl:
993                 if(thdr.count == 0)
994                         return Ebadctl;
995                 if(thdr.data[thdr.count-1] == '\n')
996                         thdr.data[thdr.count-1] = 0;
997                 else
998                         thdr.data[thdr.count] = 0;
999                 n = tokenize(thdr.data, token, nelem(token));
1000                 if(n == 0)
1001                         return Ebadctl;
1002                 if(strcmp(token[0], "open") == 0){
1003                         file = s_new();
1004                         switch(n){
1005                         case 1:
1006                                 err = Ebadctl;
1007                                 break;
1008                         case 2:
1009                                 mboxpath(token[1], getlog(), file, 0);
1010                                 err = newmbox(s_to_c(file), nil, 0);
1011                                 break;
1012                         default:
1013                                 mboxpath(token[1], getlog(), file, 0);
1014                                 if(strchr(token[2], '/') != nil)
1015                                         err = "/ not allowed in mailbox name";
1016                                 else
1017                                         err = newmbox(s_to_c(file), token[2], 0);
1018                                 break;
1019                         }
1020                         s_free(file);
1021                         return err;
1022                 }
1023                 if(strcmp(token[0], "close") == 0){
1024                         if(n < 2)
1025                                 return nil;
1026                         freembox(token[1]);
1027                         return nil;
1028                 }
1029                 if(strcmp(token[0], "delete") == 0){
1030                         if(n < 3)
1031                                 return nil;
1032                         delmessages(n-1, &token[1]);
1033                         return nil;
1034                 }
1035                 return Ebadctl;
1036         case Qmboxctl:
1037                 if(f->mb && f->mb->ctl){
1038                         if(thdr.count == 0)
1039                                 return Ebadctl;
1040                         if(thdr.data[thdr.count-1] == '\n')
1041                                 thdr.data[thdr.count-1] = 0;
1042                         else
1043                                 thdr.data[thdr.count] = 0;
1044                         n = tokenize(thdr.data, token, nelem(token));
1045                         if(n == 0)
1046                                 return Ebadctl;
1047                         return (*f->mb->ctl)(f->mb, n, token);
1048                 }
1049         }
1050         return Eperm;
1051 }
1052
1053 char *
1054 rclunk(Fid *f)
1055 {
1056         Mailbox *mb;
1057
1058         f->busy = 0;
1059         f->open = 0;
1060         if(f->mtop != nil){
1061                 qlock(f->mb);
1062                 msgdecref(f->mb, f->mtop);
1063                 qunlock(f->mb);
1064         }
1065         f->m = f->mtop = nil;
1066         mb = f->mb;
1067         if(mb != nil){
1068                 f->mb = nil;
1069                 assert(mb->refs > 0);
1070                 qlock(&mbllock);
1071                 mboxdecref(mb);
1072                 qunlock(&mbllock);
1073         }
1074         f->fid = -1;
1075         return 0;
1076 }
1077
1078 char *
1079 rremove(Fid *f)
1080 {
1081         if(f->m != nil){
1082                 if(f->m->deleted == 0)
1083                         mailplumb(f->mb, f->m, 1);
1084                 f->m->deleted = 1;
1085         }
1086         return rclunk(f);
1087 }
1088
1089 char *
1090 rstat(Fid *f)
1091 {
1092         Dir d;
1093
1094         if(FILE(f->qid.path) == Qmbox){
1095                 qlock(f->mb);
1096                 syncmbox(f->mb, 1);
1097                 qunlock(f->mb);
1098         }
1099         mkstat(&d, f->mb, f->m, FILE(f->qid.path));
1100         rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ);
1101         rhdr.stat = mbuf;
1102         return 0;
1103 }
1104
1105 char *
1106 rwstat(Fid*)
1107 {
1108         return Eperm;
1109 }
1110
1111 Fid *
1112 newfid(int fid)
1113 {
1114         Fid *f, *ff;
1115
1116         ff = 0;
1117         for(f = fids; f; f = f->next)
1118                 if(f->fid == fid)
1119                         return f;
1120                 else if(!ff && !f->busy)
1121                         ff = f;
1122         if(ff){
1123                 ff->fid = fid;
1124                 ff->fptr = nil;
1125                 return ff;
1126         }
1127         f = emalloc(sizeof *f);
1128         f->fid = fid;
1129         f->fptr = nil;
1130         f->next = fids;
1131         fids = f;
1132         return f;
1133 }
1134
1135 int
1136 fidmboxrefs(Mailbox *mb)
1137 {
1138         Fid *f;
1139         int refs = 0;
1140
1141         for(f = fids; f; f = f->next){
1142                 if(f->mb == mb)
1143                         refs++;
1144         }
1145         return refs;
1146 }
1147
1148 void
1149 io(void)
1150 {
1151         char *err;
1152         int n;
1153
1154         /* start a process to watch the mailboxes*/
1155         if(plumbing){
1156                 switch(rfork(RFPROC|RFMEM)){
1157                 case -1:
1158                         /* oh well */
1159                         break;
1160                 case 0:
1161                         reader();
1162                         exits(nil);
1163                 default:
1164                         break;
1165                 }
1166         }
1167
1168         while((n = read9pmsg(mfd[0], mdata, messagesize)) != 0){
1169                 if(n < 0)
1170                         error("mount read");
1171                 if(convM2S(mdata, n, &thdr) != n)
1172                         error("convM2S format error");
1173
1174                 if(debug)
1175                         fprint(2, "%s:<-%F\n", argv0, &thdr);
1176
1177                 rhdr.data = (char*)mdata + messagesize;
1178                 if(!fcalls[thdr.type])
1179                         err = "bad fcall type";
1180                 else
1181                         err = (*fcalls[thdr.type])(newfid(thdr.fid));
1182                 if(err){
1183                         rhdr.type = Rerror;
1184                         rhdr.ename = err;
1185                 }else{
1186                         rhdr.type = thdr.type + 1;
1187                         rhdr.fid = thdr.fid;
1188                 }
1189                 rhdr.tag = thdr.tag;
1190                 if(debug)
1191                         fprint(2, "%s:->%F\n", argv0, &rhdr);/**/
1192                 n = convS2M(&rhdr, mdata, messagesize);
1193                 if(write(mfd[1], mdata, n) != n)
1194                         error("mount write");
1195         }
1196 }
1197
1198 void
1199 reader(void)
1200 {
1201         ulong t;
1202         Dir *d;
1203         Mailbox *mb;
1204
1205         sleep(15*1000);
1206         for(;;){
1207                 t = time(0);
1208                 qlock(&mbllock);
1209                 for(mb = mbl; mb != nil; mb = mb->next){
1210                         assert(mb->refs > 0);
1211                         if(mb->waketime != 0 && t > mb->waketime){
1212                                 qlock(mb);
1213                                 mb->waketime = 0;
1214                                 break;
1215                         }
1216
1217                         if(mb->d == nil || mb->d->name == nil)
1218                                 continue;
1219                         d = dirstat(mb->path);
1220                         if(d == nil)
1221                                 continue;
1222
1223                         qlock(mb);
1224                         if(mb->d)
1225                         if(d->qid.path != mb->d->qid.path
1226                            || d->qid.vers != mb->d->qid.vers){
1227                                 free(d);
1228                                 break;
1229                         }
1230                         qunlock(mb);
1231                         free(d);
1232                 }
1233                 qunlock(&mbllock);
1234                 if(mb != nil){
1235                         syncmbox(mb, 1);
1236                         qunlock(mb);
1237                 } else
1238                         sleep(15*1000);
1239         }
1240 }
1241
1242 int
1243 newid(void)
1244 {
1245         int rv;
1246         static int id;
1247         static Lock idlock;
1248
1249         lock(&idlock);
1250         rv = ++id;
1251         unlock(&idlock);
1252
1253         return rv;
1254 }
1255
1256 void
1257 error(char *s)
1258 {
1259         postnote(PNGROUP, getpid(), "die yankee pig dog");
1260         fprint(2, "%s: %s: %r\n", argv0, s);
1261         exits(s);
1262 }
1263
1264
1265 typedef struct Ignorance Ignorance;
1266 struct Ignorance
1267 {
1268         Ignorance *next;
1269         char    *str;           /* string */
1270         int     partial;        /* true if not exact match */
1271 };
1272 Ignorance *ignorance;
1273
1274 /*
1275  *  read the file of headers to ignore 
1276  */
1277 void
1278 readignore(void)
1279 {
1280         char *p;
1281         Ignorance *i;
1282         Biobuf *b;
1283
1284         if(ignorance != nil)
1285                 return;
1286
1287         b = Bopen("/mail/lib/ignore", OREAD);
1288         if(b == 0)
1289                 return;
1290         while(p = Brdline(b, '\n')){
1291                 p[Blinelen(b)-1] = 0;
1292                 while(*p && (*p == ' ' || *p == '\t'))
1293                         p++;
1294                 if(*p == '#')
1295                         continue;
1296                 i = malloc(sizeof(Ignorance));
1297                 if(i == 0)
1298                         break;
1299                 i->partial = strlen(p);
1300                 i->str = strdup(p);
1301                 if(i->str == 0){
1302                         free(i);
1303                         break;
1304                 }
1305                 i->next = ignorance;
1306                 ignorance = i;
1307         }
1308         Bterm(b);
1309 }
1310
1311 int
1312 ignore(char *p)
1313 {
1314         Ignorance *i;
1315
1316         readignore();
1317         for(i = ignorance; i != nil; i = i->next)
1318                 if(cistrncmp(i->str, p, i->partial) == 0)
1319                         return 1;
1320         return 0;
1321 }
1322
1323 int
1324 hdrlen(char *p, char *e)
1325 {
1326         char *ep;
1327
1328         ep = p;
1329         do {
1330                 ep = strchr(ep, '\n');
1331                 if(ep == nil){
1332                         ep = e;
1333                         break;
1334                 }
1335                 ep++;
1336                 if(ep >= e){
1337                         ep = e;
1338                         break;
1339                 }
1340         } while(*ep == ' ' || *ep == '\t');
1341         return ep - p;
1342 }
1343
1344 // rfc2047 non-ascii: =?charset?q?encoded-text?=
1345 int
1346 rfc2047convert(String *s, char *token, int len)
1347 {
1348         char charset[100], decoded[1024], *e, *x;
1349         int l;
1350
1351         if(len == 0)
1352                 return -1;
1353
1354         e = token+len-2;
1355         token += 2;
1356
1357         x = memchr(token, '?', e-token);
1358         if(x == nil || (l=x-token) >= sizeof charset)
1359                 return -1;
1360         memmove(charset, token, l);
1361         charset[l] = 0;
1362
1363         token = x+1;
1364
1365         // bail if it doesn't fit 
1366         if(e-token > sizeof(decoded)-1)
1367                 return -1;
1368
1369         // bail if we don't understand the encoding
1370         if(cistrncmp(token, "b?", 2) == 0){
1371                 token += 2;
1372                 len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
1373                 decoded[len] = 0;
1374         } else if(cistrncmp(token, "q?", 2) == 0){
1375                 token += 2;
1376                 len = decquoted(decoded, token, e, 1);
1377                 if(len > 0 && decoded[len-1] == '\n')
1378                         len--;
1379                 decoded[len] = 0;
1380         } else
1381                 return -1;
1382
1383         if(xtoutf(charset, &x, decoded, decoded+len) <= 0)
1384                 s_append(s, decoded);
1385         else {
1386                 s_append(s, x);
1387                 free(x);
1388         }
1389         return 0;
1390 }
1391
1392 char*
1393 rfc2047start(char *start, char *end)
1394 {
1395         int quests;
1396
1397         if(*--end != '=')
1398                 return nil;
1399         if(*--end != '?')
1400                 return nil;
1401
1402         quests = 0;
1403         for(end--; end >= start; end--){
1404                 switch(*end){
1405                 case '=':
1406                         if(quests == 3 && *(end+1) == '?')
1407                                 return end;
1408                         break;
1409                 case '?':
1410                         ++quests;
1411                         break;
1412                 case ' ':
1413                 case '\t':
1414                 case '\n':
1415                 case '\r':
1416                         /* can't have white space in a token */
1417                         return nil;
1418                 }
1419         }
1420         return nil;
1421 }
1422
1423 // convert a header line
1424 String*
1425 stringconvert(String *s, char *uneaten, int len)
1426 {
1427         char *token, *p, *e;
1428
1429         s = s_reset(s);
1430         p = uneaten;
1431         for(e = p+len; p < e; ){
1432                 while(*p++ == '=' && (token = rfc2047start(uneaten, p))){
1433                         s_nappend(s, uneaten, token-uneaten);
1434                         if(rfc2047convert(s, token, p - token) < 0)
1435                                 s_nappend(s, token, p - token);
1436                         uneaten = p;
1437                         for(; p<e && isspace(*p);)
1438                                 p++;
1439                         if(p+2 < e && p[0] == '=' && p[1] == '?')
1440                                 uneaten = p;    // paste
1441                 }
1442         }
1443         if(p > uneaten)
1444                 s_nappend(s, uneaten, p-uneaten);
1445         return s;
1446 }
1447
1448 int
1449 readheader(Message *m, char *buf, int off, int cnt)
1450 {
1451         char *p, *e;
1452         int n, ns;
1453         char *to = buf;
1454         String *s;
1455
1456         p = m->header;
1457         e = m->hend;
1458         s = nil;
1459
1460         // copy in good headers
1461         while(cnt > 0 && p < e){
1462                 n = hdrlen(p, e);
1463                 if(ignore(p)){
1464                         p += n;
1465                         continue;
1466                 }
1467
1468                 // rfc2047 processing
1469                 s = stringconvert(s, p, n);
1470                 ns = s_len(s);
1471                 if(off > 0){
1472                         if(ns <= off){
1473                                 off -= ns;
1474                                 p += n;
1475                                 continue;
1476                         }
1477                         ns -= off;
1478                 }
1479                 if(ns > cnt)
1480                         ns = cnt;
1481                 memmove(to, s_to_c(s)+off, ns);
1482                 to += ns;
1483                 p += n;
1484                 cnt -= ns;
1485                 off = 0;
1486         }
1487
1488         s_free(s);
1489         return to - buf;
1490 }
1491
1492 int
1493 headerlen(Message *m)
1494 {
1495         char buf[1024];
1496         int i, n;
1497
1498         if(m->hlen >= 0)
1499                 return m->hlen;
1500         for(n = 0; ; n += i){
1501                 i = readheader(m, buf, n, sizeof(buf));
1502                 if(i <= 0)
1503                         break;
1504         }
1505         m->hlen = n;
1506         return n;
1507 }
1508
1509 QLock hashlock;
1510
1511 uint
1512 hash(ulong ppath, char *name)
1513 {
1514         uchar *p;
1515         uint h;
1516
1517         h = 0;
1518         for(p = (uchar*)name; *p; p++)
1519                 h = h*7 + *p;
1520         h += ppath;
1521
1522         return h % Hsize;
1523 }
1524
1525 Hash*
1526 hlook(ulong ppath, char *name)
1527 {
1528         int h;
1529         Hash *hp;
1530
1531         qlock(&hashlock);
1532         h = hash(ppath, name);
1533         for(hp = htab[h]; hp != nil; hp = hp->next)
1534                 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1535                         qunlock(&hashlock);
1536                         return hp;
1537                 }
1538         qunlock(&hashlock);
1539         return nil;
1540 }
1541
1542 void
1543 henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
1544 {
1545         int h;
1546         Hash *hp, **l;
1547
1548         qlock(&hashlock);
1549         h = hash(ppath, name);
1550         for(l = &htab[h]; *l != nil; l = &(*l)->next){
1551                 hp = *l;
1552                 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1553                         hp->m = m;
1554                         hp->mb = mb;
1555                         hp->qid = qid;
1556                         qunlock(&hashlock);
1557                         return;
1558                 }
1559         }
1560
1561         *l = hp = emalloc(sizeof(*hp));
1562         hp->m = m;
1563         hp->mb = mb;
1564         hp->qid = qid;
1565         hp->name = name;
1566         hp->ppath = ppath;
1567         qunlock(&hashlock);
1568 }
1569
1570 void
1571 hfree(ulong ppath, char *name)
1572 {
1573         int h;
1574         Hash *hp, **l;
1575
1576         qlock(&hashlock);
1577         h = hash(ppath, name);
1578         for(l = &htab[h]; *l != nil; l = &(*l)->next){
1579                 hp = *l;
1580                 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1581                         hp->mb = nil;
1582                         *l = hp->next;
1583                         free(hp);
1584                         break;
1585                 }
1586         }
1587         qunlock(&hashlock);
1588 }
1589
1590 int
1591 hashmboxrefs(Mailbox *mb)
1592 {
1593         int h;
1594         Hash *hp;
1595         int refs = 0;
1596
1597         qlock(&hashlock);
1598         for(h = 0; h < Hsize; h++){
1599                 for(hp = htab[h]; hp != nil; hp = hp->next)
1600                         if(hp->mb == mb)
1601                                 refs++;
1602         }
1603         qunlock(&hashlock);
1604         return refs;
1605 }
1606
1607 void
1608 post(char *name, char *envname, int srvfd)
1609 {
1610         int fd;
1611         char buf[32];
1612
1613         fd = create(name, OWRITE, 0600);
1614         if(fd < 0)
1615                 error("post failed");
1616         sprint(buf, "%d",srvfd);
1617         if(write(fd, buf, strlen(buf)) != strlen(buf))
1618                 error("srv write");
1619         close(fd);
1620         putenv(envname, name);
1621 }