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