1 /* $NetBSD: fio.c,v 1.31 2007/10/29 23:20:38 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1980, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 #if 0 35 static char sccsid[] = "@(#)fio.c 8.2 (Berkeley) 4/20/95"; 36 #else 37 __RCSID("$NetBSD: fio.c,v 1.31 2007/10/29 23:20:38 christos Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 #include "rcv.h" 42 #include "extern.h" 43 #include "thread.h" 44 45 /* 46 * Mail -- a mail program 47 * 48 * File I/O. 49 */ 50 51 #ifndef THREAD_SUPPORT 52 /************************************************************************/ 53 /* 54 * If we have threading support, these routines live in thread.c. 55 */ 56 static struct message *message; /* message structure array */ 57 static int msgCount; /* Count of messages read in */ 58 59 PUBLIC struct message * 60 next_message(struct message *mp) 61 { 62 if (mp + 1 < message || mp + 1 >= message + msgCount) 63 return NULL; 64 65 return mp + 1; 66 } 67 68 PUBLIC struct message * 69 prev_message(struct message *mp) 70 { 71 if (mp - 1 < message || mp - 1 >= message + msgCount) 72 return NULL; 73 74 return mp - 1; 75 } 76 77 PUBLIC struct message * 78 get_message(int msgnum) 79 { 80 if (msgnum < 1 || msgnum > msgCount) 81 return NULL; 82 83 return message + msgnum - 1; 84 } 85 86 PUBLIC int 87 get_msgnum(struct message *mp) 88 { 89 if (mp < message || mp >= message + msgCount) 90 return 0; 91 92 return mp - message + 1; 93 } 94 95 PUBLIC int 96 get_msgCount(void) 97 { 98 return msgCount; 99 } 100 #endif /* THREAD_SUPPORT */ 101 /************************************************************************/ 102 103 /* 104 * Initialize a message structure. 105 */ 106 static void 107 message_init(struct message *mp, off_t offset, short flags) 108 { 109 /* use memset so new fields are always zeroed */ 110 (void)memset(mp, 0, sizeof(*mp)); 111 mp->m_flag = flags; 112 mp->m_block = blockof(offset); 113 mp->m_offset = blkoffsetof(offset); 114 } 115 116 /* 117 * Take the data out of the passed ghost file and toss it into 118 * a dynamically allocated message structure. 119 */ 120 static void 121 makemessage(FILE *f, int omsgCount, int nmsgCount) 122 { 123 size_t size; 124 struct message *omessage; /* old message structure array */ 125 struct message *nmessage; 126 127 omessage = get_abs_message(1); 128 129 size = (nmsgCount + 1) * sizeof(*nmessage); 130 nmessage = realloc(omessage, size); 131 if (nmessage == NULL) 132 err(1, "Insufficient memory for %d messages", nmsgCount); 133 if (omsgCount == 0 || omessage == NULL) 134 dot = nmessage; 135 else 136 dot = nmessage + (dot - omessage); 137 138 thread_fix_old_links(nmessage, omessage, omsgCount); 139 140 #ifndef THREAD_SUPPORT 141 message = nmessage; 142 #endif 143 size -= (omsgCount + 1) * sizeof(*nmessage); 144 (void)fflush(f); 145 (void)lseek(fileno(f), (off_t)sizeof(*nmessage), SEEK_SET); 146 if (read(fileno(f), &nmessage[omsgCount], size) != (ssize_t)size) 147 errx(EXIT_FAILURE, "Message temporary file corrupted"); 148 149 message_init(&nmessage[nmsgCount], (off_t)0, 0); /* append a dummy */ 150 151 thread_fix_new_links(nmessage, omsgCount, nmsgCount); 152 153 (void)Fclose(f); 154 } 155 156 /* 157 * Append the passed message descriptor onto the temp file. 158 * If the write fails, return 1, else 0 159 */ 160 static int 161 append(struct message *mp, FILE *f) 162 { 163 return fwrite(mp, sizeof(*mp), 1, f) != 1; 164 } 165 166 /* 167 * Set up the input pointers while copying the mail file into /tmp. 168 */ 169 PUBLIC void 170 setptr(FILE *ibuf, off_t offset) 171 { 172 int c; 173 size_t len; 174 char *cp; 175 const char *cp2; 176 struct message this; 177 FILE *mestmp; 178 int maybe, inhead; 179 char linebuf[LINESIZE]; 180 int omsgCount; 181 #ifdef THREAD_SUPPORT 182 int nmsgCount; 183 #else 184 # define nmsgCount msgCount 185 #endif 186 187 /* Get temporary file. */ 188 (void)snprintf(linebuf, LINESIZE, "%s/mail.XXXXXX", tmpdir); 189 if ((c = mkstemp(linebuf)) == -1 || 190 (mestmp = Fdopen(c, "r+")) == NULL) { 191 (void)fprintf(stderr, "mail: can't open %s\n", linebuf); 192 exit(1); 193 } 194 (void)unlink(linebuf); 195 196 nmsgCount = get_abs_msgCount(); 197 if (offset == 0) { 198 nmsgCount = 0; 199 } else { 200 /* Seek into the file to get to the new messages */ 201 (void)fseeko(ibuf, offset, 0); 202 /* 203 * We need to make "offset" a pointer to the end of 204 * the temp file that has the copy of the mail file. 205 * If any messages have been edited, this will be 206 * different from the offset into the mail file. 207 */ 208 (void)fseek(otf, 0L, SEEK_END); 209 offset = ftell(otf); 210 } 211 omsgCount = nmsgCount; 212 maybe = 1; 213 inhead = 0; 214 message_init(&this, (off_t)0, MUSED|MNEW); 215 216 for (;;) { 217 if (fgets(linebuf, LINESIZE, ibuf) == NULL) { 218 if (append(&this, mestmp)) { 219 warn("temporary file"); 220 exit(1); 221 } 222 makemessage(mestmp, omsgCount, nmsgCount); 223 return; 224 } 225 len = strlen(linebuf); 226 /* 227 * Transforms lines ending in <CR><LF> to just <LF>. 228 * This allows mail to be able to read Eudora mailboxes 229 * that reside on a DOS partition. 230 */ 231 if (len >= 2 && linebuf[len - 1] == '\n' && 232 linebuf[len - 2] == '\r') { 233 linebuf[len - 2] = '\n'; 234 len--; 235 } 236 (void)fwrite(linebuf, sizeof(*linebuf), len, otf); 237 if (ferror(otf)) { 238 warn("/tmp"); 239 exit(1); 240 } 241 if(len) 242 linebuf[len - 1] = 0; 243 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { 244 nmsgCount++; 245 if (append(&this, mestmp)) { 246 warn("temporary file"); 247 exit(1); 248 } 249 message_init(&this, offset, MUSED|MNEW); 250 inhead = 1; 251 } else if (linebuf[0] == 0) { 252 inhead = 0; 253 } else if (inhead) { 254 for (cp = linebuf, cp2 = "status";; cp++) { 255 if ((c = *cp2++) == 0) { 256 while (isspace((unsigned char)*cp++)) 257 continue; 258 if (cp[-1] != ':') 259 break; 260 while ((c = *cp++) != '\0') 261 if (c == 'R') 262 this.m_flag |= MREAD; 263 else if (c == 'O') 264 this.m_flag &= ~MNEW; 265 inhead = 0; 266 break; 267 } 268 if (*cp != c && *cp != toupper(c)) 269 break; 270 } 271 } 272 offset += len; 273 this.m_size += len; 274 this.m_lines++; 275 if (!inhead) { 276 int lines_plus_wraps = 1; 277 size_t linelen = strlen(linebuf); 278 279 if (screenwidth && (int)linelen > screenwidth) { 280 lines_plus_wraps = linelen / screenwidth; 281 if (linelen % screenwidth != 0) 282 ++lines_plus_wraps; 283 } 284 this.m_blines += lines_plus_wraps; 285 } 286 maybe = linebuf[0] == 0; 287 } 288 } 289 290 /* 291 * Drop the passed line onto the passed output buffer. 292 * If a write error occurs, return -1, else the count of 293 * characters written, including the newline if requested. 294 */ 295 PUBLIC int 296 putline(FILE *obuf, const char *linebuf, int outlf) 297 { 298 size_t c; 299 300 c = strlen(linebuf); 301 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf); 302 if (outlf) { 303 (void)putc('\n', obuf); 304 c++; 305 } 306 if (ferror(obuf)) 307 return -1; 308 return c; 309 } 310 311 /* 312 * Read up a line from the specified input into the line 313 * buffer. Return the number of characters read. Do not 314 * include the newline at the end. 315 */ 316 PUBLIC int 317 mail_readline(FILE *ibuf, char *linebuf, int linesize) 318 { 319 int n; 320 321 clearerr(ibuf); 322 if (fgets(linebuf, linesize, ibuf) == NULL) 323 return -1; 324 n = strlen(linebuf); 325 if (n > 0 && linebuf[n - 1] == '\n') 326 linebuf[--n] = '\0'; 327 return n; 328 } 329 330 /* 331 * Return a file buffer all ready to read up the 332 * passed message pointer. 333 */ 334 PUBLIC FILE * 335 setinput(const struct message *mp) 336 { 337 338 (void)fflush(otf); 339 if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0) 340 err(1, "fseek"); 341 return itf; 342 } 343 344 /* 345 * Delete a file, but only if the file is a plain file. 346 */ 347 PUBLIC int 348 rm(char *name) 349 { 350 struct stat sb; 351 352 if (stat(name, &sb) < 0) 353 return -1; 354 if (!S_ISREG(sb.st_mode)) { 355 errno = EISDIR; 356 return -1; 357 } 358 return unlink(name); 359 } 360 361 static int sigdepth; /* depth of holdsigs() */ 362 static sigset_t nset, oset; 363 /* 364 * Hold signals SIGHUP, SIGINT, and SIGQUIT. 365 */ 366 PUBLIC void 367 holdsigs(void) 368 { 369 370 if (sigdepth++ == 0) { 371 (void)sigemptyset(&nset); 372 (void)sigaddset(&nset, SIGHUP); 373 (void)sigaddset(&nset, SIGINT); 374 (void)sigaddset(&nset, SIGQUIT); 375 (void)sigprocmask(SIG_BLOCK, &nset, &oset); 376 } 377 } 378 379 /* 380 * Release signals SIGHUP, SIGINT, and SIGQUIT. 381 */ 382 PUBLIC void 383 relsesigs(void) 384 { 385 386 if (--sigdepth == 0) 387 (void)sigprocmask(SIG_SETMASK, &oset, NULL); 388 } 389 390 /* 391 * Determine the size of the file possessed by 392 * the passed buffer. 393 */ 394 PUBLIC off_t 395 fsize(FILE *iob) 396 { 397 struct stat sbuf; 398 399 if (fstat(fileno(iob), &sbuf) < 0) 400 return 0; 401 return sbuf.st_size; 402 } 403 404 /* 405 * Determine the current folder directory name. 406 */ 407 PUBLIC int 408 getfold(char *name, size_t namesize) 409 { 410 char *folder; 411 412 if ((folder = value(ENAME_FOLDER)) == NULL) 413 return -1; 414 if (*folder == '/') 415 (void)strlcpy(name, folder, namesize); 416 else 417 (void)snprintf(name, namesize, "%s/%s", homedir, folder); 418 return 0; 419 } 420 421 /* 422 * Evaluate the string given as a new mailbox name. 423 * Supported meta characters: 424 * % for my system mail box 425 * %user for user's system mail box 426 * # for previous file 427 * & invoker's mbox file 428 * +file file in folder directory 429 * any shell meta character 430 * Return the file name as a dynamic string. 431 */ 432 PUBLIC const char * 433 expand(const char *name) 434 { 435 char xname[PATHSIZE]; 436 char cmdbuf[PATHSIZE]; /* also used for file names */ 437 int pid, l; 438 char *cp; 439 const char *shellcmd; 440 int pivec[2]; 441 struct stat sbuf; 442 443 /* 444 * The order of evaluation is "%" and "#" expand into constants. 445 * "&" can expand into "+". "+" can expand into shell meta characters. 446 * Shell meta characters expand into constants. 447 * This way, we make no recursive expansion. 448 */ 449 switch (*name) { 450 case '%': 451 findmail(name[1] ? name + 1 : myname, xname, sizeof(xname)); 452 return savestr(xname); 453 case '#': 454 if (name[1] != 0) 455 break; 456 if (prevfile[0] == 0) { 457 (void)printf("No previous file\n"); 458 return NULL; 459 } 460 return savestr(prevfile); 461 case '&': 462 if (name[1] == 0 && (name = value(ENAME_MBOX)) == NULL) 463 name = "~/mbox"; 464 /* fall through */ 465 } 466 if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) { 467 (void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1); 468 name = savestr(xname); 469 } 470 /* catch the most common shell meta character */ 471 if (name[0] == '~' && (name[1] == '/' || name[1] == '\0')) { 472 (void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1); 473 name = savestr(xname); 474 } 475 if (strpbrk(name, "~{[*?$`'\"\\") == NULL) 476 return name; 477 if (pipe(pivec) < 0) { 478 warn("pipe"); 479 return name; 480 } 481 (void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name); 482 if ((shellcmd = value(ENAME_SHELL)) == NULL) 483 shellcmd = _PATH_CSHELL; 484 pid = start_command(shellcmd, 0, -1, pivec[1], "-c", cmdbuf, NULL); 485 if (pid < 0) { 486 (void)close(pivec[0]); 487 (void)close(pivec[1]); 488 return NULL; 489 } 490 (void)close(pivec[1]); 491 l = read(pivec[0], xname, sizeof(xname)); 492 (void)close(pivec[0]); 493 if (wait_child(pid) < 0 && WTERMSIG(wait_status) != SIGPIPE) { 494 (void)fprintf(stderr, "\"%s\": Expansion failed.\n", name); 495 return NULL; 496 } 497 if (l < 0) { 498 warn("read"); 499 return NULL; 500 } 501 if (l == 0) { 502 (void)fprintf(stderr, "\"%s\": No match.\n", name); 503 return NULL; 504 } 505 if (l == sizeof(xname)) { 506 (void)fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name); 507 return NULL; 508 } 509 xname[l] = '\0'; 510 for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--) 511 continue; 512 cp[1] = '\0'; 513 if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) { 514 (void)fprintf(stderr, "\"%s\": Ambiguous.\n", name); 515 return NULL; 516 } 517 return savestr(xname); 518 } 519 520 /* 521 * Return the name of the dead.letter file. 522 */ 523 PUBLIC const char * 524 getdeadletter(void) 525 { 526 const char *cp; 527 528 if ((cp = value(ENAME_DEAD)) == NULL || (cp = expand(cp)) == NULL) 529 cp = expand("~/dead.letter"); 530 else if (*cp != '/') { 531 char buf[PATHSIZE]; 532 (void)snprintf(buf, sizeof(buf), "~/%s", cp); 533 cp = expand(buf); 534 } 535 return cp; 536 } 537