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