1 /* $NetBSD: fio.c,v 1.34 2010/01/12 14:45:31 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.34 2010/01/12 14:45:31 christos Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 #include "rcv.h" 42 #include "extern.h" 43 #include "thread.h" 44 #include "sig.h" 45 46 /* 47 * Mail -- a mail program 48 * 49 * File I/O. 50 */ 51 52 #ifndef THREAD_SUPPORT 53 /************************************************************************/ 54 /* 55 * If we have threading support, these routines live in thread.c. 56 */ 57 static struct message *message; /* message structure array */ 58 static int msgCount; /* Count of messages read in */ 59 60 PUBLIC struct message * 61 next_message(struct message *mp) 62 { 63 if (mp + 1 < message || mp + 1 >= message + msgCount) 64 return NULL; 65 66 return mp + 1; 67 } 68 69 PUBLIC struct message * 70 prev_message(struct message *mp) 71 { 72 if (mp - 1 < message || mp - 1 >= message + msgCount) 73 return NULL; 74 75 return mp - 1; 76 } 77 78 PUBLIC struct message * 79 get_message(int msgnum) 80 { 81 if (msgnum < 1 || msgnum > msgCount) 82 return NULL; 83 84 return message + msgnum - 1; 85 } 86 87 PUBLIC int 88 get_msgnum(struct message *mp) 89 { 90 if (mp < message || mp >= message + msgCount) 91 return 0; 92 93 return mp - message + 1; 94 } 95 96 PUBLIC int 97 get_msgCount(void) 98 { 99 return msgCount; 100 } 101 #endif /* THREAD_SUPPORT */ 102 /************************************************************************/ 103 104 /* 105 * Initialize a message structure. 106 */ 107 static void 108 message_init(struct message *mp, off_t offset, short flags) 109 { 110 /* use memset so new fields are always zeroed */ 111 (void)memset(mp, 0, sizeof(*mp)); 112 mp->m_flag = flags; 113 mp->m_block = blockof(offset); 114 mp->m_offset = blkoffsetof(offset); 115 } 116 117 /* 118 * Take the data out of the passed ghost file and toss it into 119 * a dynamically allocated message structure. 120 */ 121 static void 122 makemessage(FILE *f, int omsgCount, int nmsgCount) 123 { 124 size_t size; 125 struct message *omessage; /* old message structure array */ 126 struct message *nmessage; 127 128 omessage = get_abs_message(1); 129 130 size = (nmsgCount + 1) * sizeof(*nmessage); 131 nmessage = realloc(omessage, size); 132 if (nmessage == NULL) 133 err(EXIT_FAILURE, 134 "Insufficient memory for %d messages", nmsgCount); 135 if (omsgCount == 0 || omessage == NULL) 136 dot = nmessage; 137 else 138 dot = nmessage + (dot - omessage); 139 140 thread_fix_old_links(nmessage, omessage, omsgCount); 141 142 #ifndef THREAD_SUPPORT 143 message = nmessage; 144 #endif 145 size -= (omsgCount + 1) * sizeof(*nmessage); 146 (void)fflush(f); 147 (void)lseek(fileno(f), (off_t)sizeof(*nmessage), SEEK_SET); 148 if (read(fileno(f), &nmessage[omsgCount], size) != (ssize_t)size) 149 errx(EXIT_FAILURE, "Message temporary file corrupted"); 150 151 message_init(&nmessage[nmsgCount], (off_t)0, 0); /* append a dummy */ 152 153 thread_fix_new_links(nmessage, omsgCount, nmsgCount); 154 155 (void)Fclose(f); 156 } 157 158 /* 159 * Append the passed message descriptor onto the temp file. 160 * If the write fails, return 1, else 0 161 */ 162 static int 163 append(struct message *mp, FILE *f) 164 { 165 return fwrite(mp, sizeof(*mp), 1, f) != 1; 166 } 167 168 /* 169 * Set up the input pointers while copying the mail file into /tmp. 170 */ 171 PUBLIC void 172 setptr(FILE *ibuf, off_t offset) 173 { 174 int c; 175 size_t len; 176 char *cp; 177 const char *cp2; 178 struct message this; 179 FILE *mestmp; 180 int maybe, inhead; 181 char linebuf[LINESIZE]; 182 int omsgCount; 183 #ifdef THREAD_SUPPORT 184 int nmsgCount; 185 #else 186 # define nmsgCount msgCount 187 #endif 188 189 /* Get temporary file. */ 190 (void)snprintf(linebuf, LINESIZE, "%s/mail.XXXXXX", tmpdir); 191 if ((c = mkstemp(linebuf)) == -1 || 192 (mestmp = Fdopen(c, "r+")) == NULL) { 193 (void)fprintf(stderr, "mail: can't open %s\n", linebuf); 194 exit(1); 195 } 196 (void)unlink(linebuf); 197 198 nmsgCount = get_abs_msgCount(); 199 if (offset == 0) { 200 nmsgCount = 0; 201 } else { 202 /* Seek into the file to get to the new messages */ 203 (void)fseeko(ibuf, offset, 0); 204 /* 205 * We need to make "offset" a pointer to the end of 206 * the temp file that has the copy of the mail file. 207 * If any messages have been edited, this will be 208 * different from the offset into the mail file. 209 */ 210 (void)fseek(otf, 0L, SEEK_END); 211 offset = ftell(otf); 212 } 213 omsgCount = nmsgCount; 214 maybe = 1; 215 inhead = 0; 216 message_init(&this, (off_t)0, MUSED|MNEW); 217 218 for (;;) { 219 if (fgets(linebuf, LINESIZE, ibuf) == NULL) { 220 if (append(&this, mestmp)) 221 err(EXIT_FAILURE, "temporary file"); 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 err(EXIT_FAILURE, "/tmp"); 239 if (len) 240 linebuf[len - 1] = 0; 241 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { 242 nmsgCount++; 243 if (append(&this, mestmp)) 244 err(EXIT_FAILURE, "temporary file"); 245 message_init(&this, offset, MUSED|MNEW); 246 inhead = 1; 247 } else if (linebuf[0] == 0) { 248 inhead = 0; 249 } else if (inhead) { 250 for (cp = linebuf, cp2 = "status";; cp++) { 251 if ((c = *cp2++) == 0) { 252 while (isspace((unsigned char)*cp++)) 253 continue; 254 if (cp[-1] != ':') 255 break; 256 while ((c = *cp++) != '\0') 257 if (c == 'R') 258 this.m_flag |= MREAD; 259 else if (c == 'O') 260 this.m_flag &= ~MNEW; 261 inhead = 0; 262 break; 263 } 264 if (*cp != c && *cp != toupper(c)) 265 break; 266 } 267 } 268 offset += len; 269 this.m_size += len; 270 this.m_lines++; 271 if (!inhead) { 272 int lines_plus_wraps = 1; 273 int linelen = (int)strlen(linebuf); 274 275 if (screenwidth && (int)linelen > screenwidth) { 276 lines_plus_wraps = linelen / screenwidth; 277 if (linelen % screenwidth != 0) 278 ++lines_plus_wraps; 279 } 280 this.m_blines += lines_plus_wraps; 281 } 282 maybe = linebuf[0] == 0; 283 } 284 } 285 286 /* 287 * Drop the passed line onto the passed output buffer. 288 * If a write error occurs, return -1, else the count of 289 * characters written, including the newline if requested. 290 */ 291 PUBLIC int 292 putline(FILE *obuf, const char *linebuf, int outlf) 293 { 294 size_t c; 295 296 c = strlen(linebuf); 297 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf); 298 if (outlf) { 299 (void)putc('\n', obuf); 300 c++; 301 } 302 if (ferror(obuf)) 303 return -1; 304 return (int)c; 305 } 306 307 /* 308 * Read up a line from the specified input into the line 309 * buffer. Return the number of characters read. Do not 310 * include the newline at the end. 311 */ 312 PUBLIC int 313 readline(FILE *ibuf, char *linebuf, int linesize, int no_restart) 314 { 315 struct sigaction osa_sigtstp; 316 struct sigaction osa_sigttin; 317 struct sigaction osa_sigttou; 318 int n; 319 320 clearerr(ibuf); 321 322 sig_check(); 323 if (no_restart) { 324 (void)sig_setflags(SIGTSTP, 0, &osa_sigtstp); 325 (void)sig_setflags(SIGTTIN, 0, &osa_sigttin); 326 (void)sig_setflags(SIGTTOU, 0, &osa_sigttou); 327 } 328 if (fgets(linebuf, linesize, ibuf) == NULL) 329 n = -1; 330 else { 331 n = (int)strlen(linebuf); 332 if (n > 0 && linebuf[n - 1] == '\n') 333 linebuf[--n] = '\0'; 334 } 335 if (no_restart) { 336 (void)sigaction(SIGTSTP, &osa_sigtstp, NULL); 337 (void)sigaction(SIGTTIN, &osa_sigttin, NULL); 338 (void)sigaction(SIGTTOU, &osa_sigttou, NULL); 339 } 340 sig_check(); 341 return n; 342 } 343 344 /* 345 * Return a file buffer all ready to read up the 346 * passed message pointer. 347 */ 348 PUBLIC FILE * 349 setinput(const struct message *mp) 350 { 351 352 (void)fflush(otf); 353 if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0) 354 err(EXIT_FAILURE, "fseek"); 355 return itf; 356 } 357 358 /* 359 * Delete a file, but only if the file is a plain file. 360 */ 361 PUBLIC int 362 rm(char *name) 363 { 364 struct stat sb; 365 366 if (stat(name, &sb) < 0) 367 return -1; 368 if (!S_ISREG(sb.st_mode)) { 369 errno = EISDIR; 370 return -1; 371 } 372 return unlink(name); 373 } 374 375 /* 376 * Determine the size of the file possessed by 377 * the passed buffer. 378 */ 379 PUBLIC off_t 380 fsize(FILE *iob) 381 { 382 struct stat sbuf; 383 384 if (fstat(fileno(iob), &sbuf) < 0) 385 return 0; 386 return sbuf.st_size; 387 } 388 389 /* 390 * Determine the current folder directory name. 391 */ 392 PUBLIC int 393 getfold(char *name, size_t namesize) 394 { 395 char *folder; 396 397 if ((folder = value(ENAME_FOLDER)) == NULL) 398 return -1; 399 if (*folder == '/') 400 (void)strlcpy(name, folder, namesize); 401 else 402 (void)snprintf(name, namesize, "%s/%s", homedir, folder); 403 return 0; 404 } 405 406 /* 407 * Evaluate the string given as a new mailbox name. 408 * Supported meta characters: 409 * % for my system mail box 410 * %user for user's system mail box 411 * # for previous file 412 * & invoker's mbox file 413 * +file file in folder directory 414 * any shell meta character 415 * Return the file name as a dynamic string. 416 */ 417 PUBLIC const char * 418 expand(const char *name) 419 { 420 char xname[PATHSIZE]; 421 char cmdbuf[PATHSIZE]; /* also used for file names */ 422 pid_t pid; 423 ssize_t l; 424 char *cp; 425 const char *shellcmd; 426 int pivec[2]; 427 struct stat sbuf; 428 429 /* 430 * The order of evaluation is "%" and "#" expand into constants. 431 * "&" can expand into "+". "+" can expand into shell meta characters. 432 * Shell meta characters expand into constants. 433 * This way, we make no recursive expansion. 434 */ 435 switch (*name) { 436 case '%': 437 findmail(name[1] ? name + 1 : myname, xname, sizeof(xname)); 438 return savestr(xname); 439 case '#': 440 if (name[1] != 0) 441 break; 442 if (prevfile[0] == 0) { 443 (void)printf("No previous file\n"); 444 return NULL; 445 } 446 return savestr(prevfile); 447 case '&': 448 if (name[1] == 0 && (name = value(ENAME_MBOX)) == NULL) 449 name = "~/mbox"; 450 /* fall through */ 451 } 452 if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) { 453 (void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1); 454 name = savestr(xname); 455 } 456 /* catch the most common shell meta character */ 457 if (name[0] == '~' && (name[1] == '/' || name[1] == '\0')) { 458 (void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1); 459 name = savestr(xname); 460 } 461 if (strpbrk(name, "~{[*?$`'\"\\") == NULL) 462 return name; 463 if (pipe(pivec) < 0) { 464 warn("pipe"); 465 return name; 466 } 467 (void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name); 468 if ((shellcmd = value(ENAME_SHELL)) == NULL) 469 shellcmd = _PATH_CSHELL; 470 pid = start_command(shellcmd, NULL, -1, pivec[1], "-c", cmdbuf, NULL); 471 if (pid < 0) { 472 (void)close(pivec[0]); 473 (void)close(pivec[1]); 474 return NULL; 475 } 476 (void)close(pivec[1]); 477 l = read(pivec[0], xname, sizeof(xname)); 478 (void)close(pivec[0]); 479 if (wait_child(pid) < 0 && WTERMSIG(wait_status) != SIGPIPE) { 480 warnx("Expansion `%s' failed [%x]", cmdbuf, wait_status); 481 return NULL; 482 } 483 if (l < 0) { 484 warn("read"); 485 return NULL; 486 } 487 if (l == 0) { 488 warnx("No match for `%s'", name); 489 return NULL; 490 } 491 if (l == sizeof(xname)) { 492 warnx("Expansion buffer overflow for `%s'", name); 493 return NULL; 494 } 495 xname[l] = '\0'; 496 for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--) 497 continue; 498 cp[1] = '\0'; 499 if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) { 500 warnx("Ambiguous expansion for `%s'", name); 501 return NULL; 502 } 503 return savestr(xname); 504 } 505 506 /* 507 * Return the name of the dead.letter file. 508 */ 509 PUBLIC const char * 510 getdeadletter(void) 511 { 512 const char *cp; 513 514 if ((cp = value(ENAME_DEAD)) == NULL || (cp = expand(cp)) == NULL) 515 cp = expand("~/dead.letter"); 516 else if (*cp != '/') { 517 char buf[PATHSIZE]; 518 (void)snprintf(buf, sizeof(buf), "~/%s", cp); 519 cp = expand(buf); 520 } 521 return cp; 522 } 523