1 /* $NetBSD: fio.c,v 1.33 2009/04/11 14:22:32 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.33 2009/04/11 14:22:32 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(1, "Insufficient memory for %d messages", nmsgCount); 134 if (omsgCount == 0 || omessage == NULL) 135 dot = nmessage; 136 else 137 dot = nmessage + (dot - omessage); 138 139 thread_fix_old_links(nmessage, omessage, omsgCount); 140 141 #ifndef THREAD_SUPPORT 142 message = nmessage; 143 #endif 144 size -= (omsgCount + 1) * sizeof(*nmessage); 145 (void)fflush(f); 146 (void)lseek(fileno(f), (off_t)sizeof(*nmessage), SEEK_SET); 147 if (read(fileno(f), &nmessage[omsgCount], size) != (ssize_t)size) 148 errx(EXIT_FAILURE, "Message temporary file corrupted"); 149 150 message_init(&nmessage[nmsgCount], (off_t)0, 0); /* append a dummy */ 151 152 thread_fix_new_links(nmessage, omsgCount, nmsgCount); 153 154 (void)Fclose(f); 155 } 156 157 /* 158 * Append the passed message descriptor onto the temp file. 159 * If the write fails, return 1, else 0 160 */ 161 static int 162 append(struct message *mp, FILE *f) 163 { 164 return fwrite(mp, sizeof(*mp), 1, f) != 1; 165 } 166 167 /* 168 * Set up the input pointers while copying the mail file into /tmp. 169 */ 170 PUBLIC void 171 setptr(FILE *ibuf, off_t offset) 172 { 173 int c; 174 size_t len; 175 char *cp; 176 const char *cp2; 177 struct message this; 178 FILE *mestmp; 179 int maybe, inhead; 180 char linebuf[LINESIZE]; 181 int omsgCount; 182 #ifdef THREAD_SUPPORT 183 int nmsgCount; 184 #else 185 # define nmsgCount msgCount 186 #endif 187 188 /* Get temporary file. */ 189 (void)snprintf(linebuf, LINESIZE, "%s/mail.XXXXXX", tmpdir); 190 if ((c = mkstemp(linebuf)) == -1 || 191 (mestmp = Fdopen(c, "r+")) == NULL) { 192 (void)fprintf(stderr, "mail: can't open %s\n", linebuf); 193 exit(1); 194 } 195 (void)unlink(linebuf); 196 197 nmsgCount = get_abs_msgCount(); 198 if (offset == 0) { 199 nmsgCount = 0; 200 } else { 201 /* Seek into the file to get to the new messages */ 202 (void)fseeko(ibuf, offset, 0); 203 /* 204 * We need to make "offset" a pointer to the end of 205 * the temp file that has the copy of the mail file. 206 * If any messages have been edited, this will be 207 * different from the offset into the mail file. 208 */ 209 (void)fseek(otf, 0L, SEEK_END); 210 offset = ftell(otf); 211 } 212 omsgCount = nmsgCount; 213 maybe = 1; 214 inhead = 0; 215 message_init(&this, (off_t)0, MUSED|MNEW); 216 217 for (;;) { 218 if (fgets(linebuf, LINESIZE, ibuf) == NULL) { 219 if (append(&this, mestmp)) 220 err(EXIT_FAILURE, "temporary file"); 221 makemessage(mestmp, omsgCount, nmsgCount); 222 return; 223 } 224 len = strlen(linebuf); 225 /* 226 * Transforms lines ending in <CR><LF> to just <LF>. 227 * This allows mail to be able to read Eudora mailboxes 228 * that reside on a DOS partition. 229 */ 230 if (len >= 2 && linebuf[len - 1] == '\n' && 231 linebuf[len - 2] == '\r') { 232 linebuf[len - 2] = '\n'; 233 len--; 234 } 235 (void)fwrite(linebuf, sizeof(*linebuf), len, otf); 236 if (ferror(otf)) 237 err(EXIT_FAILURE, "/tmp"); 238 if (len) 239 linebuf[len - 1] = 0; 240 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { 241 nmsgCount++; 242 if (append(&this, mestmp)) 243 err(EXIT_FAILURE, "temporary file"); 244 message_init(&this, offset, MUSED|MNEW); 245 inhead = 1; 246 } else if (linebuf[0] == 0) { 247 inhead = 0; 248 } else if (inhead) { 249 for (cp = linebuf, cp2 = "status";; cp++) { 250 if ((c = *cp2++) == 0) { 251 while (isspace((unsigned char)*cp++)) 252 continue; 253 if (cp[-1] != ':') 254 break; 255 while ((c = *cp++) != '\0') 256 if (c == 'R') 257 this.m_flag |= MREAD; 258 else if (c == 'O') 259 this.m_flag &= ~MNEW; 260 inhead = 0; 261 break; 262 } 263 if (*cp != c && *cp != toupper(c)) 264 break; 265 } 266 } 267 offset += len; 268 this.m_size += len; 269 this.m_lines++; 270 if (!inhead) { 271 int lines_plus_wraps = 1; 272 int linelen = (int)strlen(linebuf); 273 274 if (screenwidth && (int)linelen > screenwidth) { 275 lines_plus_wraps = linelen / screenwidth; 276 if (linelen % screenwidth != 0) 277 ++lines_plus_wraps; 278 } 279 this.m_blines += lines_plus_wraps; 280 } 281 maybe = linebuf[0] == 0; 282 } 283 } 284 285 /* 286 * Drop the passed line onto the passed output buffer. 287 * If a write error occurs, return -1, else the count of 288 * characters written, including the newline if requested. 289 */ 290 PUBLIC int 291 putline(FILE *obuf, const char *linebuf, int outlf) 292 { 293 size_t c; 294 295 c = strlen(linebuf); 296 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf); 297 if (outlf) { 298 (void)putc('\n', obuf); 299 c++; 300 } 301 if (ferror(obuf)) 302 return -1; 303 return (int)c; 304 } 305 306 /* 307 * Read up a line from the specified input into the line 308 * buffer. Return the number of characters read. Do not 309 * include the newline at the end. 310 */ 311 PUBLIC int 312 readline(FILE *ibuf, char *linebuf, int linesize, int no_restart) 313 { 314 struct sigaction osa_sigtstp; 315 struct sigaction osa_sigttin; 316 struct sigaction osa_sigttou; 317 int n; 318 319 clearerr(ibuf); 320 321 sig_check(); 322 if (no_restart) { 323 (void)sig_setflags(SIGTSTP, 0, &osa_sigtstp); 324 (void)sig_setflags(SIGTTIN, 0, &osa_sigttin); 325 (void)sig_setflags(SIGTTOU, 0, &osa_sigttou); 326 } 327 if (fgets(linebuf, linesize, ibuf) == NULL) 328 n = -1; 329 else { 330 n = (int)strlen(linebuf); 331 if (n > 0 && linebuf[n - 1] == '\n') 332 linebuf[--n] = '\0'; 333 } 334 if (no_restart) { 335 (void)sigaction(SIGTSTP, &osa_sigtstp, NULL); 336 (void)sigaction(SIGTTIN, &osa_sigttin, NULL); 337 (void)sigaction(SIGTTOU, &osa_sigttou, NULL); 338 } 339 sig_check(); 340 return n; 341 } 342 343 /* 344 * Return a file buffer all ready to read up the 345 * passed message pointer. 346 */ 347 PUBLIC FILE * 348 setinput(const struct message *mp) 349 { 350 351 (void)fflush(otf); 352 if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0) 353 err(1, "fseek"); 354 return itf; 355 } 356 357 /* 358 * Delete a file, but only if the file is a plain file. 359 */ 360 PUBLIC int 361 rm(char *name) 362 { 363 struct stat sb; 364 365 if (stat(name, &sb) < 0) 366 return -1; 367 if (!S_ISREG(sb.st_mode)) { 368 errno = EISDIR; 369 return -1; 370 } 371 return unlink(name); 372 } 373 374 /* 375 * Determine the size of the file possessed by 376 * the passed buffer. 377 */ 378 PUBLIC off_t 379 fsize(FILE *iob) 380 { 381 struct stat sbuf; 382 383 if (fstat(fileno(iob), &sbuf) < 0) 384 return 0; 385 return sbuf.st_size; 386 } 387 388 /* 389 * Determine the current folder directory name. 390 */ 391 PUBLIC int 392 getfold(char *name, size_t namesize) 393 { 394 char *folder; 395 396 if ((folder = value(ENAME_FOLDER)) == NULL) 397 return -1; 398 if (*folder == '/') 399 (void)strlcpy(name, folder, namesize); 400 else 401 (void)snprintf(name, namesize, "%s/%s", homedir, folder); 402 return 0; 403 } 404 405 /* 406 * Evaluate the string given as a new mailbox name. 407 * Supported meta characters: 408 * % for my system mail box 409 * %user for user's system mail box 410 * # for previous file 411 * & invoker's mbox file 412 * +file file in folder directory 413 * any shell meta character 414 * Return the file name as a dynamic string. 415 */ 416 PUBLIC const char * 417 expand(const char *name) 418 { 419 char xname[PATHSIZE]; 420 char cmdbuf[PATHSIZE]; /* also used for file names */ 421 pid_t pid; 422 ssize_t l; 423 char *cp; 424 const char *shellcmd; 425 int pivec[2]; 426 struct stat sbuf; 427 428 /* 429 * The order of evaluation is "%" and "#" expand into constants. 430 * "&" can expand into "+". "+" can expand into shell meta characters. 431 * Shell meta characters expand into constants. 432 * This way, we make no recursive expansion. 433 */ 434 switch (*name) { 435 case '%': 436 findmail(name[1] ? name + 1 : myname, xname, sizeof(xname)); 437 return savestr(xname); 438 case '#': 439 if (name[1] != 0) 440 break; 441 if (prevfile[0] == 0) { 442 (void)printf("No previous file\n"); 443 return NULL; 444 } 445 return savestr(prevfile); 446 case '&': 447 if (name[1] == 0 && (name = value(ENAME_MBOX)) == NULL) 448 name = "~/mbox"; 449 /* fall through */ 450 } 451 if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) { 452 (void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1); 453 name = savestr(xname); 454 } 455 /* catch the most common shell meta character */ 456 if (name[0] == '~' && (name[1] == '/' || name[1] == '\0')) { 457 (void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1); 458 name = savestr(xname); 459 } 460 if (strpbrk(name, "~{[*?$`'\"\\") == NULL) 461 return name; 462 if (pipe(pivec) < 0) { 463 warn("pipe"); 464 return name; 465 } 466 (void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name); 467 if ((shellcmd = value(ENAME_SHELL)) == NULL) 468 shellcmd = _PATH_CSHELL; 469 pid = start_command(shellcmd, NULL, -1, pivec[1], "-c", cmdbuf, NULL); 470 if (pid < 0) { 471 (void)close(pivec[0]); 472 (void)close(pivec[1]); 473 return NULL; 474 } 475 (void)close(pivec[1]); 476 l = read(pivec[0], xname, sizeof(xname)); 477 (void)close(pivec[0]); 478 if (wait_child(pid) < 0 && WTERMSIG(wait_status) != SIGPIPE) { 479 (void)fprintf(stderr, "\"%s\": Expansion failed.\n", name); 480 return NULL; 481 } 482 if (l < 0) { 483 warn("read"); 484 return NULL; 485 } 486 if (l == 0) { 487 (void)fprintf(stderr, "\"%s\": No match.\n", name); 488 return NULL; 489 } 490 if (l == sizeof(xname)) { 491 (void)fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name); 492 return NULL; 493 } 494 xname[l] = '\0'; 495 for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--) 496 continue; 497 cp[1] = '\0'; 498 if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) { 499 (void)fprintf(stderr, "\"%s\": Ambiguous.\n", name); 500 return NULL; 501 } 502 return savestr(xname); 503 } 504 505 /* 506 * Return the name of the dead.letter file. 507 */ 508 PUBLIC const char * 509 getdeadletter(void) 510 { 511 const char *cp; 512 513 if ((cp = value(ENAME_DEAD)) == NULL || (cp = expand(cp)) == NULL) 514 cp = expand("~/dead.letter"); 515 else if (*cp != '/') { 516 char buf[PATHSIZE]; 517 (void)snprintf(buf, sizeof(buf), "~/%s", cp); 518 cp = expand(buf); 519 } 520 return cp; 521 } 522