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