1 /* $OpenBSD: file.c,v 1.75 2011/01/23 00:45:03 kjell Exp $ */ 2 3 /* This file is in the public domain. */ 4 5 /* 6 * File commands. 7 */ 8 9 #include "def.h" 10 11 #include <libgen.h> 12 13 size_t xdirname(char *, const char *, size_t); 14 15 /* 16 * Insert a file into the current buffer. Real easy - just call the 17 * insertfile routine with the file name. 18 */ 19 /* ARGSUSED */ 20 int 21 fileinsert(int f, int n) 22 { 23 char fname[NFILEN], *bufp, *adjf; 24 25 if (getbufcwd(fname, sizeof(fname)) != TRUE) 26 fname[0] = '\0'; 27 bufp = eread("Insert file: ", fname, NFILEN, 28 EFNEW | EFCR | EFFILE | EFDEF); 29 if (bufp == NULL) 30 return (ABORT); 31 else if (bufp[0] == '\0') 32 return (FALSE); 33 adjf = adjustname(bufp, TRUE); 34 if (adjf == NULL) 35 return (FALSE); 36 return (insertfile(adjf, NULL, FALSE)); 37 } 38 39 /* 40 * Select a file for editing. Look around to see if you can find the file 41 * in another buffer; if you can find it, just switch to the buffer. If 42 * you cannot find the file, create a new buffer, read in the text, and 43 * switch to the new buffer. 44 */ 45 /* ARGSUSED */ 46 int 47 filevisit(int f, int n) 48 { 49 struct buffer *bp; 50 char fname[NFILEN], *bufp, *adjf; 51 int status; 52 53 if (getbufcwd(fname, sizeof(fname)) != TRUE) 54 fname[0] = '\0'; 55 bufp = eread("Find file: ", fname, NFILEN, 56 EFNEW | EFCR | EFFILE | EFDEF); 57 if (bufp == NULL) 58 return (ABORT); 59 else if (bufp[0] == '\0') 60 return (FALSE); 61 adjf = adjustname(fname, TRUE); 62 if (adjf == NULL) 63 return (FALSE); 64 if ((bp = findbuffer(adjf)) == NULL) 65 return (FALSE); 66 curbp = bp; 67 if (showbuffer(bp, curwp, WFFULL) != TRUE) 68 return (FALSE); 69 if (bp->b_fname[0] == '\0') { 70 if ((status = readin(adjf)) != TRUE) 71 killbuffer(bp); 72 return (status); 73 } 74 return (TRUE); 75 } 76 77 /* 78 * Replace the current file with an alternate one. Semantics for finding 79 * the replacement file are the same as 'filevisit', except the current 80 * buffer is killed before the switch. If the kill fails, or is aborted, 81 * revert to the original file. 82 */ 83 /* ARGSUSED */ 84 int 85 filevisitalt(int f, int n) 86 { 87 struct buffer *bp; 88 char fname[NFILEN], *bufp, *adjf; 89 int status; 90 91 if (getbufcwd(fname, sizeof(fname)) != TRUE) 92 fname[0] = '\0'; 93 bufp = eread("Find alternate file: ", fname, NFILEN, 94 EFNEW | EFCR | EFFILE | EFDEF); 95 if (bufp == NULL) 96 return (ABORT); 97 else if (bufp[0] == '\0') 98 return (FALSE); 99 100 status = killbuffer(curbp); 101 if (status == ABORT || status == FALSE) 102 return (ABORT); 103 104 adjf = adjustname(fname, TRUE); 105 if (adjf == NULL) 106 return (FALSE); 107 if ((bp = findbuffer(adjf)) == NULL) 108 return (FALSE); 109 curbp = bp; 110 if (showbuffer(bp, curwp, WFFULL) != TRUE) 111 return (FALSE); 112 if (bp->b_fname[0] == '\0') { 113 if ((status = readin(adjf)) != TRUE) 114 killbuffer(bp); 115 return (status); 116 } 117 return (TRUE); 118 } 119 120 int 121 filevisitro(int f, int n) 122 { 123 int error; 124 125 error = filevisit(f, n); 126 if (error != TRUE) 127 return (error); 128 curbp->b_flag |= BFREADONLY; 129 return (TRUE); 130 } 131 132 /* 133 * Pop to a file in the other window. Same as the last function, but uses 134 * popbuf instead of showbuffer. 135 */ 136 /* ARGSUSED */ 137 int 138 poptofile(int f, int n) 139 { 140 struct buffer *bp; 141 struct mgwin *wp; 142 char fname[NFILEN], *adjf, *bufp; 143 int status; 144 145 if (getbufcwd(fname, sizeof(fname)) != TRUE) 146 fname[0] = '\0'; 147 if ((bufp = eread("Find file in other window: ", fname, NFILEN, 148 EFNEW | EFCR | EFFILE | EFDEF)) == NULL) 149 return (ABORT); 150 else if (bufp[0] == '\0') 151 return (FALSE); 152 adjf = adjustname(fname, TRUE); 153 if (adjf == NULL) 154 return (FALSE); 155 if ((bp = findbuffer(adjf)) == NULL) 156 return (FALSE); 157 if (bp == curbp) 158 return (splitwind(f, n)); 159 if ((wp = popbuf(bp, WNONE)) == NULL) 160 return (FALSE); 161 curbp = bp; 162 curwp = wp; 163 if (bp->b_fname[0] == '\0') { 164 if ((status = readin(adjf)) != TRUE) 165 killbuffer(bp); 166 return (status); 167 } 168 return (TRUE); 169 } 170 171 /* 172 * Given a file name, either find the buffer it uses, or create a new 173 * empty buffer to put it in. 174 */ 175 struct buffer * 176 findbuffer(char *fn) 177 { 178 struct buffer *bp; 179 char bname[NBUFN], fname[NBUFN]; 180 181 if (strlcpy(fname, fn, sizeof(fname)) >= sizeof(fname)) { 182 ewprintf("filename too long"); 183 return (NULL); 184 } 185 186 for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { 187 if (strcmp(bp->b_fname, fname) == 0) 188 return (bp); 189 } 190 /* Not found. Create a new one, adjusting name first */ 191 if (augbname(bname, fname, sizeof(bname)) == FALSE) 192 return (NULL); 193 194 bp = bfind(bname, TRUE); 195 return (bp); 196 } 197 198 /* 199 * Read the file "fname" into the current buffer. Make all of the text 200 * in the buffer go away, after checking for unsaved changes. This is 201 * called by the "read" command, the "visit" command, and the mainline 202 * (for "mg file"). 203 */ 204 int 205 readin(char *fname) 206 { 207 struct mgwin *wp; 208 int status, i, ro = FALSE; 209 PF *ael; 210 211 /* might be old */ 212 if (bclear(curbp) != TRUE) 213 return (TRUE); 214 /* Clear readonly. May be set by autoexec path */ 215 curbp->b_flag &= ~BFREADONLY; 216 if ((status = insertfile(fname, fname, TRUE)) != TRUE) { 217 ewprintf("File is not readable: %s", fname); 218 return (FALSE); 219 } 220 221 for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { 222 if (wp->w_bufp == curbp) { 223 if ((fisdir(fname)) != TRUE) { 224 wp->w_dotp = wp->w_linep = bfirstlp(curbp); 225 wp->w_doto = 0; 226 wp->w_markp = NULL; 227 wp->w_marko = 0; 228 } 229 } 230 } 231 232 /* 233 * Call auto-executing function if we need to. 234 */ 235 if ((ael = find_autoexec(fname)) != NULL) { 236 for (i = 0; ael[i] != NULL; i++) 237 (*ael[i])(0, 1); 238 free(ael); 239 } 240 241 /* no change */ 242 curbp->b_flag &= ~BFCHG; 243 244 /* 245 * We need to set the READONLY flag after we insert the file, 246 * unless the file is a directory. 247 */ 248 if (access(fname, W_OK) && errno != ENOENT) 249 ro = TRUE; 250 if (fisdir(fname) == TRUE) 251 ro = TRUE; 252 if (ro == TRUE) 253 curbp->b_flag |= BFREADONLY; 254 255 if (startrow) { 256 gotoline(FFARG, startrow); 257 startrow = 0; 258 } 259 260 undo_add_modified(); 261 return (status); 262 } 263 264 /* 265 * NB, getting file attributes is done here under control of a flag 266 * rather than in readin, which would be cleaner. I was concerned 267 * that some operating system might require the file to be open 268 * in order to get the information. Similarly for writing. 269 */ 270 271 /* 272 * Insert a file in the current buffer, after dot. If file is a directory, 273 * and 'replacebuf' is TRUE, invoke dired mode, else die with an error. 274 * If file is a regular file, set mark at the end of the text inserted; 275 * point at the beginning. Return a standard status. Print a summary 276 * (lines read, error message) out as well. This routine also does the 277 * read end of backup processing. The BFBAK flag, if set in a buffer, 278 * says that a backup should be taken. It is set when a file is read in, 279 * but not on a new file. You don't need to make a backup copy of nothing. 280 */ 281 282 static char *line = NULL; 283 static int linesize = 0; 284 285 int 286 insertfile(char *fname, char *newname, int replacebuf) 287 { 288 struct buffer *bp; 289 struct line *lp1, *lp2; 290 struct line *olp; /* line we started at */ 291 struct mgwin *wp; 292 int nbytes, s, nline = 0, siz, x, x2; 293 int opos; /* offset we started at */ 294 int oline; /* original line number */ 295 296 if (replacebuf == TRUE) 297 x = undo_enable(FFRAND, 0); 298 else 299 x = undo_enabled(); 300 301 lp1 = NULL; 302 if (line == NULL) { 303 line = malloc(NLINE); 304 if (line == NULL) 305 panic("out of memory"); 306 linesize = NLINE; 307 } 308 309 /* cheap */ 310 bp = curbp; 311 if (newname != NULL) { 312 (void)strlcpy(bp->b_fname, newname, sizeof(bp->b_fname)); 313 (void)xdirname(bp->b_cwd, newname, sizeof(bp->b_cwd)); 314 (void)strlcat(bp->b_cwd, "/", sizeof(bp->b_cwd)); 315 } 316 317 /* hard file open */ 318 if ((s = ffropen(fname, (replacebuf == TRUE) ? bp : NULL)) == FIOERR) 319 goto out; 320 if (s == FIOFNF) { 321 /* file not found */ 322 if (newname != NULL) 323 ewprintf("(New file)"); 324 else 325 ewprintf("(File not found)"); 326 goto out; 327 } else if (s == FIODIR) { 328 /* file was a directory */ 329 if (replacebuf == FALSE) { 330 ewprintf("Cannot insert: file is a directory, %s", 331 fname); 332 goto cleanup; 333 } 334 killbuffer(bp); 335 bp = dired_(fname); 336 undo_enable(FFRAND, x); 337 if (bp == NULL) 338 return (FALSE); 339 curbp = bp; 340 return (showbuffer(bp, curwp, WFFULL | WFMODE)); 341 } else { 342 (void)xdirname(bp->b_cwd, fname, sizeof(bp->b_cwd)); 343 (void)strlcat(bp->b_cwd, "/", sizeof(bp->b_cwd)); 344 } 345 opos = curwp->w_doto; 346 oline = curwp->w_dotline; 347 /* 348 * Open a new line at dot and start inserting after it. 349 * We will delete this newline after insertion. 350 * Disable undo, as we create the undo record manually. 351 */ 352 x2 = undo_enable(FFRAND, 0); 353 (void)lnewline(); 354 olp = lback(curwp->w_dotp); 355 undo_enable(FFRAND, x2); 356 357 nline = 0; 358 siz = 0; 359 while ((s = ffgetline(line, linesize, &nbytes)) != FIOERR) { 360 retry: 361 siz += nbytes + 1; 362 switch (s) { 363 case FIOSUC: 364 /* FALLTHRU */ 365 case FIOEOF: 366 ++nline; 367 if ((lp1 = lalloc(nbytes)) == NULL) { 368 /* keep message on the display */ 369 s = FIOERR; 370 undo_add_insert(olp, opos, 371 siz - nbytes - 1 - 1); 372 goto endoffile; 373 } 374 bcopy(line, <ext(lp1)[0], nbytes); 375 lp2 = lback(curwp->w_dotp); 376 lp2->l_fp = lp1; 377 lp1->l_fp = curwp->w_dotp; 378 lp1->l_bp = lp2; 379 curwp->w_dotp->l_bp = lp1; 380 if (s == FIOEOF) { 381 undo_add_insert(olp, opos, siz - 1); 382 goto endoffile; 383 } 384 break; 385 case FIOLONG: { 386 /* a line too long to fit in our buffer */ 387 char *cp; 388 int newsize; 389 390 newsize = linesize * 2; 391 if (newsize < 0 || 392 (cp = malloc(newsize)) == NULL) { 393 ewprintf("Could not allocate %d bytes", 394 newsize); 395 s = FIOERR; 396 goto endoffile; 397 } 398 bcopy(line, cp, linesize); 399 free(line); 400 line = cp; 401 s = ffgetline(line + linesize, linesize, 402 &nbytes); 403 nbytes += linesize; 404 linesize = newsize; 405 if (s == FIOERR) 406 goto endoffile; 407 goto retry; 408 } 409 default: 410 ewprintf("Unknown code %d reading file", s); 411 s = FIOERR; 412 break; 413 } 414 } 415 endoffile: 416 /* ignore errors */ 417 ffclose(NULL); 418 /* don't zap an error */ 419 if (s == FIOEOF) { 420 if (nline == 1) 421 ewprintf("(Read 1 line)"); 422 else 423 ewprintf("(Read %d lines)", nline); 424 } 425 /* set mark at the end of the text */ 426 curwp->w_dotp = curwp->w_markp = lback(curwp->w_dotp); 427 curwp->w_marko = llength(curwp->w_markp); 428 curwp->w_markline = oline + nline + 1; 429 /* 430 * if we are at the end of the file, ldelnewline is a no-op, 431 * but we still need to decrement the line and markline counts 432 * as we've accounted for this fencepost in our arithmetic 433 */ 434 if (lforw(curwp->w_dotp) == curwp->w_bufp->b_headp) { 435 curwp->w_bufp->b_lines--; 436 curwp->w_markline--; 437 } else 438 (void)ldelnewline(); 439 curwp->w_dotp = olp; 440 curwp->w_doto = opos; 441 curwp->w_dotline = oline; 442 if (olp == curbp->b_headp) 443 curwp->w_dotp = lforw(olp); 444 if (newname != NULL) 445 bp->b_flag |= BFCHG | BFBAK; /* Need a backup. */ 446 else 447 bp->b_flag |= BFCHG; 448 /* 449 * If the insert was at the end of buffer, set lp1 to the end of 450 * buffer line, and lp2 to the beginning of the newly inserted text. 451 * (Otherwise lp2 is set to NULL.) This is used below to set 452 * pointers in other windows correctly if they are also at the end of 453 * buffer. 454 */ 455 lp1 = bp->b_headp; 456 if (curwp->w_markp == lp1) { 457 lp2 = curwp->w_dotp; 458 } else { 459 /* delete extraneous newline */ 460 (void)ldelnewline(); 461 out: lp2 = NULL; 462 } 463 for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { 464 if (wp->w_bufp == curbp) { 465 wp->w_rflag |= WFMODE | WFEDIT; 466 if (wp != curwp && lp2 != NULL) { 467 if (wp->w_dotp == lp1) 468 wp->w_dotp = lp2; 469 if (wp->w_markp == lp1) 470 wp->w_markp = lp2; 471 if (wp->w_linep == lp1) 472 wp->w_linep = lp2; 473 } 474 } 475 } 476 bp->b_lines += nline; 477 cleanup: 478 undo_enable(FFRAND, x); 479 480 /* return FALSE if error */ 481 return (s != FIOERR); 482 } 483 484 /* 485 * Ask for a file name and write the contents of the current buffer to that 486 * file. Update the remembered file name and clear the buffer changed flag. 487 * This handling of file names is different from the earlier versions and 488 * is more compatible with Gosling EMACS than with ITS EMACS. 489 */ 490 /* ARGSUSED */ 491 int 492 filewrite(int f, int n) 493 { 494 int s; 495 char fname[NFILEN], bn[NBUFN]; 496 char *adjfname, *bufp; 497 498 if (getbufcwd(fname, sizeof(fname)) != TRUE) 499 fname[0] = '\0'; 500 if ((bufp = eread("Write file: ", fname, NFILEN, 501 EFDEF | EFNEW | EFCR | EFFILE)) == NULL) 502 return (ABORT); 503 else if (bufp[0] == '\0') 504 return (FALSE); 505 506 adjfname = adjustname(fname, TRUE); 507 if (adjfname == NULL) 508 return (FALSE); 509 /* old attributes are no longer current */ 510 bzero(&curbp->b_fi, sizeof(curbp->b_fi)); 511 if ((s = writeout(curbp, adjfname)) == TRUE) { 512 (void)strlcpy(curbp->b_fname, adjfname, sizeof(curbp->b_fname)); 513 if (getbufcwd(curbp->b_cwd, sizeof(curbp->b_cwd)) != TRUE) 514 (void)strlcpy(curbp->b_cwd, "/", sizeof(curbp->b_cwd)); 515 if (augbname(bn, curbp->b_fname, sizeof(bn)) 516 == FALSE) 517 return (FALSE); 518 free(curbp->b_bname); 519 if ((curbp->b_bname = strdup(bn)) == NULL) 520 return (FALSE); 521 curbp->b_flag &= ~(BFBAK | BFCHG); 522 upmodes(curbp); 523 } 524 return (s); 525 } 526 527 /* 528 * Save the contents of the current buffer back into its associated file. 529 */ 530 #ifndef MAKEBACKUP 531 #define MAKEBACKUP TRUE 532 #endif /* !MAKEBACKUP */ 533 static int makebackup = MAKEBACKUP; 534 535 /* ARGSUSED */ 536 int 537 filesave(int f, int n) 538 { 539 return (buffsave(curbp)); 540 } 541 542 /* 543 * Save the contents of the buffer argument into its associated file. Do 544 * nothing if there have been no changes (is this a bug, or a feature?). 545 * Error if there is no remembered file name. If this is the first write 546 * since the read or visit, then a backup copy of the file is made. 547 * Allow user to select whether or not to make backup files by looking at 548 * the value of makebackup. 549 */ 550 int 551 buffsave(struct buffer *bp) 552 { 553 int s; 554 555 /* return, no changes */ 556 if ((bp->b_flag & BFCHG) == 0) { 557 ewprintf("(No changes need to be saved)"); 558 return (TRUE); 559 } 560 561 /* must have a name */ 562 if (bp->b_fname[0] == '\0') { 563 ewprintf("No file name"); 564 return (FALSE); 565 } 566 567 /* Ensure file has not been modified elsewhere */ 568 /* We don't use the ignore flag here */ 569 if (fchecktime(bp) != TRUE) { 570 if ((s = eyesno("File has changed on disk since last save. " 571 "Save anyway")) != TRUE) 572 return (s); 573 } 574 575 if (makebackup && (bp->b_flag & BFBAK)) { 576 s = fbackupfile(bp->b_fname); 577 /* hard error */ 578 if (s == ABORT) 579 return (FALSE); 580 /* softer error */ 581 if (s == FALSE && 582 (s = eyesno("Backup error, save anyway")) != TRUE) 583 return (s); 584 } 585 if ((s = writeout(bp, bp->b_fname)) == TRUE) { 586 bp->b_flag &= ~(BFCHG | BFBAK); 587 upmodes(bp); 588 } 589 return (s); 590 } 591 592 /* 593 * Since we don't have variables (we probably should) this is a command 594 * processor for changing the value of the make backup flag. If no argument 595 * is given, sets makebackup to true, so backups are made. If an argument is 596 * given, no backup files are made when saving a new version of a file. 597 */ 598 /* ARGSUSED */ 599 int 600 makebkfile(int f, int n) 601 { 602 if (f & FFARG) 603 makebackup = n > 0; 604 else 605 makebackup = !makebackup; 606 ewprintf("Backup files %sabled", makebackup ? "en" : "dis"); 607 return (TRUE); 608 } 609 610 /* 611 * NB: bp is passed to both ffwopen and ffclose because some 612 * attribute information may need to be updated at open time 613 * and others after the close. This is OS-dependent. Note 614 * that the ff routines are assumed to be able to tell whether 615 * the attribute information has been set up in this buffer 616 * or not. 617 */ 618 619 /* 620 * This function performs the details of file writing; writing the file 621 * in buffer bp to file fn. Uses the file management routines in the 622 * "fileio.c" package. Most of the grief is checking of some sort. 623 */ 624 int 625 writeout(struct buffer *bp, char *fn) 626 { 627 int s; 628 629 /* open writes message */ 630 if ((s = ffwopen(fn, bp)) != FIOSUC) 631 return (FALSE); 632 s = ffputbuf(bp); 633 if (s == FIOSUC) { 634 /* no write error */ 635 s = ffclose(bp); 636 if (s == FIOSUC) 637 ewprintf("Wrote %s", fn); 638 } else 639 /* ignore close error if it is a write error */ 640 (void)ffclose(bp); 641 (void)fupdstat(bp); 642 return (s == FIOSUC); 643 } 644 645 /* 646 * Tag all windows for bp (all windows if bp == NULL) as needing their 647 * mode line updated. 648 */ 649 void 650 upmodes(struct buffer *bp) 651 { 652 struct mgwin *wp; 653 654 for (wp = wheadp; wp != NULL; wp = wp->w_wndp) 655 if (bp == NULL || curwp->w_bufp == bp) 656 wp->w_rflag |= WFMODE; 657 } 658 659 /* 660 * dirname using strlcpy semantic. 661 * Like dirname() except an empty string is returned in 662 * place of "/". This means we can always add a trailing 663 * slash and be correct. 664 * Address portability issues by copying argument 665 * before using. Some implementations modify the input string. 666 */ 667 size_t 668 xdirname(char *dp, const char *path, size_t dplen) 669 { 670 char ts[NFILEN]; 671 size_t len; 672 673 (void)strlcpy(ts, path, NFILEN); 674 len = strlcpy(dp, dirname(ts), dplen); 675 if (dplen > 0 && dp[0] == '/' && dp[1] == '\0') { 676 dp[0] = '\0'; 677 len = 0; 678 } 679 return (len); 680 } 681 682 /* 683 * basename using strlcpy/strlcat semantic. 684 * Address portability issue by copying argument 685 * before using: some implementations modify the input string. 686 */ 687 size_t 688 xbasename(char *bp, const char *path, size_t bplen) 689 { 690 char ts[NFILEN]; 691 692 (void)strlcpy(ts, path, NFILEN); 693 return (strlcpy(bp, basename(ts), bplen)); 694 } 695