1/* $OpenBSD: fileio.c,v 1.112 2023/08/11 04:45:05 guenther Exp $ */ 2 3/* This file is in the public domain. */ 4 5/* 6 * POSIX fileio.c 7 */ 8 9#include <sys/queue.h> 10#include <sys/resource.h> 11#include <sys/stat.h> 12#include <sys/time.h> 13#include <sys/types.h> 14#include <sys/wait.h> 15#include <dirent.h> 16#include <errno.h> 17#include <fcntl.h> 18#include <limits.h> 19#include <pwd.h> 20#include <signal.h> 21#include <stdio.h> 22#include <stdlib.h> 23#include <string.h> 24#include <unistd.h> 25 26#include "def.h" 27#include "kbd.h" 28#include "pathnames.h" 29 30static char *bkuplocation(const char *); 31static int bkupleavetmp(const char *); 32 33static char *bkupdir; 34static int leavetmp = 0; /* 1 = leave any '~' files in tmp dir */ 35 36/* 37 * Open a file for reading. 38 */ 39int 40ffropen(FILE ** ffp, const char *fn, struct buffer *bp) 41{ 42 if ((*ffp = fopen(fn, "r")) == NULL) { 43 if (errno == ENOENT) 44 return (FIOFNF); 45 return (FIOERR); 46 } 47 48 /* If 'fn' is a directory open it with dired. */ 49 if (fisdir(fn) == TRUE) 50 return (FIODIR); 51 52 ffstat(*ffp, bp); 53 54 return (FIOSUC); 55} 56 57/* 58 * Update stat/dirty info 59 */ 60void 61ffstat(FILE *ffp, struct buffer *bp) 62{ 63 struct stat sb; 64 65 if (bp && fstat(fileno(ffp), &sb) == 0) { 66 /* set highorder bit to make sure this isn't all zero */ 67 bp->b_fi.fi_mode = sb.st_mode | 0x8000; 68 bp->b_fi.fi_uid = sb.st_uid; 69 bp->b_fi.fi_gid = sb.st_gid; 70 bp->b_fi.fi_mtime = sb.st_mtim; 71 /* Clear the ignore flag */ 72 bp->b_flag &= ~(BFIGNDIRTY | BFDIRTY); 73 } 74} 75 76/* 77 * Update the status/dirty info. If there is an error, 78 * there's not a lot we can do. 79 */ 80int 81fupdstat(struct buffer *bp) 82{ 83 FILE *ffp; 84 85 if ((ffp = fopen(bp->b_fname, "r")) == NULL) { 86 if (errno == ENOENT) 87 return (FIOFNF); 88 return (FIOERR); 89 } 90 ffstat(ffp, bp); 91 (void)ffclose(ffp, bp); 92 return (FIOSUC); 93} 94 95/* 96 * Open a file for writing. 97 */ 98int 99ffwopen(FILE ** ffp, const char *fn, struct buffer *bp) 100{ 101 int fd; 102 mode_t fmode = DEFFILEMODE; 103 104 if (bp && bp->b_fi.fi_mode) 105 fmode = bp->b_fi.fi_mode & 07777; 106 107 fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, fmode); 108 if (fd == -1) { 109 ffp = NULL; 110 dobeep(); 111 ewprintf("Cannot open file for writing : %s", strerror(errno)); 112 return (FIOERR); 113 } 114 115 if ((*ffp = fdopen(fd, "w")) == NULL) { 116 dobeep(); 117 ewprintf("Cannot open file for writing : %s", strerror(errno)); 118 close(fd); 119 return (FIOERR); 120 } 121 122 /* 123 * If we have file information, use it. We don't bother to check for 124 * errors, because there's no a lot we can do about it. Certainly 125 * trying to change ownership will fail if we aren't root. That's 126 * probably OK. If we don't have info, no need to get it, since any 127 * future writes will do the same thing. 128 */ 129 if (bp && bp->b_fi.fi_mode) { 130 fchmod(fd, bp->b_fi.fi_mode & 07777); 131 fchown(fd, bp->b_fi.fi_uid, bp->b_fi.fi_gid); 132 } 133 return (FIOSUC); 134} 135 136/* 137 * Close a file. 138 */ 139int 140ffclose(FILE *ffp, struct buffer *bp) 141{ 142 if (fclose(ffp) == 0) 143 return (FIOSUC); 144 return (FIOERR); 145} 146 147/* 148 * Write a buffer to the already opened file. bp points to the 149 * buffer. Return the status. 150 */ 151int 152ffputbuf(FILE *ffp, struct buffer *bp, int eobnl) 153{ 154 struct line *lp, *lpend; 155 156 lpend = bp->b_headp; 157 158 for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) { 159 if (fwrite(ltext(lp), 1, llength(lp), ffp) != llength(lp)) { 160 dobeep(); 161 ewprintf("Write I/O error"); 162 return (FIOERR); 163 } 164 if (lforw(lp) != lpend) /* no implied \n on last line */ 165 putc(*bp->b_nlchr, ffp); 166 } 167 if (eobnl) { 168 lnewline_at(lback(lpend), llength(lback(lpend))); 169 putc(*bp->b_nlchr, ffp); 170 } 171 return (FIOSUC); 172} 173 174/* 175 * Read a line from a file, and store the bytes 176 * in the supplied buffer. Stop on end of file or end of 177 * line. When FIOEOF is returned, there is a valid line 178 * of data without the normally implied \n. 179 * If the line length exceeds nbuf, FIOLONG is returned. 180 */ 181int 182ffgetline(FILE *ffp, char *buf, int nbuf, int *nbytes) 183{ 184 int c, i; 185 186 i = 0; 187 while ((c = getc(ffp)) != EOF && c != *curbp->b_nlchr) { 188 buf[i++] = c; 189 if (i >= nbuf) 190 return (FIOLONG); 191 } 192 if (c == EOF && ferror(ffp) != FALSE) { 193 dobeep(); 194 ewprintf("File read error"); 195 return (FIOERR); 196 } 197 *nbytes = i; 198 return (c == EOF ? FIOEOF : FIOSUC); 199} 200 201/* 202 * Make a backup copy of "fname". On Unix the backup has the same 203 * name as the original file, with a "~" on the end; this seems to 204 * be newest of the new-speak. The error handling is all in "file.c". 205 * We do a copy instead of a rename since otherwise another process 206 * with an open fd will get the backup, not the new file. This is 207 * a problem when using mg with things like crontab and vipw. 208 */ 209int 210fbackupfile(const char *fn) 211{ 212 struct stat sb; 213 struct timespec new_times[2]; 214 int from, to, serrno; 215 ssize_t nread; 216 char buf[BUFSIZ]; 217 char *nname, *tname, *bkpth; 218 219 if (stat(fn, &sb) == -1) { 220 dobeep(); 221 ewprintf("Can't stat %s : %s", fn, strerror(errno)); 222 return (FALSE); 223 } 224 225 if ((bkpth = bkuplocation(fn)) == NULL) 226 return (FALSE); 227 228 if (asprintf(&nname, "%s~", bkpth) == -1) { 229 dobeep(); 230 ewprintf("Can't allocate backup file name : %s", strerror(errno)); 231 free(bkpth); 232 return (ABORT); 233 } 234 if (asprintf(&tname, "%s.XXXXXXXXXX", bkpth) == -1) { 235 dobeep(); 236 ewprintf("Can't allocate temp file name : %s", strerror(errno)); 237 free(bkpth); 238 free(nname); 239 return (ABORT); 240 } 241 free(bkpth); 242 243 if ((from = open(fn, O_RDONLY)) == -1) { 244 free(nname); 245 free(tname); 246 return (FALSE); 247 } 248 to = mkstemp(tname); 249 if (to == -1) { 250 serrno = errno; 251 close(from); 252 free(nname); 253 free(tname); 254 errno = serrno; 255 return (FALSE); 256 } 257 while ((nread = read(from, buf, sizeof(buf))) > 0) { 258 if (write(to, buf, (size_t)nread) != nread) { 259 nread = -1; 260 break; 261 } 262 } 263 serrno = errno; 264 (void) fchmod(to, (sb.st_mode & 0777)); 265 266 /* copy the mtime to the backupfile */ 267 new_times[0] = sb.st_atim; 268 new_times[1] = sb.st_mtim; 269 futimens(to, new_times); 270 271 close(from); 272 close(to); 273 if (nread == -1) { 274 if (unlink(tname) == -1) 275 ewprintf("Can't unlink temp : %s", strerror(errno)); 276 } else { 277 if (rename(tname, nname) == -1) { 278 ewprintf("Can't rename temp : %s", strerror(errno)); 279 (void) unlink(tname); 280 nread = -1; 281 } 282 } 283 free(nname); 284 free(tname); 285 errno = serrno; 286 287 return (nread == -1 ? FALSE : TRUE); 288} 289 290/* 291 * Convert "fn" to a canonicalized absolute filename, replacing 292 * a leading ~/ with the user's home dir, following symlinks, and 293 * remove all occurrences of /./ and /../ 294 */ 295char * 296adjustname(const char *fn, int slashslash) 297{ 298 static char fnb[PATH_MAX]; 299 const char *cp, *ep = NULL; 300 char *path; 301 302 if (slashslash == TRUE) { 303 cp = fn + strlen(fn) - 1; 304 for (; cp >= fn; cp--) { 305 if (ep && (*cp == '/')) { 306 fn = ep; 307 break; 308 } 309 if (*cp == '/' || *cp == '~') 310 ep = cp; 311 else 312 ep = NULL; 313 } 314 } 315 if ((path = expandtilde(fn)) == NULL) 316 return (NULL); 317 318 if (realpath(path, fnb) == NULL) 319 (void)strlcpy(fnb, path, sizeof(fnb)); 320 321 free(path); 322 return (fnb); 323} 324 325/* 326 * Find a startup file for the user and return its name. As a service 327 * to other pieces of code that may want to find a startup file (like 328 * the terminal driver in particular), accepts a suffix to be appended 329 * to the startup file name. 330 */ 331FILE * 332startupfile(char *suffix, char *conffile, char *path, size_t len) 333{ 334 FILE *ffp; 335 char *home; 336 int ret; 337 338 if ((home = getenv("HOME")) == NULL || *home == '\0') 339 goto nohome; 340 341 if (conffile != NULL) { 342 (void)strlcpy(path, conffile, len); 343 } else if (suffix == NULL) { 344 ret = snprintf(path, len, _PATH_MG_STARTUP, home); 345 if (ret < 0 || ret >= len) 346 return (NULL); 347 } else { 348 ret = snprintf(path, len, _PATH_MG_TERM, home, suffix); 349 if (ret < 0 || ret >= len) 350 return (NULL); 351 } 352 353 ret = ffropen(&ffp, path, NULL); 354 if (ret == FIOSUC) 355 return (ffp); 356 if (ret == FIODIR) 357 (void)ffclose(ffp, NULL); 358nohome: 359#ifdef STARTUPFILE 360 if (suffix == NULL) { 361 ret = snprintf(path, len, "%s", STARTUPFILE); 362 if (ret < 0 || ret >= len) 363 return (NULL); 364 } else { 365 ret = snprintf(path, len, "%s%s", STARTUPFILE, 366 suffix); 367 if (ret < 0 || ret >= len) 368 return (NULL); 369 } 370 371 ret = ffropen(&ffp, path, NULL); 372 if (ret == FIOSUC) 373 return (ffp); 374 if (ret == FIODIR) 375 (void)ffclose(ffp, NULL); 376#endif /* STARTUPFILE */ 377 return (NULL); 378} 379 380int 381copy(char *frname, char *toname) 382{ 383 int ifd, ofd; 384 char buf[BUFSIZ]; 385 mode_t fmode = DEFFILEMODE; /* XXX?? */ 386 struct stat orig; 387 ssize_t sr; 388 389 if ((ifd = open(frname, O_RDONLY)) == -1) 390 return (FALSE); 391 if (fstat(ifd, &orig) == -1) { 392 dobeep(); 393 ewprintf("fstat: %s", strerror(errno)); 394 close(ifd); 395 return (FALSE); 396 } 397 398 if ((ofd = open(toname, O_WRONLY|O_CREAT|O_TRUNC, fmode)) == -1) { 399 close(ifd); 400 return (FALSE); 401 } 402 while ((sr = read(ifd, buf, sizeof(buf))) > 0) { 403 if (write(ofd, buf, (size_t)sr) != sr) { 404 ewprintf("write error : %s", strerror(errno)); 405 break; 406 } 407 } 408 if (fchmod(ofd, orig.st_mode) == -1) 409 ewprintf("Cannot set original mode : %s", strerror(errno)); 410 411 if (sr == -1) { 412 ewprintf("Read error : %s", strerror(errno)); 413 close(ifd); 414 close(ofd); 415 return (FALSE); 416 } 417 /* 418 * It is "normal" for this to fail since we can't guarantee that 419 * we will be running as root. 420 */ 421 if (fchown(ofd, orig.st_uid, orig.st_gid) && errno != EPERM) 422 ewprintf("Cannot set owner : %s", strerror(errno)); 423 424 (void) close(ifd); 425 (void) close(ofd); 426 427 return (TRUE); 428} 429 430/* 431 * return list of file names that match the name in buf. 432 */ 433struct list * 434make_file_list(char *buf) 435{ 436 char *dir, *file, *cp; 437 size_t len, preflen; 438 int ret; 439 DIR *dirp; 440 struct dirent *dent; 441 struct list *last, *current; 442 char fl_name[NFILEN + 2]; 443 char prefixx[NFILEN + 1]; 444 445 /* 446 * We need three different strings: 447 448 * dir - the name of the directory containing what the user typed. 449 * Must be a real unix file name, e.g. no ~user, etc.. 450 * Must not end in /. 451 * prefix - the portion of what the user typed that is before the 452 * names we are going to find in the directory. Must have a 453 * trailing / if the user typed it. 454 * names from the directory - We open dir, and return prefix 455 * concatenated with names. 456 */ 457 458 /* first we get a directory name we can look up */ 459 /* 460 * Names ending in . are potentially odd, because adjustname will 461 * treat foo/bar/.. as a foo/, whereas we are 462 * interested in names starting with .. 463 */ 464 len = strlen(buf); 465 if (len && buf[len - 1] == '.') { 466 buf[len - 1] = 'x'; 467 dir = adjustname(buf, TRUE); 468 buf[len - 1] = '.'; 469 } else 470 dir = adjustname(buf, TRUE); 471 if (dir == NULL) 472 return (NULL); 473 /* 474 * If the user typed a trailing / or the empty string 475 * he wants us to use his file spec as a directory name. 476 */ 477 if (len && buf[len - 1] != '/') { 478 file = strrchr(dir, '/'); 479 if (file) { 480 *file = '\0'; 481 if (*dir == '\0') 482 dir = "/"; 483 } else 484 return (NULL); 485 } 486 /* Now we get the prefix of the name the user typed. */ 487 if (strlcpy(prefixx, buf, sizeof(prefixx)) >= sizeof(prefixx)) 488 return (NULL); 489 cp = strrchr(prefixx, '/'); 490 if (cp == NULL) 491 prefixx[0] = '\0'; 492 else 493 cp[1] = '\0'; 494 495 preflen = strlen(prefixx); 496 /* cp is the tail of buf that really needs to be compared. */ 497 cp = buf + preflen; 498 len = strlen(cp); 499 500 /* 501 * Now make sure that file names will fit in the buffers allocated. 502 * SV files are fairly short. For BSD, something more general would 503 * be required. 504 */ 505 if (preflen > NFILEN - MAXNAMLEN) 506 return (NULL); 507 508 /* loop over the specified directory, making up the list of files */ 509 510 /* 511 * Note that it is worth our time to filter out names that don't 512 * match, even though our caller is going to do so again, and to 513 * avoid doing the stat if completion is being done, because stat'ing 514 * every file in the directory is relatively expensive. 515 */ 516 517 dirp = opendir(dir); 518 if (dirp == NULL) 519 return (NULL); 520 last = NULL; 521 522 while ((dent = readdir(dirp)) != NULL) { 523 int isdir; 524 if (strncmp(cp, dent->d_name, len) != 0) 525 continue; 526 isdir = 0; 527 if (dent->d_type == DT_DIR) { 528 isdir = 1; 529 } else if (dent->d_type == DT_LNK || 530 dent->d_type == DT_UNKNOWN) { 531 struct stat statbuf; 532 533 if (fstatat(dirfd(dirp), dent->d_name, &statbuf, 0) < 0) 534 continue; 535 if (S_ISDIR(statbuf.st_mode)) 536 isdir = 1; 537 } 538 539 if ((current = malloc(sizeof(struct list))) == NULL) { 540 free_file_list(last); 541 closedir(dirp); 542 return (NULL); 543 } 544 ret = snprintf(fl_name, sizeof(fl_name), 545 "%s%s%s", prefixx, dent->d_name, isdir ? "/" : ""); 546 if (ret < 0 || ret >= sizeof(fl_name)) { 547 free(current); 548 continue; 549 } 550 current->l_next = last; 551 current->l_name = strdup(fl_name); 552 last = current; 553 } 554 closedir(dirp); 555 556 return (last); 557} 558 559/* 560 * Test if a supplied filename refers to a directory 561 * Returns ABORT on error, TRUE if directory. FALSE otherwise 562 */ 563int 564fisdir(const char *fname) 565{ 566 struct stat statbuf; 567 568 if (stat(fname, &statbuf) != 0) 569 return (ABORT); 570 571 if (S_ISDIR(statbuf.st_mode)) 572 return (TRUE); 573 574 return (FALSE); 575} 576 577/* 578 * Check the mtime of the supplied filename. 579 * Return TRUE if last mtime matches, FALSE if not, 580 * If the stat fails, return TRUE and try the save anyway 581 */ 582int 583fchecktime(struct buffer *bp) 584{ 585 struct stat sb; 586 587 if (stat(bp->b_fname, &sb) == -1) 588 return (TRUE); 589 590 if (bp->b_fi.fi_mtime.tv_sec != sb.st_mtim.tv_sec || 591 bp->b_fi.fi_mtime.tv_nsec != sb.st_mtim.tv_nsec) 592 return (FALSE); 593 594 return (TRUE); 595 596} 597 598/* 599 * Location of backup file. This function creates the correct path. 600 */ 601static char * 602bkuplocation(const char *fn) 603{ 604 struct stat sb; 605 char *ret; 606 607 if (bkupdir != NULL && (stat(bkupdir, &sb) == 0) && 608 S_ISDIR(sb.st_mode) && !bkupleavetmp(fn)) { 609 char fname[NFILEN]; 610 const char *c; 611 int i = 0, len; 612 613 c = fn; 614 len = strlen(bkupdir); 615 616 while (*c != '\0') { 617 /* Make sure we don't go over combined: 618 * strlen(bkupdir + '/' + fname + '\0') 619 */ 620 if (i >= NFILEN - len - 1) 621 return (NULL); 622 if (*c == '/') { 623 fname[i] = '!'; 624 } else if (*c == '!') { 625 if (i >= NFILEN - len - 2) 626 return (NULL); 627 fname[i++] = '!'; 628 fname[i] = '!'; 629 } else 630 fname[i] = *c; 631 i++; 632 c++; 633 } 634 fname[i] = '\0'; 635 if (asprintf(&ret, "%s/%s", bkupdir, fname) == -1) 636 return (NULL); 637 638 } else if ((ret = strndup(fn, NFILEN)) == NULL) 639 return (NULL); 640 641 return (ret); 642} 643 644int 645backuptohomedir(int f, int n) 646{ 647 const char *c = _PATH_MG_DIR; 648 char *p; 649 650 if (bkupdir == NULL) { 651 p = adjustname(c, TRUE); 652 bkupdir = strndup(p, NFILEN); 653 if (bkupdir == NULL) 654 return(FALSE); 655 656 if (mkdir(bkupdir, 0700) == -1 && errno != EEXIST) { 657 free(bkupdir); 658 bkupdir = NULL; 659 } 660 } else { 661 free(bkupdir); 662 bkupdir = NULL; 663 } 664 665 return (TRUE); 666} 667 668/* 669 * For applications that use mg as the editor and have a desire to keep 670 * '~' files in /tmp, toggle the location: /tmp | ~/.mg.d 671 */ 672int 673toggleleavetmp(int f, int n) 674{ 675 leavetmp = !leavetmp; 676 677 return (TRUE); 678} 679 680/* 681 * Returns TRUE if fn is located in the temp directory and we want to save 682 * those backups there. 683 */ 684int 685bkupleavetmp(const char *fn) 686{ 687 if (!leavetmp) 688 return(FALSE); 689 690 if (strncmp(fn, "/tmp", 4) == 0) 691 return (TRUE); 692 693 return (FALSE); 694} 695 696/* 697 * Expand file names beginning with '~' if appropriate: 698 * 1, if ./~fn exists, continue without expanding tilde. 699 * 2, else, if username 'fn' exists, expand tilde with home directory path. 700 * 3, otherwise, continue and create new buffer called ~fn. 701 */ 702char * 703expandtilde(const char *fn) 704{ 705 struct passwd *pw; 706 struct stat statbuf; 707 const char *cp; 708 char user[LOGIN_NAME_MAX], path[NFILEN]; 709 char *ret; 710 size_t ulen, plen; 711 712 path[0] = '\0'; 713 714 if (fn[0] != '~' || stat(fn, &statbuf) == 0) { 715 if ((ret = strndup(fn, NFILEN)) == NULL) 716 return (NULL); 717 return(ret); 718 } 719 cp = strchr(fn, '/'); 720 if (cp == NULL) 721 cp = fn + strlen(fn); /* point to the NUL byte */ 722 ulen = cp - &fn[1]; 723 if (ulen >= sizeof(user)) { 724 if ((ret = strndup(fn, NFILEN)) == NULL) 725 return (NULL); 726 return(ret); 727 } 728 if (ulen == 0) /* ~/ or ~ */ 729 pw = getpwuid(geteuid()); 730 else { /* ~user/ or ~user */ 731 memcpy(user, &fn[1], ulen); 732 user[ulen] = '\0'; 733 pw = getpwnam(user); 734 } 735 if (pw != NULL) { 736 plen = strlcpy(path, pw->pw_dir, sizeof(path)); 737 if (plen == 0 || path[plen - 1] != '/') { 738 if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) { 739 dobeep(); 740 ewprintf("Path too long"); 741 return (NULL); 742 } 743 } 744 fn = cp; 745 if (*fn == '/') 746 fn++; 747 } 748 if (strlcat(path, fn, sizeof(path)) >= sizeof(path)) { 749 dobeep(); 750 ewprintf("Path too long"); 751 return (NULL); 752 } 753 if ((ret = strndup(path, NFILEN)) == NULL) 754 return (NULL); 755 756 return (ret); 757} 758