plan9.c (7547B)
1 #include "common.h" 2 #include <ctype.h> 3 #include <plumb.h> 4 #include <libsec.h> 5 #include "dat.h" 6 7 enum { 8 Buffersize = 64*1024 9 }; 10 11 typedef struct Inbuf Inbuf; 12 struct Inbuf 13 { 14 int fd; 15 uchar *lim; 16 uchar *rptr; 17 uchar *wptr; 18 uchar data[Buffersize+7]; 19 }; 20 21 static void 22 addtomessage(Message *m, uchar *p, int n, int done) 23 { 24 int i, len; 25 26 /* add to message (+ 1 in malloc is for a trailing null) */ 27 if(m->lim - m->end < n){ 28 if(m->start != nil){ 29 i = m->end-m->start; 30 if(done) 31 len = i + n; 32 else 33 len = (4*(i+n))/3; 34 m->start = erealloc(m->start, len + 1); 35 m->end = m->start + i; 36 } else { 37 if(done) 38 len = n; 39 else 40 len = 2*n; 41 m->start = emalloc(len + 1); 42 m->end = m->start; 43 } 44 m->lim = m->start + len; 45 } 46 47 memmove(m->end, p, n); 48 m->end += n; 49 } 50 51 /* */ 52 /* read in a single message */ 53 /* */ 54 static int 55 readmessage(Message *m, Inbuf *inb) 56 { 57 int i, n, done; 58 uchar *p, *np; 59 char sdigest[SHA1dlen*2+1]; 60 char tmp[64]; 61 62 for(done = 0; !done;){ 63 n = inb->wptr - inb->rptr; 64 if(n < 6){ 65 if(n) 66 memmove(inb->data, inb->rptr, n); 67 inb->rptr = inb->data; 68 inb->wptr = inb->rptr + n; 69 i = read(inb->fd, inb->wptr, Buffersize); 70 if(i < 0){ 71 /* if(fd2path(inb->fd, tmp, sizeof tmp) < 0) 72 strcpy(tmp, "unknown mailbox"); jpc */ 73 fprint(2, "error reading '%s': %r\n", tmp); 74 return -1; 75 } 76 if(i == 0){ 77 if(n != 0) 78 addtomessage(m, inb->rptr, n, 1); 79 if(m->end == m->start) 80 return -1; 81 break; 82 } 83 inb->wptr += i; 84 } 85 86 /* look for end of message */ 87 for(p = inb->rptr; p < inb->wptr; p = np+1){ 88 /* first part of search for '\nFrom ' */ 89 np = memchr(p, '\n', inb->wptr - p); 90 if(np == nil){ 91 p = inb->wptr; 92 break; 93 } 94 95 /* 96 * if we've found a \n but there's 97 * not enough room for '\nFrom ', don't do 98 * the comparison till we've read in more. 99 */ 100 if(inb->wptr - np < 6){ 101 p = np; 102 break; 103 } 104 105 if(strncmp((char*)np, "\nFrom ", 6) == 0){ 106 done = 1; 107 p = np+1; 108 break; 109 } 110 } 111 112 /* add to message (+ 1 in malloc is for a trailing null) */ 113 n = p - inb->rptr; 114 addtomessage(m, inb->rptr, n, done); 115 inb->rptr += n; 116 } 117 118 /* if it doesn't start with a 'From ', this ain't a mailbox */ 119 if(strncmp(m->start, "From ", 5) != 0) 120 return -1; 121 122 /* dump trailing newline, make sure there's a trailing null */ 123 /* (helps in body searches) */ 124 if(*(m->end-1) == '\n') 125 m->end--; 126 *m->end = 0; 127 m->bend = m->rbend = m->end; 128 129 /* digest message */ 130 sha1((uchar*)m->start, m->end - m->start, m->digest, nil); 131 for(i = 0; i < SHA1dlen; i++) 132 sprint(sdigest+2*i, "%2.2ux", m->digest[i]); 133 m->sdigest = s_copy(sdigest); 134 135 return 0; 136 } 137 138 139 /* throw out deleted messages. return number of freshly deleted messages */ 140 int 141 purgedeleted(Mailbox *mb) 142 { 143 Message *m, *next; 144 int newdels; 145 146 /* forget about what's no longer in the mailbox */ 147 newdels = 0; 148 for(m = mb->root->part; m != nil; m = next){ 149 next = m->next; 150 if(m->deleted && m->refs == 0){ 151 if(m->inmbox) 152 newdels++; 153 delmessage(mb, m); 154 } 155 } 156 return newdels; 157 } 158 159 /* */ 160 /* read in the mailbox and parse into messages. */ 161 /* */ 162 static char* 163 _readmbox(Mailbox *mb, int doplumb, Mlock *lk) 164 { 165 int fd; 166 String *tmp; 167 Dir *d; 168 static char err[128]; 169 Message *m, **l; 170 Inbuf *inb; 171 char *x; 172 173 l = &mb->root->part; 174 175 /* 176 * open the mailbox. If it doesn't exist, try the temporary one. 177 */ 178 retry: 179 fd = open(mb->path, OREAD); 180 if(fd < 0){ 181 errstr(err, sizeof(err)); 182 if(strstr(err, "exist") != 0){ 183 tmp = s_copy(mb->path); 184 s_append(tmp, ".tmp"); 185 if(sysrename(s_to_c(tmp), mb->path) == 0){ 186 s_free(tmp); 187 goto retry; 188 } 189 s_free(tmp); 190 } 191 return err; 192 } 193 194 /* 195 * a new qid.path means reread the mailbox, while 196 * a new qid.vers means read any new messages 197 */ 198 d = dirfstat(fd); 199 if(d == nil){ 200 close(fd); 201 errstr(err, sizeof(err)); 202 return err; 203 } 204 if(mb->d != nil){ 205 if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){ 206 close(fd); 207 free(d); 208 return nil; 209 } 210 if(d->qid.path == mb->d->qid.path){ 211 while(*l != nil) 212 l = &(*l)->next; 213 seek(fd, mb->d->length, 0); 214 } 215 free(mb->d); 216 } 217 mb->d = d; 218 mb->vers++; 219 henter(PATH(0, Qtop), mb->name, 220 (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); 221 222 inb = emalloc(sizeof(Inbuf)); 223 inb->rptr = inb->wptr = inb->data; 224 inb->fd = fd; 225 226 /* read new messages */ 227 snprint(err, sizeof err, "reading '%s'", mb->path); 228 logmsg(err, nil); 229 for(;;){ 230 if(lk != nil) 231 syslockrefresh(lk); 232 m = newmessage(mb->root); 233 m->mallocd = 1; 234 m->inmbox = 1; 235 if(readmessage(m, inb) < 0){ 236 delmessage(mb, m); 237 mb->root->subname--; 238 break; 239 } 240 241 /* merge mailbox versions */ 242 while(*l != nil){ 243 if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){ 244 /* matches mail we already read, discard */ 245 logmsg("duplicate", *l); 246 delmessage(mb, m); 247 mb->root->subname--; 248 m = nil; 249 l = &(*l)->next; 250 break; 251 } else { 252 /* old mail no longer in box, mark deleted */ 253 logmsg("disappeared", *l); 254 if(doplumb) 255 mailplumb(mb, *l, 1); 256 (*l)->inmbox = 0; 257 (*l)->deleted = 1; 258 l = &(*l)->next; 259 } 260 } 261 if(m == nil) 262 continue; 263 264 x = strchr(m->start, '\n'); 265 if(x == nil) 266 m->header = m->end; 267 else 268 m->header = x + 1; 269 m->mheader = m->mhend = m->header; 270 parseunix(m); 271 parse(m, 0, mb, 0); 272 logmsg("new", m); 273 274 /* chain in */ 275 *l = m; 276 l = &m->next; 277 if(doplumb) 278 mailplumb(mb, m, 0); 279 280 } 281 logmsg("mbox read", nil); 282 283 /* whatever is left has been removed from the mbox, mark deleted */ 284 while(*l != nil){ 285 if(doplumb) 286 mailplumb(mb, *l, 1); 287 (*l)->inmbox = 0; 288 (*l)->deleted = 1; 289 l = &(*l)->next; 290 } 291 292 close(fd); 293 free(inb); 294 return nil; 295 } 296 297 static void 298 _writembox(Mailbox *mb, Mlock *lk) 299 { 300 Dir *d; 301 Message *m; 302 String *tmp; 303 int mode, errs; 304 Biobuf *b; 305 306 tmp = s_copy(mb->path); 307 s_append(tmp, ".tmp"); 308 309 /* 310 * preserve old files permissions, if possible 311 */ 312 d = dirstat(mb->path); 313 if(d != nil){ 314 mode = d->mode&0777; 315 free(d); 316 } else 317 mode = MBOXMODE; 318 319 sysremove(s_to_c(tmp)); 320 b = sysopen(s_to_c(tmp), "alc", mode); 321 if(b == 0){ 322 fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp)); 323 return; 324 } 325 326 logmsg("writing new mbox", nil); 327 errs = 0; 328 for(m = mb->root->part; m != nil; m = m->next){ 329 if(lk != nil) 330 syslockrefresh(lk); 331 if(m->deleted) 332 continue; 333 logmsg("writing", m); 334 if(Bwrite(b, m->start, m->end - m->start) < 0) 335 errs = 1; 336 if(Bwrite(b, "\n", 1) < 0) 337 errs = 1; 338 } 339 logmsg("wrote new mbox", nil); 340 341 if(sysclose(b) < 0) 342 errs = 1; 343 344 if(errs){ 345 fprint(2, "error writing temporary mail file\n"); 346 s_free(tmp); 347 return; 348 } 349 350 sysremove(mb->path); 351 if(sysrename(s_to_c(tmp), mb->path) < 0) 352 fprint(2, "%s: can't rename %s to %s: %r\n", argv0, 353 s_to_c(tmp), mb->path); 354 s_free(tmp); 355 if(mb->d != nil) 356 free(mb->d); 357 mb->d = dirstat(mb->path); 358 } 359 360 char* 361 plan9syncmbox(Mailbox *mb, int doplumb) 362 { 363 Mlock *lk; 364 char *rv; 365 366 lk = nil; 367 if(mb->dolock){ 368 lk = syslock(mb->path); 369 if(lk == nil) 370 return "can't lock mailbox"; 371 } 372 373 rv = _readmbox(mb, doplumb, lk); /* interpolate */ 374 if(purgedeleted(mb) > 0) 375 _writembox(mb, lk); 376 377 if(lk != nil) 378 sysunlock(lk); 379 380 return rv; 381 } 382 383 /* */ 384 /* look to see if we can open this mail box */ 385 /* */ 386 char* 387 plan9mbox(Mailbox *mb, char *path) 388 { 389 static char err[64]; 390 String *tmp; 391 392 if(access(path, AEXIST) < 0){ 393 errstr(err, sizeof(err)); 394 tmp = s_copy(path); 395 s_append(tmp, ".tmp"); 396 if(access(s_to_c(tmp), AEXIST) < 0){ 397 s_free(tmp); 398 return err; 399 } 400 s_free(tmp); 401 } 402 403 mb->sync = plan9syncmbox; 404 return nil; 405 }