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