7 typedef struct Fid Fid;
17 Message *mtop; /* top level message */
19 long foff; /* offset/DIRLEN of finger */
20 Message *fptr; /* pointer to message at off */
21 int fvers; /* mailbox version when finger was saved */
27 void *erealloc(void*, ulong);
31 int readheader(Message*, char*, int, int);
32 void post(char*, char*, int);
34 char *rflush(Fid*), *rauth(Fid*),
35 *rattach(Fid*), *rwalk(Fid*),
36 *ropen(Fid*), *rcreate(Fid*),
37 *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
38 *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
41 char *(*fcalls[])(Fid*) = {
57 char Eperm[] = "permission denied";
58 char Enotdir[] = "not a directory";
59 char Enoauth[] = "upas/fs: authentication not required";
60 char Enotexist[] = "file does not exist";
61 char Einuse[] = "file in use";
62 char Eexist[] = "file exists";
63 char Enotowner[] = "not owner";
64 char Eisopen[] = "file already open for I/O";
65 char Excl[] = "exclusive use file already open";
66 char Ename[] = "illegal name";
67 char Ebadctl[] = "unknown control message";
68 char Ebadargs[] = "invalid arguments";
69 char Enotme[] = "path not served by this file server";
78 [Qdisposition] "disposition",
81 [Qfilename] "filename",
86 [Qinreplyto] "inreplyto",
88 [Qmessageid] "messageid",
89 [Qmimeheader] "mimeheader",
92 [Qrawheader] "rawheader",
94 [Qreferences] "references",
101 [Qunixdate] "unixdate",
102 [Qunixheader] "unixheader",
122 ulong cachetarg = Maxcache;
126 static int messagesize = 8*1024 + IOHDRSZ;
128 static char hbuf[32*1024];
129 static uchar mbuf[16*1024 + IOHDRSZ];
130 static uchar mdata[16*1024 + IOHDRSZ];
131 static ulong path; /* incremented for each new file */
132 static Hash *htab[Hsize];
136 static uintptr bos = 0xd0000000; /* pc kernel specific */
138 #define onstack(x) ((uintptr)(x) >= bos)
139 #define intext(x) ((char*)(x) <= end)
140 #define validgptr(x) assert(!onstack(x) && !intext(x))
146 assert(m->refs < 100);
151 assert(m->next != m);
152 if(m->end < m->start)
154 if(m->ballocd && (m->start <= m->body && m->end >= m->body))
156 if(m->end - m->start > Maxmsg)
160 if(m->fileid != 0 && m->fileid <= 1000000ull<<8)
165 sanembmsg(Mailbox *mb, Message *m)
169 if(m->start > end && m->size == 0)
171 if(m->fileid <= 1000000ull<<8)
183 assert(f->mtop->refs > 0);
187 if(Topmsg(f->mb, f->m))
188 assert(f->m->refs > 0);
196 for(f = fids; f; f = f->next)
204 char buf[SHA1dlen*2 + 1];
207 u = va_arg(f->args, uchar*);
208 if(u == 0 && f->flags & FmtSharp)
209 return fmtstrcpy(f, "-");
211 return fmtstrcpy(f, "<nildigest>");
212 for(i = 0; i < SHA1dlen; i++)
213 sprint(buf + 2*i, "%2.2ux", u[i]);
214 return fmtstrcpy(f, buf);
223 v = va_arg(f->args, uvlong);
224 if(f->flags & FmtSharp)
226 return fmtstrcpy(f, "");
227 strcpy(buf, ctime(v>>8));
229 return fmtstrcpy(f, buf);
239 v = va_arg(f->args, uvlong);
243 snprint(buf, sizeof buf, "%llud.%.2d", v>>8, seq);
244 return fmtstrcpy(f, buf);
248 mpair(Mailbox *mb, Message *m)
260 char buf[128], *p, *e;
266 mp = va_arg(f->args, Mpair);
269 if(m == nil || mb == nil)
270 return fmtstrcpy(f, "<P nil>");
272 for(; !Topmsg(mb, m); m = m->whole){
281 e = buf + sizeof buf;
284 p = seprint(p, e, ".../");
286 p = seprint(p, e, "%s/", t[i]->name);
288 seprint(p, e, "%s", t[0]->name);
289 return fmtstrcpy(f, buf);
295 fprint(2, "usage: upas/fs [-DSbdlmnps] [-c cachetarg] [-f mboxfile] [-m mountpoint]\n");
300 notifyf(void *, char *s)
302 if(strncmp(s, "interrupt", 9) == 0)
304 if(strncmp(s, "die: yankee pig dog", 19) != 0)
305 /* don't want to call syslog from notify handler */
306 fprint(2, "upas/fs: user: %s; note: %s\n", getuser(), s);
313 char buf[128], buf2[32], *p, *e;
316 e = buf + sizeof buf;
317 p = seprint(buf, e, "%s", v[0]);
319 p = seprint(p, e, " %s", v[i]);
320 snprint(buf2, sizeof buf2, "#p/%d/args", getpid());
321 if((fd = open(buf2, OWRITE)) >= 0){
322 write(fd, buf, p - buf);
332 n = strtoul(s, &s, 0);
350 main(int argc, char *argv[])
352 char maildir[Pathlen], mbox[Pathlen], srvfile[64], **v;
353 char *mboxfile, *err;
354 int p[2], nodflt, srvpost;
373 cachetarg = ntoi(EARGF(usage()));
377 mainmem->flags |= POOL_PARANOIA;
380 mboxfile = EARGF(usage());
389 mntpt = EARGF(usage());
406 fmtinstall('A', Afmt);
407 fmtinstall('D', Dfmt);
408 fmtinstall(L'Δ', Δfmt);
409 fmtinstall('F', fcallfmt);
410 fmtinstall('H', encodefmt); /* forces tls stuff */
411 fmtinstall('P', Pfmt);
414 error("pipe failed");
419 strcpy(user, getuser());
421 snprint(maildir, sizeof(maildir), "/mail/fs");
424 if(mboxfile == nil && !nodflt){
425 snprint(mbox, sizeof mbox, "/mail/box/%s/mbox", user);
430 if(err = newmbox(mboxfile, "mbox", 0, 0))
431 sysfatal("opening %s: %s", mboxfile, err);
433 switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
437 henter(PATH(0, Qtop), dirtab[Qctl],
438 (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
446 close(p[0]); /* don't deadlock if child fails */
448 snprint(srvfile, sizeof srvfile, "/srv/upasfs.%s", user);
449 post(srvfile, "upasfs", p[1]);
451 if(mount(p[1], -1, mntpt, MREPL, "") < 0)
452 error("mount failed");
458 sputc(char *p, char *e, int c)
466 seappend(char *s, char *e, char *a, int n)
480 fileinfo(Mailbox *mb, Message *m, int t, char **pp)
484 static char buf[64 + 512];
503 switch(m->disposition){
516 len = snprint(buf, sizeof buf, "%#Δ", m->fileid);
523 p = flagbuf(buf, m->flags);
542 len = snprint(buf, sizeof buf, "%lud", m->lines);
548 if(strncmp(m->start, "From ", 5) == 0)
549 if(e = strchr(p, '\n'))
575 e = buf + sizeof buf;
577 for(i = 0; i < nelem(m->references); i++){
578 if(m->references[i] == 0)
580 s = seprint(s, e, "%s\n", m->references[i]);
587 if(m->replyto != nil)
589 else if(m->from != nil)
591 else if(m->sender != nil)
593 else if(m->unixfrom != nil)
603 len = snprint(buf, sizeof buf, "%lud", m->size);
611 len = rtab[m->type].l;
615 len = snprint(buf, sizeof buf, "%#Δ", m->fileid);
619 len = snprint(buf, sizeof buf, "%D", m->fileid);
627 len = snprint(buf, sizeof buf, "%A", m->digest);
663 readinfo(Mailbox *mb, Message *m, char *buf, long off, int count)
669 if(m->infolen > 0 && off >= m->infolen)
674 for(i = 0; s < e; i++){
675 if(i == nelem(infofields)){
676 m->infolen = s - buf + off0;
679 n = fileinfo(mb, m, infofields[i], &p);
700 mkstat(Dir *d, Mailbox *mb, Message *m, int t)
709 d->qid.type = QTFILE;
712 if(m && m->fileid > 1000000ull)
713 d->atime = m->fileid >> 8;
715 d->atime = mb->d->mtime;
723 d->mode = DMDIR|0555;
724 d->atime = d->mtime = time(0);
726 d->qid.path = PATH(0, Qtop);
731 d->mode = DMDIR|0555;
733 d->qid.path = PATH(mb->id, Qmbox);
735 d->qid.vers = mb->vers;
739 d->mode = DMDIR|0555;
741 d->qid.path = PATH(m->id, Qdir);
747 d->atime = d->mtime = time(0);
749 d->qid.path = PATH(0, Qctl);
754 d->length = readheader(m, hbuf, 0, sizeof hbuf);
760 d->atime = d->mtime = time(0);
762 d->qid.path = PATH(mb->id, Qmboxctl);
766 d->length = readinfo(mb, m, hbuf, 0, sizeof hbuf);
767 d->qid.path = PATH(m->id, t);
772 if(strncmp(m->start, "From ", 5) == 0)
773 if(e = strchr(p, '\n'))
776 d->length = m->size - (p - m->start);
781 d->length = m->rawbsize;
786 if(mb->addfrom && Topmsg(mb, m)){
788 d->length += strlen(m->unixheader);
796 d->length = fileinfo(mb, m, t, &p);
797 d->qid.path = PATH(m->id, t);
808 return "max messagesize too small";
809 if(thdr.msize < messagesize)
810 messagesize = thdr.msize;
811 rhdr.msize = messagesize;
812 if(strncmp(thdr.version, "9P2000", 6) != 0)
813 return "unknown 9P version";
815 rhdr.version = "9P2000";
816 for(f = fids; f; f = f->next)
840 f->qid.path = PATH(0, Qtop);
844 if(strcmp(thdr.uname, user) != 0)
850 doclone(Fid *f, int nfid)
862 msgincref(gettopmsg(nf->mb, nf->m));
863 if(nf->mtop = f->mtop){
874 /* slow? binary search? */
880 for(i = 0; i < Qmax; i++)
882 if(strcmp(dirtab[i], name) == 0)
888 dowalk(Fid *f, char *name)
895 t = FILE(f->qid.path);
904 /* this must catch everything except . and .. */
907 t1 = FILE(f->qid.path);
908 if((t1 == Qmbox || t1 == Qdir) && *name >= 'a' && *name <= 'z'){
909 h = hlook(f->qid.path, "xxx"); /* sleezy speedup */
914 h = hlook(f->qid.path, name);
917 msgdecref(f->mb, gettopmsg(f->mb, f->m));
918 if(f->mb && f->mb != h->mb)
923 msgincref(gettopmsg(f->mb, f->m));
938 f->qid.path = PATH(f->m->id, t1); /* sleezy speedup */
941 }else if((p = strchr(name, '.')) != nil && *name != '.'){
953 if(strcmp(name, ".") == 0)
956 if(f->qid.type != QTDIR)
959 if(strcmp(name, "..") == 0){
962 f->qid.path = PATH(0, Qtop);
967 f->qid.path = PATH(0, Qtop);
978 if(Topmsg(f->mb, f->m)){
979 f->qid.path = PATH(f->mb->id, Qmbox);
981 f->qid.vers = f->mb->d->qid.vers;
982 msgdecref(f->mb, f->mtop);
983 msgdecref(f->mb, f->m);
984 f->m = f->mtop = nil;
986 /* refs don't change; still the same message */
988 f->qid.path = PATH(f->m->id, Qdir);
1013 /* clone if requested */
1014 if(thdr.newfid != thdr.fid){
1015 nf = doclone(f, thdr.newfid);
1017 return "new fid in use";
1021 /* if it's just a clone, return */
1022 if(thdr.nwname == 0 && nf != nil)
1025 /* walk each element */
1027 for(i = 0; i < thdr.nwname; i++){
1028 rv = dowalk(f, thdr.wname[i]);
1034 rhdr.wqid[i] = f->qid;
1038 /* we only error out if no walk */
1052 file = FILE(f->qid.path);
1053 if(thdr.mode != OREAD)
1054 if(file != Qctl && file != Qmboxctl && file != Qflags)
1057 /* make sure we've decoded */
1059 cachebody(f->mb, f->m);
1062 putcache(f->mb, f->m);
1078 readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
1087 mkstat(&d, nil, nil, Qctl);
1088 m = convD2M(&d, &buf[n], blen);
1090 if(m <= BIT16SZ || m > cnt)
1097 for(mb = mbl; mb != nil; mb = mb->next){
1098 mkstat(&d, mb, nil, Qmbox);
1099 m = convD2M(&d, &buf[n], blen - n);
1101 if(m <= BIT16SZ || m > cnt)
1112 readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
1121 mkstat(&d, f->mb, nil, Qmboxctl);
1122 m = convD2M(&d, &buf[n], blen);
1124 if(m <= BIT16SZ || m > cnt){
1134 /* to avoid n**2 reads of the directory, use a saved finger pointer */
1135 if(f->mb->vers == f->fvers && off >= f->foff && f->fptr != nil){
1139 msg = f->mb->root->part;
1143 for(; cnt > 0 && msg != nil; msg = msg->next){
1144 /* act like deleted files aren't there */
1148 mkstat(&d, f->mb, msg, Qdir);
1149 m = convD2M(&d, &buf[n], blen - n);
1151 if(m <= BIT16SZ || m > cnt)
1159 /* save a finger pointer for next read of the mbox directory */
1162 f->fvers = f->mb->vers;
1168 readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
1177 for(i = 0; i < Qmax; i++){
1178 mkstat(&d, f->mb, f->m, i);
1179 m = convD2M(&d, &buf[n], blen - n);
1181 if(m <= BIT16SZ || m > cnt)
1188 for(msg = f->m->part; msg != nil; msg = msg->next){
1189 mkstat(&d, f->mb, msg, Qdir);
1190 m = convD2M(&d, &buf[n], blen - n);
1192 if(m <= BIT16SZ || m > cnt)
1204 mboxctlread(Mailbox *mb, char **p)
1206 static char buf[128];
1209 return snprint(*p, sizeof buf, "%s\n%ld\n", mb->path, mb->vers);
1222 if(cnt > messagesize - IOHDRSZ)
1223 cnt = messagesize - IOHDRSZ;
1224 rhdr.data = (char*)mbuf;
1227 t = FILE(f->qid.path);
1228 if(f->qid.type & QTDIR){
1231 n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
1233 }else if(t == Qmbox) {
1237 n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
1239 }else if(t == Qmboxctl)
1242 n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
1252 i = mboxctlread(f->mb, &p);
1256 cacheheaders(f->mb, f->m);
1257 rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
1258 putcache(f->mb, f->m);
1261 if(cnt > sizeof mbuf)
1263 rhdr.count = readinfo(f->mb, f->m, (char*)mbuf, off, cnt);
1266 if(f->mb->addfrom && Topmsg(f->mb, f->m)){
1267 cacheheaders(f->mb, f->m);
1268 p = f->m->unixheader;
1269 if(off < strlen(p)){
1270 rhdr.count = strlen(p + off);
1271 memmove(mbuf, p + off, rhdr.count);
1275 putcache(f->mb, f->m);
1278 i = fileinfo(f->mb, f->m, t, &p);
1283 if(cnt > sizeof mbuf)
1285 memmove(mbuf, p + off, cnt);
1294 modflags(Mailbox *mb, Message *m, char *p)
1300 if(err = txflags(p, &f))
1303 if(mb->modflags != nil)
1304 mb->modflags(mb, m, f);
1306 m->cstate |= Cidxstale;
1314 char *argvbuf[1024], **argv, file[Pathlen], *err, *v0;
1315 int i, t, argc, flags;
1318 t = FILE(f->qid.path);
1319 rhdr.count = thdr.count;
1323 if(thdr.data[thdr.count - 1] == '\n')
1324 thdr.data[thdr.count - 1] = 0;
1326 thdr.data[thdr.count] = 0;
1330 memset(argvbuf, 0, sizeof argvbuf);
1331 argc = tokenize(thdr.data, argv, nelem(argvbuf) - 1);
1334 if(strcmp(argv[0], "open") == 0 || strcmp(argv[0], "create") == 0){
1335 if(argc == 1 || argc > 3)
1337 mboxpathbuf(file, sizeof file, getlog(), argv[1]);
1339 if(strchr(argv[2], '/') != nil)
1340 return "/ not allowed in mailbox name";
1344 if(strcmp(argv[0], "create") == 0)
1346 return newmbox(file, argv[2], flags, 0);
1348 if(strcmp(argv[0], "close") == 0){
1351 for(i = 1; i < argc; i++)
1355 if(strcmp(argv[0], "delete") == 0){
1358 delmessages(argc - 1, argv + 1);
1361 if(strcmp(argv[0], "flag") == 0){
1364 return flagmessages(argc - 1, argv + 1);
1366 if(strcmp(argv[0], "remove") == 0){
1383 for(; *argv; argv++){
1384 mboxpathbuf(file, sizeof file, getlog(), *argv);
1385 if(err = newmbox(file, nil, 0, 0))
1388 // return "remove not implemented";
1389 if(err = removembox(file, flags))
1394 if(strcmp(argv[0], "rename") == 0){
1405 return mboxrename(argv[0], argv[1], flags);
1409 if(f->mb && f->mb->ctl){
1410 argc = tokenize(thdr.data, argv, nelem(argvbuf));
1413 return f->mb->ctl(f->mb, argc, argv);
1418 * modifying flags on subparts is a little strange.
1422 m = gettopmsg(f->mb, f->m);
1423 err = modflags(f->mb, m, thdr.data);
1424 // premature optimization? flags not written immediately.
1425 // if(err == nil && f->m->cstate&Cidxstale)
1426 // wridxfile(f->mb); /* syncmbox(f->mb, 1); */
1444 msgdecref(f->mb, f->mtop);
1448 msgdecref(f->mb, gettopmsg(f->mb, f->m));
1449 f->m = f->mtop = nil;
1453 assert(mb->refs > 0);
1467 if(f->m->deleted == 0)
1468 mailplumb(f->mb, f->m, 1);
1469 f->m->deleted = Deleted;
1480 if(FILE(f->qid.path) == Qmbox){
1485 mkstat(&d, f->mb, f->m, FILE(f->qid.path));
1486 rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ);
1503 for(f = fids; f; f = f->next)
1506 else if(!ff && !f->busy)
1513 f = emalloc(sizeof *f);
1522 fidmboxrefs(Mailbox *mb)
1527 for(f = fids; f; f = f->next){
1540 /* start a process to watch the mailboxes*/
1541 if(plumbing || biffing)
1542 switch(rfork(RFPROC|RFMEM)){
1555 * reading from a pipe or a network device
1556 * will give an error after a few eof reads
1557 * however, we cannot tell the difference
1558 * between a zero-length read and an interrupt
1559 * on the processes writing to us,
1560 * so we wait for the error
1563 n = read9pmsg(mfd[0], mdata, messagesize);
1568 if(convM2S(mdata, n, &thdr) == 0)
1572 fprint(2, "%s:<-%F\n", argv0, &thdr);
1574 rhdr.data = (char*)mdata + messagesize;
1575 if(!fcalls[thdr.type])
1576 err = "bad fcall type";
1578 err = fcalls[thdr.type](newfid(thdr.fid));
1583 rhdr.type = thdr.type + 1;
1584 rhdr.fid = thdr.fid;
1586 rhdr.tag = thdr.tag;
1588 fprint(2, "%s:->%F\n", argv0, &rhdr);
1589 n = convS2M(&rhdr, mdata, messagesize);
1590 if(write(mfd[1], mdata, n) != n)
1591 error("mount write");
1595 static char *readerargv[] = {"upas/fs", "plumbing", 0};
1604 setname(readerargv);
1609 for(mb = mbl; mb != nil; mb = mb->next){
1610 assert(mb->refs > 0);
1611 if(mb->waketime != 0 && t >= mb->waketime){
1617 if(mb->d == nil || mb->d->name == nil)
1619 d = dirstat(mb->path);
1625 if(d->qid.path != mb->d->qid.path
1626 || d->qid.vers != mb->d->qid.vers){
1659 syskillpg(getpid());
1660 eprint("upas/fs: fatal error: %s: %r\n", s);
1665 typedef struct Ignorance Ignorance;
1672 Ignorance *ignorance;
1675 * read the file of headers to ignore
1684 if(ignorance != nil)
1687 b = Bopen("/mail/lib/ignore", OREAD);
1690 while(p = Brdline(b, '\n')){
1691 p[Blinelen(b) - 1] = 0;
1692 while(*p && (*p == ' ' || *p == '\t'))
1696 i = emalloc(sizeof *i);
1703 i->next = ignorance;
1715 for(i = ignorance; i != nil; i = i->next)
1716 if(cistrncmp(i->str, p, i->len) == 0)
1722 readheader(Message *m, char *buf, int off, int cnt)
1724 char *s, *end, *se, *p, *e, *to;
1730 s = emalloc(salloc = 2048);
1733 /* copy in good headers */
1734 while(cnt > 0 && p < e){
1742 s = erealloc(s, salloc = n + 1);
1745 se = rfc2047(s, end, p, n, 0);
1757 memmove(to, s + off, ns);
1770 hash(ulong ppath, char *name)
1776 for(p = (uchar*)name; *p; p++)
1784 hlook(ulong ppath, char *name)
1790 h = hash(ppath, name);
1791 for(hp = htab[h]; hp != nil; hp = hp->next)
1792 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1801 henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
1808 h = hash(ppath, name);
1809 for(l = &htab[h]; *l != nil; l = &(*l)->next){
1811 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1820 *l = hp = emalloc(sizeof(*hp));
1830 hfree(ulong ppath, char *name)
1836 h = hash(ppath, name);
1837 for(l = &htab[h]; *l != nil; l = &(*l)->next){
1839 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1850 hashmboxrefs(Mailbox *mb)
1857 for(h = 0; h < Hsize; h++)
1858 for(hp = htab[h]; hp != nil; hp = hp->next)
1872 for(mb = mbl; mb; mb = mb->next){
1874 refs = fidmboxrefs(mb) + 1;
1875 if(refs != mb->refs){
1876 eprint("%s:%s ref mismatch got %d expected %d\n", mb->name, mb->path, refs, mb->refs);
1881 // qunlock(&mbllock);
1885 post(char *name, char *envname, int srvfd)
1890 fd = create(name, OWRITE, 0600);
1892 error("post failed");
1893 snprint(buf, sizeof buf, "%d", srvfd);
1894 if(write(fd, buf, strlen(buf)) != strlen(buf))
1897 putenv(envname, name);