10 OPERM = 0x3, // mask of all permission types in open mode
13 typedef struct Fid Fid;
24 Message *mtop; // top level message
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
32 ulong path; // incremented for each new file
36 int messagesize = 4*1024+IOHDRSZ;
37 uchar mdata[8*1024+IOHDRSZ];
38 uchar mbuf[8*1024+IOHDRSZ];
52 void *erealloc(void*, ulong);
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);
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*),
69 char *(*fcalls[])(Fid*) = {
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";
105 [Qdisposition] "disposition",
106 [Qfilename] "filename",
110 [Qinreplyto] "inreplyto",
112 [Qmimeheader] "mimeheader",
113 [Qmessageid] "messageid",
115 [Qrawunix] "rawunix",
116 [Qrawbody] "rawbody",
117 [Qrawheader] "rawheader",
118 [Qreplyto] "replyto",
120 [Qsubject] "subject",
123 [Qunixdate] "unixdate",
124 [Qunixheader] "unixheader",
143 fprint(2, "usage: upas/fs [-bdlnps] [-f mboxfile] [-m mountpoint]\n");
148 notifyf(void *, char *s)
150 if(strstr(s, "alarm") || strstr(s, "interrupt"))
156 main(int argc, char *argv[])
158 int p[2], std, nodflt;
161 char *mboxfile, *err;
179 mboxfile = EARGF(usage());
182 mntpt = EARGF(usage());
206 error("pipe failed");
211 strcpy(user, getuser());
213 snprint(maildir, sizeof(maildir), "/mail/fs");
216 if(mboxfile == nil && !nodflt){
217 snprint(mbox, sizeof(mbox), "/mail/box/%s/mbox", user);
223 fmtinstall('F', fcallfmt);
226 err = newmbox(mboxfile, "mbox", std);
228 sysfatal("opening %s: %s", mboxfile, err);
231 switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
235 henter(PATH(0, Qtop), dirtab[Qctl],
236 (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
239 postnote(PNGROUP, getpid(), "die yankee pig dog");
242 close(p[0]); /* don't deadlock if child fails */
244 sprint(srvfile, "/srv/upasfs.%s", user);
245 post(srvfile, "upasfs", p[1]);
247 if(mount(p[1], -1, mntpt, MREPL, "") < 0)
248 error("mount failed");
255 fileinfo(Message *m, int t, char **pp)
265 len = m->bend - m->body;
269 p = s_to_c(m->bcc822);
275 p = s_to_c(m->cc822);
280 switch(m->disposition){
292 p = s_to_c(m->date822);
294 } else if(m->unixdate != nil){
295 p = s_to_c(m->unixdate);
301 p = s_to_c(m->filename);
307 p = s_to_c(m->inreplyto822);
313 p = s_to_c(m->messageid822);
319 p = s_to_c(m->from822);
321 } else if(m->unixfrom != nil){
322 p = s_to_c(m->unixfrom);
334 len = strlen(m->lines);
338 if(strncmp(m->start, "From ", 5) == 0){
365 if(m->replyto822 != nil){
366 p = s_to_c(m->replyto822);
368 } else if(m->from822 != nil){
369 p = s_to_c(m->from822);
371 } else if(m->sender822 != nil){
372 p = s_to_c(m->sender822);
374 } else if(m->unixfrom != nil){
375 p = s_to_c(m->unixfrom);
381 p = s_to_c(m->sender822);
388 p = s_to_c(m->subject822);
394 p = s_to_c(m->to822);
406 p = s_to_c(m->unixdate);
412 p = s_to_c(m->unixheader);
413 len = s_len(m->unixheader);
418 p = s_to_c(m->sdigest);
448 readinfo(Message *m, char *buf, long off, int count)
456 for(i = 0; len < count && infofields[i] >= 0; i++){
457 n = fileinfo(m, infofields[i], &p);
458 s = stringconvert(s, p, n);
474 memmove(buf+len, p, n);
482 mkstat(Dir *d, Mailbox *mb, Message *m, int t)
491 d->qid.type = QTFILE;
494 if(mb != nil && mb->d != nil){
495 d->atime = mb->d->atime;
496 d->mtime = mb->d->mtime;
505 d->mode = DMDIR|0555;
506 d->atime = d->mtime = time(0);
508 d->qid.path = PATH(0, Qtop);
513 d->mode = DMDIR|0555;
515 d->qid.path = PATH(mb->id, Qmbox);
517 d->qid.vers = mb->vers;
521 d->mode = DMDIR|0555;
523 d->qid.path = PATH(m->id, Qdir);
529 d->atime = d->mtime = time(0);
531 d->qid.path = PATH(0, Qctl);
536 d->atime = d->mtime = time(0);
538 d->qid.path = PATH(mb->id, Qmboxctl);
542 d->length = readinfo(m, nil, 0, 1<<30);
543 d->qid.path = PATH(m->id, t);
547 d->length = fileinfo(m, t, &p);
548 d->qid.path = PATH(m->id, t);
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";
566 rhdr.version = "9P2000";
567 for(f = fids; f; f = f->next)
592 f->qid.path = PATH(0, Qtop);
596 if(strcmp(thdr.uname, user) != 0)
602 doclone(Fid *f, int nfid)
626 dowalk(Fid *f, char *name)
633 t = FILE(f->qid.path);
643 // this must catch everything except . and ..
645 h = hlook(f->qid.path, name);
663 } else if((p = strchr(name, '.')) != nil && *name != '.'){
675 if(strcmp(name, ".") == 0)
678 if(f->qid.type != QTDIR)
681 if(strcmp(name, "..") == 0){
684 f->qid.path = PATH(0, Qtop);
689 f->qid.path = PATH(0, Qtop);
700 if(f->m->whole == f->mb->root){
701 f->qid.path = PATH(f->mb->id, Qmbox);
703 f->qid.vers = f->mb->d->qid.vers;
704 msgdecref(f->mb, f->mtop);
705 f->m = f->mtop = nil;
708 f->qid.path = PATH(f->m->id, Qdir);
732 /* clone if requested */
733 if(thdr.newfid != thdr.fid){
734 nf = doclone(f, thdr.newfid);
736 return "new fid in use";
740 /* if it's just a clone, return */
741 if(thdr.nwname == 0 && nf != nil)
744 /* walk each element */
746 for(i = 0; i < thdr.nwname; i++){
747 rv = dowalk(f, thdr.wname[i]);
753 rhdr.wqid[i] = f->qid;
757 /* we only error out if no walk */
772 file = FILE(f->qid.path);
773 if(thdr.mode != OREAD)
774 if(file != Qctl && file != Qmboxctl)
777 // make sure we've decoded
779 if(f->m->decoded == 0)
781 if(f->m->converted == 0)
798 readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
807 mkstat(&d, nil, nil, Qctl);
808 m = convD2M(&d, &buf[n], blen);
810 if(m <= BIT16SZ || m > cnt)
817 for(mb = mbl; mb != nil; mb = mb->next){
818 mkstat(&d, mb, nil, Qmbox);
819 m = convD2M(&d, &buf[n], blen-n);
821 if(m <= BIT16SZ || m > cnt)
832 readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
841 mkstat(&d, f->mb, nil, Qmboxctl);
842 m = convD2M(&d, &buf[n], blen);
844 if(m <= BIT16SZ || m > cnt){
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){
859 msg = f->mb->root->part;
863 for(; cnt > 0 && msg != nil; msg = msg->next){
864 // act like deleted files aren't there
868 mkstat(&d, f->mb, msg, Qdir);
869 m = convD2M(&d, &buf[n], blen-n);
871 if(m <= BIT16SZ || m > cnt)
879 // save a finger pointer for next read of the mbox directory
882 f->fvers = f->mb->vers;
888 readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
897 for(i = 0; i < Qmax; i++){
898 mkstat(&d, f->mb, f->m, i);
899 m = convD2M(&d, &buf[n], blen-n);
901 if(m <= BIT16SZ || m > cnt)
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);
912 if(m <= BIT16SZ || m > cnt)
934 if(cnt > messagesize - IOHDRSZ)
935 cnt = messagesize - IOHDRSZ;
937 rhdr.data = (char*)mbuf;
939 t = FILE(f->qid.path);
940 if(f->qid.type & QTDIR){
943 n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
945 } else if(t == Qmbox) {
949 n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
951 } else if(t == Qmboxctl) {
954 n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
961 if(FILE(f->qid.path) == Qheader){
962 rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
966 if(FILE(f->qid.path) == Qinfo){
967 rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt);
971 i = fileinfo(f->m, FILE(f->qid.path), &p);
975 memmove(mbuf, p + off, cnt);
989 t = FILE(f->qid.path);
990 rhdr.count = thdr.count;
995 if(thdr.data[thdr.count-1] == '\n')
996 thdr.data[thdr.count-1] = 0;
998 thdr.data[thdr.count] = 0;
999 n = tokenize(thdr.data, token, nelem(token));
1002 if(strcmp(token[0], "open") == 0){
1009 mboxpath(token[1], getlog(), file, 0);
1010 err = newmbox(s_to_c(file), nil, 0);
1013 mboxpath(token[1], getlog(), file, 0);
1014 if(strchr(token[2], '/') != nil)
1015 err = "/ not allowed in mailbox name";
1017 err = newmbox(s_to_c(file), token[2], 0);
1023 if(strcmp(token[0], "close") == 0){
1029 if(strcmp(token[0], "delete") == 0){
1032 delmessages(n-1, &token[1]);
1037 if(f->mb && f->mb->ctl){
1040 if(thdr.data[thdr.count-1] == '\n')
1041 thdr.data[thdr.count-1] = 0;
1043 thdr.data[thdr.count] = 0;
1044 n = tokenize(thdr.data, token, nelem(token));
1047 return (*f->mb->ctl)(f->mb, n, token);
1062 msgdecref(f->mb, f->mtop);
1065 f->m = f->mtop = nil;
1069 assert(mb->refs > 0);
1082 if(f->m->deleted == 0)
1083 mailplumb(f->mb, f->m, 1);
1094 if(FILE(f->qid.path) == Qmbox){
1099 mkstat(&d, f->mb, f->m, FILE(f->qid.path));
1100 rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ);
1117 for(f = fids; f; f = f->next)
1120 else if(!ff && !f->busy)
1127 f = emalloc(sizeof *f);
1136 fidmboxrefs(Mailbox *mb)
1141 for(f = fids; f; f = f->next){
1154 /* start a process to watch the mailboxes*/
1156 switch(rfork(RFPROC|RFMEM)){
1168 while((n = read9pmsg(mfd[0], mdata, messagesize)) != 0){
1170 error("mount read");
1171 if(convM2S(mdata, n, &thdr) != n)
1172 error("convM2S format error");
1175 fprint(2, "%s:<-%F\n", argv0, &thdr);
1177 rhdr.data = (char*)mdata + messagesize;
1178 if(!fcalls[thdr.type])
1179 err = "bad fcall type";
1181 err = (*fcalls[thdr.type])(newfid(thdr.fid));
1186 rhdr.type = thdr.type + 1;
1187 rhdr.fid = thdr.fid;
1189 rhdr.tag = thdr.tag;
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");
1209 for(mb = mbl; mb != nil; mb = mb->next){
1210 assert(mb->refs > 0);
1211 if(mb->waketime != 0 && t > mb->waketime){
1217 if(mb->d == nil || mb->d->name == nil)
1219 d = dirstat(mb->path);
1225 if(d->qid.path != mb->d->qid.path
1226 || d->qid.vers != mb->d->qid.vers){
1259 postnote(PNGROUP, getpid(), "die yankee pig dog");
1260 fprint(2, "%s: %s: %r\n", argv0, s);
1265 typedef struct Ignorance Ignorance;
1269 char *str; /* string */
1270 int partial; /* true if not exact match */
1272 Ignorance *ignorance;
1275 * read the file of headers to ignore
1284 if(ignorance != nil)
1287 b = Bopen("/mail/lib/ignore", OREAD);
1290 while(p = Brdline(b, '\n')){
1291 p[Blinelen(b)-1] = 0;
1292 while(*p && (*p == ' ' || *p == '\t'))
1296 i = malloc(sizeof(Ignorance));
1299 i->partial = strlen(p);
1305 i->next = ignorance;
1317 for(i = ignorance; i != nil; i = i->next)
1318 if(cistrncmp(i->str, p, i->partial) == 0)
1324 hdrlen(char *p, char *e)
1330 ep = strchr(ep, '\n');
1340 } while(*ep == ' ' || *ep == '\t');
1344 // rfc2047 non-ascii: =?charset?q?encoded-text?=
1346 rfc2047convert(String *s, char *token, int len)
1348 char charset[100], decoded[1024], *e, *x;
1357 x = memchr(token, '?', e-token);
1358 if(x == nil || (l=x-token) >= sizeof charset)
1360 memmove(charset, token, l);
1365 // bail if it doesn't fit
1366 if(e-token > sizeof(decoded)-1)
1369 // bail if we don't understand the encoding
1370 if(cistrncmp(token, "b?", 2) == 0){
1372 len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
1374 } else if(cistrncmp(token, "q?", 2) == 0){
1376 len = decquoted(decoded, token, e, 1);
1377 if(len > 0 && decoded[len-1] == '\n')
1383 if(xtoutf(charset, &x, decoded, decoded+len) <= 0)
1384 s_append(s, decoded);
1393 rfc2047start(char *start, char *end)
1403 for(end--; end >= start; end--){
1406 if(quests == 3 && *(end+1) == '?')
1416 /* can't have white space in a token */
1423 // convert a header line
1425 stringconvert(String *s, char *uneaten, int len)
1427 char *token, *p, *e;
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);
1437 for(; p<e && isspace(*p);)
1439 if(p+2 < e && p[0] == '=' && p[1] == '?')
1440 uneaten = p; // paste
1444 s_nappend(s, uneaten, p-uneaten);
1449 readheader(Message *m, char *buf, int off, int cnt)
1460 // copy in good headers
1461 while(cnt > 0 && p < e){
1468 // rfc2047 processing
1469 s = stringconvert(s, p, n);
1481 memmove(to, s_to_c(s)+off, ns);
1493 headerlen(Message *m)
1500 for(n = 0; ; n += i){
1501 i = readheader(m, buf, n, sizeof(buf));
1512 hash(ulong ppath, char *name)
1518 for(p = (uchar*)name; *p; p++)
1526 hlook(ulong ppath, char *name)
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){
1543 henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
1549 h = hash(ppath, name);
1550 for(l = &htab[h]; *l != nil; l = &(*l)->next){
1552 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1561 *l = hp = emalloc(sizeof(*hp));
1571 hfree(ulong ppath, char *name)
1577 h = hash(ppath, name);
1578 for(l = &htab[h]; *l != nil; l = &(*l)->next){
1580 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1591 hashmboxrefs(Mailbox *mb)
1598 for(h = 0; h < Hsize; h++){
1599 for(hp = htab[h]; hp != nil; hp = hp->next)
1608 post(char *name, char *envname, int srvfd)
1613 fd = create(name, OWRITE, 0600);
1615 error("post failed");
1616 sprint(buf, "%d",srvfd);
1617 if(write(fd, buf, strlen(buf)) != strlen(buf))
1620 putenv(envname, name);