1 /* $OpenBSD: fileio.c,v 1.106 2019/06/22 10:21:57 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('\n', ffp); 167 } 168 if (eobnl) { 169 lnewline_at(lback(lpend), llength(lback(lpend))); 170 putc('\n', 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 != '\n') { 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) 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 (suffix == NULL) { 343 ret = snprintf(file, sizeof(file), _PATH_MG_STARTUP, home); 344 if (ret < 0 || ret >= sizeof(file)) 345 return (NULL); 346 } else { 347 ret = snprintf(file, sizeof(file), _PATH_MG_TERM, home, suffix); 348 if (ret < 0 || ret >= sizeof(file)) 349 return (NULL); 350 } 351 352 if (access(file, R_OK) == 0) 353 return (file); 354 nohome: 355 #ifdef STARTUPFILE 356 if (suffix == NULL) { 357 ret = snprintf(file, sizeof(file), "%s", STARTUPFILE); 358 if (ret < 0 || ret >= sizeof(file)) 359 return (NULL); 360 } else { 361 ret = snprintf(file, sizeof(file), "%s%s", STARTUPFILE, 362 suffix); 363 if (ret < 0 || ret >= sizeof(file)) 364 return (NULL); 365 } 366 367 if (access(file, R_OK) == 0) 368 return (file); 369 #endif /* STARTUPFILE */ 370 return (NULL); 371 } 372 373 int 374 copy(char *frname, char *toname) 375 { 376 int ifd, ofd; 377 char buf[BUFSIZ]; 378 mode_t fmode = DEFFILEMODE; /* XXX?? */ 379 struct stat orig; 380 ssize_t sr; 381 382 if ((ifd = open(frname, O_RDONLY)) == -1) 383 return (FALSE); 384 if (fstat(ifd, &orig) == -1) { 385 dobeep(); 386 ewprintf("fstat: %s", strerror(errno)); 387 close(ifd); 388 return (FALSE); 389 } 390 391 if ((ofd = open(toname, O_WRONLY|O_CREAT|O_TRUNC, fmode)) == -1) { 392 close(ifd); 393 return (FALSE); 394 } 395 while ((sr = read(ifd, buf, sizeof(buf))) > 0) { 396 if (write(ofd, buf, (size_t)sr) != sr) { 397 ewprintf("write error : %s", strerror(errno)); 398 break; 399 } 400 } 401 if (fchmod(ofd, orig.st_mode) == -1) 402 ewprintf("Cannot set original mode : %s", strerror(errno)); 403 404 if (sr == -1) { 405 ewprintf("Read error : %s", strerror(errno)); 406 close(ifd); 407 close(ofd); 408 return (FALSE); 409 } 410 /* 411 * It is "normal" for this to fail since we can't guarantee that 412 * we will be running as root. 413 */ 414 if (fchown(ofd, orig.st_uid, orig.st_gid) && errno != EPERM) 415 ewprintf("Cannot set owner : %s", strerror(errno)); 416 417 (void) close(ifd); 418 (void) close(ofd); 419 420 return (TRUE); 421 } 422 423 /* 424 * return list of file names that match the name in buf. 425 */ 426 struct list * 427 make_file_list(char *buf) 428 { 429 char *dir, *file, *cp; 430 size_t len, preflen; 431 int ret; 432 DIR *dirp; 433 struct dirent *dent; 434 struct list *last, *current; 435 char fl_name[NFILEN + 2]; 436 char prefixx[NFILEN + 1]; 437 438 /* 439 * We need three different strings: 440 441 * dir - the name of the directory containing what the user typed. 442 * Must be a real unix file name, e.g. no ~user, etc.. 443 * Must not end in /. 444 * prefix - the portion of what the user typed that is before the 445 * names we are going to find in the directory. Must have a 446 * trailing / if the user typed it. 447 * names from the directory - We open dir, and return prefix 448 * concatenated with names. 449 */ 450 451 /* first we get a directory name we can look up */ 452 /* 453 * Names ending in . are potentially odd, because adjustname will 454 * treat foo/bar/.. as a foo/, whereas we are 455 * interested in names starting with .. 456 */ 457 len = strlen(buf); 458 if (len && buf[len - 1] == '.') { 459 buf[len - 1] = 'x'; 460 dir = adjustname(buf, TRUE); 461 buf[len - 1] = '.'; 462 } else 463 dir = adjustname(buf, TRUE); 464 if (dir == NULL) 465 return (NULL); 466 /* 467 * If the user typed a trailing / or the empty string 468 * he wants us to use his file spec as a directory name. 469 */ 470 if (len && buf[len - 1] != '/') { 471 file = strrchr(dir, '/'); 472 if (file) { 473 *file = '\0'; 474 if (*dir == '\0') 475 dir = "/"; 476 } else 477 return (NULL); 478 } 479 /* Now we get the prefix of the name the user typed. */ 480 if (strlcpy(prefixx, buf, sizeof(prefixx)) >= sizeof(prefixx)) 481 return (NULL); 482 cp = strrchr(prefixx, '/'); 483 if (cp == NULL) 484 prefixx[0] = '\0'; 485 else 486 cp[1] = '\0'; 487 488 preflen = strlen(prefixx); 489 /* cp is the tail of buf that really needs to be compared. */ 490 cp = buf + preflen; 491 len = strlen(cp); 492 493 /* 494 * Now make sure that file names will fit in the buffers allocated. 495 * SV files are fairly short. For BSD, something more general would 496 * be required. 497 */ 498 if (preflen > NFILEN - MAXNAMLEN) 499 return (NULL); 500 501 /* loop over the specified directory, making up the list of files */ 502 503 /* 504 * Note that it is worth our time to filter out names that don't 505 * match, even though our caller is going to do so again, and to 506 * avoid doing the stat if completion is being done, because stat'ing 507 * every file in the directory is relatively expensive. 508 */ 509 510 dirp = opendir(dir); 511 if (dirp == NULL) 512 return (NULL); 513 last = NULL; 514 515 while ((dent = readdir(dirp)) != NULL) { 516 int isdir; 517 if (strncmp(cp, dent->d_name, len) != 0) 518 continue; 519 isdir = 0; 520 if (dent->d_type == DT_DIR) { 521 isdir = 1; 522 } else if (dent->d_type == DT_LNK || 523 dent->d_type == DT_UNKNOWN) { 524 struct stat statbuf; 525 526 if (fstatat(dirfd(dirp), dent->d_name, &statbuf, 0) < 0) 527 continue; 528 if (S_ISDIR(statbuf.st_mode)) 529 isdir = 1; 530 } 531 532 if ((current = malloc(sizeof(struct list))) == NULL) { 533 free_file_list(last); 534 closedir(dirp); 535 return (NULL); 536 } 537 ret = snprintf(fl_name, sizeof(fl_name), 538 "%s%s%s", prefixx, dent->d_name, isdir ? "/" : ""); 539 if (ret < 0 || ret >= sizeof(fl_name)) { 540 free(current); 541 continue; 542 } 543 current->l_next = last; 544 current->l_name = strdup(fl_name); 545 last = current; 546 } 547 closedir(dirp); 548 549 return (last); 550 } 551 552 /* 553 * Test if a supplied filename refers to a directory 554 * Returns ABORT on error, TRUE if directory. FALSE otherwise 555 */ 556 int 557 fisdir(const char *fname) 558 { 559 struct stat statbuf; 560 561 if (stat(fname, &statbuf) != 0) 562 return (ABORT); 563 564 if (S_ISDIR(statbuf.st_mode)) 565 return (TRUE); 566 567 return (FALSE); 568 } 569 570 /* 571 * Check the mtime of the supplied filename. 572 * Return TRUE if last mtime matches, FALSE if not, 573 * If the stat fails, return TRUE and try the save anyway 574 */ 575 int 576 fchecktime(struct buffer *bp) 577 { 578 struct stat sb; 579 580 if (stat(bp->b_fname, &sb) == -1) 581 return (TRUE); 582 583 if (bp->b_fi.fi_mtime.tv_sec != sb.st_mtimespec.tv_sec || 584 bp->b_fi.fi_mtime.tv_nsec != sb.st_mtimespec.tv_nsec) 585 return (FALSE); 586 587 return (TRUE); 588 589 } 590 591 /* 592 * Location of backup file. This function creates the correct path. 593 */ 594 static char * 595 bkuplocation(const char *fn) 596 { 597 struct stat sb; 598 char *ret; 599 600 if (bkupdir != NULL && (stat(bkupdir, &sb) == 0) && 601 S_ISDIR(sb.st_mode) && !bkupleavetmp(fn)) { 602 char fname[NFILEN]; 603 const char *c; 604 int i = 0, len; 605 606 c = fn; 607 len = strlen(bkupdir); 608 609 while (*c != '\0') { 610 /* Make sure we don't go over combined: 611 * strlen(bkupdir + '/' + fname + '\0') 612 */ 613 if (i >= NFILEN - len - 1) 614 return (NULL); 615 if (*c == '/') { 616 fname[i] = '!'; 617 } else if (*c == '!') { 618 if (i >= NFILEN - len - 2) 619 return (NULL); 620 fname[i++] = '!'; 621 fname[i] = '!'; 622 } else 623 fname[i] = *c; 624 i++; 625 c++; 626 } 627 fname[i] = '\0'; 628 if (asprintf(&ret, "%s/%s", bkupdir, fname) == -1) 629 return (NULL); 630 631 } else if ((ret = strndup(fn, NFILEN)) == NULL) 632 return (NULL); 633 634 return (ret); 635 } 636 637 int 638 backuptohomedir(int f, int n) 639 { 640 const char *c = _PATH_MG_DIR; 641 char *p; 642 643 if (bkupdir == NULL) { 644 p = adjustname(c, TRUE); 645 bkupdir = strndup(p, NFILEN); 646 if (bkupdir == NULL) 647 return(FALSE); 648 649 if (mkdir(bkupdir, 0700) == -1 && errno != EEXIST) { 650 free(bkupdir); 651 bkupdir = NULL; 652 } 653 } else { 654 free(bkupdir); 655 bkupdir = NULL; 656 } 657 658 return (TRUE); 659 } 660 661 /* 662 * For applications that use mg as the editor and have a desire to keep 663 * '~' files in /tmp, toggle the location: /tmp | ~/.mg.d 664 */ 665 int 666 toggleleavetmp(int f, int n) 667 { 668 leavetmp = !leavetmp; 669 670 return (TRUE); 671 } 672 673 /* 674 * Returns TRUE if fn is located in the temp directory and we want to save 675 * those backups there. 676 */ 677 int 678 bkupleavetmp(const char *fn) 679 { 680 if (!leavetmp) 681 return(FALSE); 682 683 if (strncmp(fn, "/tmp", 4) == 0) 684 return (TRUE); 685 686 return (FALSE); 687 } 688 689 /* 690 * Expand file names beginning with '~' if appropriate: 691 * 1, if ./~fn exists, continue without expanding tilde. 692 * 2, else, if username 'fn' exists, expand tilde with home directory path. 693 * 3, otherwise, continue and create new buffer called ~fn. 694 */ 695 char * 696 expandtilde(const char *fn) 697 { 698 struct passwd *pw; 699 struct stat statbuf; 700 const char *cp; 701 char user[LOGIN_NAME_MAX], path[NFILEN]; 702 char *ret; 703 size_t ulen, plen; 704 705 path[0] = '\0'; 706 707 if (fn[0] != '~' || stat(fn, &statbuf) == 0) { 708 if ((ret = strndup(fn, NFILEN)) == NULL) 709 return (NULL); 710 return(ret); 711 } 712 cp = strchr(fn, '/'); 713 if (cp == NULL) 714 cp = fn + strlen(fn); /* point to the NUL byte */ 715 ulen = cp - &fn[1]; 716 if (ulen >= sizeof(user)) { 717 if ((ret = strndup(fn, NFILEN)) == NULL) 718 return (NULL); 719 return(ret); 720 } 721 if (ulen == 0) /* ~/ or ~ */ 722 pw = getpwuid(geteuid()); 723 else { /* ~user/ or ~user */ 724 memcpy(user, &fn[1], ulen); 725 user[ulen] = '\0'; 726 pw = getpwnam(user); 727 } 728 if (pw != NULL) { 729 plen = strlcpy(path, pw->pw_dir, sizeof(path)); 730 if (plen == 0 || path[plen - 1] != '/') { 731 if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) { 732 dobeep(); 733 ewprintf("Path too long"); 734 return (NULL); 735 } 736 } 737 fn = cp; 738 if (*fn == '/') 739 fn++; 740 } 741 if (strlcat(path, fn, sizeof(path)) >= sizeof(path)) { 742 dobeep(); 743 ewprintf("Path too long"); 744 return (NULL); 745 } 746 if ((ret = strndup(path, NFILEN)) == NULL) 747 return (NULL); 748 749 return (ret); 750 } 751