1 /* $NetBSD: filename.c,v 1.5 2023/10/06 05:49:49 simonb Exp $ */ 2 3 /* 4 * Copyright (C) 1984-2023 Mark Nudelman 5 * 6 * You may distribute under the terms of either the GNU General Public 7 * License or the Less License, as specified in the README file. 8 * 9 * For more information, see the README file. 10 */ 11 12 13 /* 14 * Routines to mess around with filenames (and files). 15 * Much of this is very OS dependent. 16 */ 17 18 #include "less.h" 19 #include "lglob.h" 20 #if MSDOS_COMPILER 21 #include <dos.h> 22 #if MSDOS_COMPILER==WIN32C && !defined(_MSC_VER) 23 #include <dir.h> 24 #endif 25 #if MSDOS_COMPILER==DJGPPC 26 #include <glob.h> 27 #include <dir.h> 28 #define _MAX_PATH PATH_MAX 29 #endif 30 #endif 31 #ifdef _OSK 32 #include <rbf.h> 33 #ifndef _OSK_MWC32 34 #include <modes.h> 35 #endif 36 #endif 37 38 #if HAVE_STAT 39 #include <sys/stat.h> 40 #ifndef S_ISDIR 41 #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) 42 #endif 43 #ifndef S_ISREG 44 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) 45 #endif 46 #endif 47 48 extern int force_open; 49 extern int secure; 50 extern int use_lessopen; 51 extern int ctldisp; 52 extern int utf_mode; 53 extern IFILE curr_ifile; 54 extern IFILE old_ifile; 55 #if SPACES_IN_FILENAMES 56 extern char openquote; 57 extern char closequote; 58 #endif 59 #if HAVE_STAT_INO 60 extern ino_t curr_ino; 61 extern dev_t curr_dev; 62 #endif 63 64 /* 65 * Remove quotes around a filename. 66 */ 67 public char * shell_unquote(char *str) 68 { 69 char *name; 70 char *p; 71 72 name = p = (char *) ecalloc(strlen(str)+1, sizeof(char)); 73 if (*str == openquote) 74 { 75 str++; 76 while (*str != '\0') 77 { 78 if (*str == closequote) 79 { 80 if (str[1] != closequote) 81 break; 82 str++; 83 } 84 *p++ = *str++; 85 } 86 } else 87 { 88 char *esc = get_meta_escape(); 89 int esclen = (int) strlen(esc); 90 while (*str != '\0') 91 { 92 if (esclen > 0 && strncmp(str, esc, esclen) == 0) 93 str += esclen; 94 *p++ = *str++; 95 } 96 } 97 *p = '\0'; 98 return (name); 99 } 100 101 /* 102 * Get the shell's escape character. 103 */ 104 public char * get_meta_escape(void) 105 { 106 char *s; 107 108 s = lgetenv("LESSMETAESCAPE"); 109 if (s == NULL) 110 s = DEF_METAESCAPE; 111 return (s); 112 } 113 114 /* 115 * Get the characters which the shell considers to be "metacharacters". 116 */ 117 static char * metachars(void) 118 { 119 static char *mchars = NULL; 120 121 if (mchars == NULL) 122 { 123 mchars = lgetenv("LESSMETACHARS"); 124 if (mchars == NULL) 125 mchars = DEF_METACHARS; 126 } 127 return (mchars); 128 } 129 130 /* 131 * Is this a shell metacharacter? 132 */ 133 static int metachar(char c) 134 { 135 return (strchr(metachars(), c) != NULL); 136 } 137 138 /* 139 * Insert a backslash before each metacharacter in a string. 140 */ 141 public char * shell_quote(char *s) 142 { 143 char *p; 144 char *newstr; 145 int len; 146 char *esc = get_meta_escape(); 147 int esclen = (int) strlen(esc); 148 int use_quotes = 0; 149 int have_quotes = 0; 150 151 /* 152 * Determine how big a string we need to allocate. 153 */ 154 len = 1; /* Trailing null byte */ 155 for (p = s; *p != '\0'; p++) 156 { 157 len++; 158 if (*p == openquote || *p == closequote) 159 have_quotes = 1; 160 if (metachar(*p)) 161 { 162 if (esclen == 0) 163 { 164 /* 165 * We've got a metachar, but this shell 166 * doesn't support escape chars. Use quotes. 167 */ 168 use_quotes = 1; 169 } else 170 { 171 /* 172 * Allow space for the escape char. 173 */ 174 len += esclen; 175 } 176 } 177 } 178 if (use_quotes) 179 { 180 if (have_quotes) 181 /* 182 * We can't quote a string that contains quotes. 183 */ 184 return (NULL); 185 len = (int) strlen(s) + 3; 186 } 187 /* 188 * Allocate and construct the new string. 189 */ 190 newstr = p = (char *) ecalloc(len, sizeof(char)); 191 if (use_quotes) 192 { 193 SNPRINTF3(newstr, len, "%c%s%c", openquote, s, closequote); 194 } else 195 { 196 while (*s != '\0') 197 { 198 if (metachar(*s)) 199 { 200 /* 201 * Add the escape char. 202 */ 203 strcpy(p, esc); 204 p += esclen; 205 } 206 *p++ = *s++; 207 } 208 *p = '\0'; 209 } 210 return (newstr); 211 } 212 213 /* 214 * Return a pathname that points to a specified file in a specified directory. 215 * Return NULL if the file does not exist in the directory. 216 */ 217 public char * dirfile(char *dirname, char *filename, int must_exist) 218 { 219 char *pathname; 220 int len; 221 int f; 222 223 if (dirname == NULL || *dirname == '\0') 224 return (NULL); 225 /* 226 * Construct the full pathname. 227 */ 228 len = (int) (strlen(dirname) + strlen(filename) + 2); 229 pathname = (char *) calloc(len, sizeof(char)); 230 if (pathname == NULL) 231 return (NULL); 232 SNPRINTF3(pathname, len, "%s%s%s", dirname, PATHNAME_SEP, filename); 233 if (must_exist) 234 { 235 /* 236 * Make sure the file exists. 237 */ 238 f = open(pathname, OPEN_READ); 239 if (f < 0) 240 { 241 free(pathname); 242 pathname = NULL; 243 } else 244 { 245 close(f); 246 } 247 } 248 return (pathname); 249 } 250 251 /* 252 * Return the full pathname of the given file in the "home directory". 253 */ 254 public char * homefile(char *filename) 255 { 256 char *pathname; 257 258 /* Try $HOME/filename. */ 259 pathname = dirfile(lgetenv("HOME"), filename, 1); 260 if (pathname != NULL) 261 return (pathname); 262 #if OS2 263 /* Try $INIT/filename. */ 264 pathname = dirfile(lgetenv("INIT"), filename, 1); 265 if (pathname != NULL) 266 return (pathname); 267 #endif 268 #if MSDOS_COMPILER || OS2 269 /* Look for the file anywhere on search path. */ 270 pathname = (char *) ecalloc(_MAX_PATH, sizeof(char)); 271 #if MSDOS_COMPILER==DJGPPC 272 { 273 char *res = searchpath(filename); 274 if (res == 0) 275 *pathname = '\0'; 276 else 277 strcpy(pathname, res); 278 } 279 #else 280 _searchenv(filename, "PATH", pathname); 281 #endif 282 if (*pathname != '\0') 283 return (pathname); 284 free(pathname); 285 #endif 286 return (NULL); 287 } 288 289 /* 290 * Expand a string, substituting any "%" with the current filename, 291 * and any "#" with the previous filename. 292 * But a string of N "%"s is just replaced with N-1 "%"s. 293 * Likewise for a string of N "#"s. 294 * {{ This is a lot of work just to support % and #. }} 295 */ 296 public char * fexpand(char *s) 297 { 298 char *fr, *to; 299 int n; 300 char *e; 301 IFILE ifile; 302 303 #define fchar_ifile(c) \ 304 ((c) == '%' ? curr_ifile : \ 305 (c) == '#' ? old_ifile : NULL_IFILE) 306 307 /* 308 * Make one pass to see how big a buffer we 309 * need to allocate for the expanded string. 310 */ 311 n = 0; 312 for (fr = s; *fr != '\0'; fr++) 313 { 314 switch (*fr) 315 { 316 case '%': 317 case '#': 318 if (fr > s && fr[-1] == *fr) 319 { 320 /* 321 * Second (or later) char in a string 322 * of identical chars. Treat as normal. 323 */ 324 n++; 325 } else if (fr[1] != *fr) 326 { 327 /* 328 * Single char (not repeated). Treat specially. 329 */ 330 ifile = fchar_ifile(*fr); 331 if (ifile == NULL_IFILE) 332 n++; 333 else 334 n += (int) strlen(get_filename(ifile)); 335 } 336 /* 337 * Else it is the first char in a string of 338 * identical chars. Just discard it. 339 */ 340 break; 341 default: 342 n++; 343 break; 344 } 345 } 346 347 e = (char *) ecalloc(n+1, sizeof(char)); 348 349 /* 350 * Now copy the string, expanding any "%" or "#". 351 */ 352 to = e; 353 for (fr = s; *fr != '\0'; fr++) 354 { 355 switch (*fr) 356 { 357 case '%': 358 case '#': 359 if (fr > s && fr[-1] == *fr) 360 { 361 *to++ = *fr; 362 } else if (fr[1] != *fr) 363 { 364 ifile = fchar_ifile(*fr); 365 if (ifile == NULL_IFILE) 366 *to++ = *fr; 367 else 368 { 369 strcpy(to, get_filename(ifile)); 370 to += strlen(to); 371 } 372 } 373 break; 374 default: 375 *to++ = *fr; 376 break; 377 } 378 } 379 *to = '\0'; 380 return (e); 381 } 382 383 384 #if TAB_COMPLETE_FILENAME 385 386 /* 387 * Return a blank-separated list of filenames which "complete" 388 * the given string. 389 */ 390 public char * fcomplete(char *s) 391 { 392 char *fpat; 393 char *qs; 394 395 if (secure) 396 return (NULL); 397 /* 398 * Complete the filename "s" by globbing "s*". 399 */ 400 #if MSDOS_COMPILER && (MSDOS_COMPILER == MSOFTC || MSDOS_COMPILER == BORLANDC) 401 /* 402 * But in DOS, we have to glob "s*.*". 403 * But if the final component of the filename already has 404 * a dot in it, just do "s*". 405 * (Thus, "FILE" is globbed as "FILE*.*", 406 * but "FILE.A" is globbed as "FILE.A*"). 407 */ 408 { 409 char *slash; 410 int len; 411 for (slash = s+strlen(s)-1; slash > s; slash--) 412 if (*slash == *PATHNAME_SEP || *slash == '/') 413 break; 414 len = (int) strlen(s) + 4; 415 fpat = (char *) ecalloc(len, sizeof(char)); 416 if (strchr(slash, '.') == NULL) 417 SNPRINTF1(fpat, len, "%s*.*", s); 418 else 419 SNPRINTF1(fpat, len, "%s*", s); 420 } 421 #else 422 { 423 int len = (int) strlen(s) + 2; 424 fpat = (char *) ecalloc(len, sizeof(char)); 425 SNPRINTF1(fpat, len, "%s*", s); 426 } 427 #endif 428 qs = lglob(fpat); 429 s = shell_unquote(qs); 430 if (strcmp(s,fpat) == 0) 431 { 432 /* 433 * The filename didn't expand. 434 */ 435 free(qs); 436 qs = NULL; 437 } 438 free(s); 439 free(fpat); 440 return (qs); 441 } 442 #endif 443 444 /* 445 * Try to determine if a file is "binary". 446 * This is just a guess, and we need not try too hard to make it accurate. 447 */ 448 public int bin_file(int f) 449 { 450 int n; 451 int bin_count = 0; 452 char data[256]; 453 char* p; 454 char* edata; 455 456 if (!seekable(f)) 457 return (0); 458 if (lseek(f, (off_t)0, SEEK_SET) == BAD_LSEEK) 459 return (0); 460 n = read(f, data, sizeof(data)); 461 if (n <= 0) 462 return (0); 463 edata = &data[n]; 464 for (p = data; p < edata; ) 465 { 466 if (utf_mode && !is_utf8_well_formed(p, edata-p)) 467 { 468 bin_count++; 469 utf_skip_to_lead(&p, edata); 470 } else 471 { 472 LWCHAR c = step_char(&p, +1, edata); 473 struct ansi_state *pansi; 474 if (ctldisp == OPT_ONPLUS && (pansi = ansi_start(c)) != NULL) 475 { 476 skip_ansi(pansi, &p, edata); 477 ansi_done(pansi); 478 } else if (binary_char(c)) 479 bin_count++; 480 } 481 } 482 /* 483 * Call it a binary file if there are more than 5 binary characters 484 * in the first 256 bytes of the file. 485 */ 486 return (bin_count > 5); 487 } 488 489 /* 490 * Try to determine the size of a file by seeking to the end. 491 */ 492 static POSITION seek_filesize(int f) 493 { 494 off_t spos; 495 496 spos = lseek(f, (off_t)0, SEEK_END); 497 if (spos == BAD_LSEEK) 498 return (NULL_POSITION); 499 return ((POSITION) spos); 500 } 501 502 #if HAVE_POPEN 503 /* 504 * Read a string from a file. 505 * Return a pointer to the string in memory. 506 */ 507 static char * readfd(FILE *fd) 508 { 509 int len; 510 int ch; 511 char *buf; 512 char *p; 513 514 /* 515 * Make a guess about how many chars in the string 516 * and allocate a buffer to hold it. 517 */ 518 len = 100; 519 buf = (char *) ecalloc(len, sizeof(char)); 520 for (p = buf; ; p++) 521 { 522 if ((ch = getc(fd)) == '\n' || ch == EOF) 523 break; 524 if (p - buf >= len-1) 525 { 526 /* 527 * The string is too big to fit in the buffer we have. 528 * Allocate a new buffer, twice as big. 529 */ 530 len *= 2; 531 *p = '\0'; 532 p = (char *) ecalloc(len, sizeof(char)); 533 strcpy(p, buf); 534 free(buf); 535 buf = p; 536 p = buf + strlen(buf); 537 } 538 *p = ch; 539 } 540 *p = '\0'; 541 return (buf); 542 } 543 544 /* 545 * Execute a shell command. 546 * Return a pointer to a pipe connected to the shell command's standard output. 547 */ 548 static FILE * shellcmd(char *cmd) 549 { 550 FILE *fd; 551 552 #if HAVE_SHELL 553 char *shell; 554 555 shell = lgetenv("SHELL"); 556 if (!isnullenv(shell)) 557 { 558 char *scmd; 559 char *esccmd; 560 561 /* 562 * Read the output of <$SHELL -c cmd>. 563 * Escape any metacharacters in the command. 564 */ 565 esccmd = shell_quote(cmd); 566 if (esccmd == NULL) 567 { 568 fd = popen(cmd, "r"); 569 } else 570 { 571 int len = (int) (strlen(shell) + strlen(esccmd) + 5); 572 scmd = (char *) ecalloc(len, sizeof(char)); 573 SNPRINTF3(scmd, len, "%s %s %s", shell, shell_coption(), esccmd); 574 free(esccmd); 575 fd = popen(scmd, "r"); 576 free(scmd); 577 } 578 } else 579 #endif 580 { 581 fd = popen(cmd, "r"); 582 } 583 /* 584 * Redirection in `popen' might have messed with the 585 * standard devices. Restore binary input mode. 586 */ 587 SET_BINARY(0); 588 return (fd); 589 } 590 591 #endif /* HAVE_POPEN */ 592 593 594 /* 595 * Expand a filename, doing any system-specific metacharacter substitutions. 596 */ 597 public char * lglob(char *filename) 598 { 599 char *gfilename; 600 601 filename = fexpand(filename); 602 if (secure) 603 return (filename); 604 605 #ifdef DECL_GLOB_LIST 606 { 607 /* 608 * The globbing function returns a list of names. 609 */ 610 int length; 611 char *p; 612 char *qfilename; 613 DECL_GLOB_LIST(list) 614 615 GLOB_LIST(filename, list); 616 if (GLOB_LIST_FAILED(list)) 617 { 618 return (filename); 619 } 620 length = 1; /* Room for trailing null byte */ 621 for (SCAN_GLOB_LIST(list, p)) 622 { 623 INIT_GLOB_LIST(list, p); 624 qfilename = shell_quote(p); 625 if (qfilename != NULL) 626 { 627 length += strlen(qfilename) + 1; 628 free(qfilename); 629 } 630 } 631 gfilename = (char *) ecalloc(length, sizeof(char)); 632 for (SCAN_GLOB_LIST(list, p)) 633 { 634 INIT_GLOB_LIST(list, p); 635 qfilename = shell_quote(p); 636 if (qfilename != NULL) 637 { 638 sprintf(gfilename + strlen(gfilename), "%s ", qfilename); 639 free(qfilename); 640 } 641 } 642 /* 643 * Overwrite the final trailing space with a null terminator. 644 */ 645 *--p = '\0'; 646 GLOB_LIST_DONE(list); 647 } 648 #else 649 #ifdef DECL_GLOB_NAME 650 { 651 /* 652 * The globbing function returns a single name, and 653 * is called multiple times to walk thru all names. 654 */ 655 char *p; 656 int len; 657 int n; 658 char *pfilename; 659 char *qfilename; 660 DECL_GLOB_NAME(fnd,drive,dir,fname,ext,handle) 661 662 GLOB_FIRST_NAME(filename, &fnd, handle); 663 if (GLOB_FIRST_FAILED(handle)) 664 { 665 return (filename); 666 } 667 668 _splitpath(filename, drive, dir, fname, ext); 669 len = 100; 670 gfilename = (char *) ecalloc(len, sizeof(char)); 671 p = gfilename; 672 do { 673 n = (int) (strlen(drive) + strlen(dir) + strlen(fnd.GLOB_NAME) + 1); 674 pfilename = (char *) ecalloc(n, sizeof(char)); 675 SNPRINTF3(pfilename, n, "%s%s%s", drive, dir, fnd.GLOB_NAME); 676 qfilename = shell_quote(pfilename); 677 free(pfilename); 678 if (qfilename != NULL) 679 { 680 n = (int) strlen(qfilename); 681 while (p - gfilename + n + 2 >= len) 682 { 683 /* 684 * No room in current buffer. 685 * Allocate a bigger one. 686 */ 687 len *= 2; 688 *p = '\0'; 689 p = (char *) ecalloc(len, sizeof(char)); 690 strcpy(p, gfilename); 691 free(gfilename); 692 gfilename = p; 693 p = gfilename + strlen(gfilename); 694 } 695 strcpy(p, qfilename); 696 free(qfilename); 697 p += n; 698 *p++ = ' '; 699 } 700 } while (GLOB_NEXT_NAME(handle, &fnd) == 0); 701 702 /* 703 * Overwrite the final trailing space with a null terminator. 704 */ 705 *--p = '\0'; 706 GLOB_NAME_DONE(handle); 707 } 708 #else 709 #if HAVE_POPEN 710 { 711 /* 712 * We get the shell to glob the filename for us by passing 713 * an "echo" command to the shell and reading its output. 714 */ 715 FILE *fd; 716 char *s; 717 char *lessecho; 718 char *cmd; 719 char *esc; 720 int len; 721 722 esc = get_meta_escape(); 723 if (strlen(esc) == 0) 724 esc = "-"; 725 esc = shell_quote(esc); 726 if (esc == NULL) 727 { 728 return (filename); 729 } 730 lessecho = lgetenv("LESSECHO"); 731 if (isnullenv(lessecho)) 732 lessecho = "lessecho"; 733 /* 734 * Invoke lessecho, and read its output (a globbed list of filenames). 735 */ 736 len = (int) (strlen(lessecho) + strlen(filename) + (7*strlen(metachars())) + 24); 737 cmd = (char *) ecalloc(len, sizeof(char)); 738 SNPRINTF4(cmd, len, "%s -p0x%x -d0x%x -e%s ", lessecho, 739 (unsigned char) openquote, (unsigned char) closequote, esc); 740 free(esc); 741 for (s = metachars(); *s != '\0'; s++) 742 sprintf(cmd + strlen(cmd), "-n0x%x ", (unsigned char) *s); 743 sprintf(cmd + strlen(cmd), "-- %s", filename); 744 fd = shellcmd(cmd); 745 free(cmd); 746 if (fd == NULL) 747 { 748 /* 749 * Cannot create the pipe. 750 * Just return the original (fexpanded) filename. 751 */ 752 return (filename); 753 } 754 gfilename = readfd(fd); 755 pclose(fd); 756 if (*gfilename == '\0') 757 { 758 free(gfilename); 759 return (filename); 760 } 761 } 762 #else 763 /* 764 * No globbing functions at all. Just use the fexpanded filename. 765 */ 766 gfilename = save(filename); 767 #endif 768 #endif 769 #endif 770 free(filename); 771 return (gfilename); 772 } 773 774 /* 775 * Does path not represent something in the file system? 776 */ 777 public int is_fake_pathname(char *path) 778 { 779 return (strcmp(path, "-") == 0 || 780 strcmp(path, FAKE_HELPFILE) == 0 || strcmp(path, FAKE_EMPTYFILE) == 0); 781 } 782 783 /* 784 * Return canonical pathname. 785 */ 786 public char * lrealpath(char *path) 787 { 788 if (!is_fake_pathname(path)) 789 { 790 #if HAVE_REALPATH 791 char rpath[PATH_MAX]; 792 if (realpath(path, rpath) != NULL) 793 return (save(rpath)); 794 #endif 795 } 796 return (save(path)); 797 } 798 799 #if HAVE_POPEN 800 /* 801 * Return number of %s escapes in a string. 802 * Return a large number if there are any other % escapes besides %s. 803 */ 804 static int num_pct_s(char *lessopen) 805 { 806 int num = 0; 807 808 while (*lessopen != '\0') 809 { 810 if (*lessopen == '%') 811 { 812 if (lessopen[1] == '%') 813 ++lessopen; 814 else if (lessopen[1] == 's') 815 ++num; 816 else 817 return (999); 818 } 819 ++lessopen; 820 } 821 return (num); 822 } 823 #endif 824 825 /* 826 * See if we should open a "replacement file" 827 * instead of the file we're about to open. 828 */ 829 public char * open_altfile(char *filename, int *pf, void **pfd) 830 { 831 #if !HAVE_POPEN 832 return (NULL); 833 #else 834 char *lessopen; 835 char *qfilename; 836 char *cmd; 837 int len; 838 FILE *fd; 839 #if HAVE_FILENO 840 int returnfd = 0; 841 #endif 842 843 if (!use_lessopen || secure) 844 return (NULL); 845 ch_ungetchar(-1); 846 if ((lessopen = lgetenv("LESSOPEN")) == NULL) 847 return (NULL); 848 while (*lessopen == '|') 849 { 850 /* 851 * If LESSOPEN starts with a |, it indicates 852 * a "pipe preprocessor". 853 */ 854 #if !HAVE_FILENO 855 error("LESSOPEN pipe is not supported", NULL_PARG); 856 return (NULL); 857 #else 858 lessopen++; 859 returnfd++; 860 #endif 861 } 862 if (*lessopen == '-') 863 { 864 /* 865 * Lessopen preprocessor will accept "-" as a filename. 866 */ 867 lessopen++; 868 } else 869 { 870 if (strcmp(filename, "-") == 0) 871 return (NULL); 872 } 873 if (num_pct_s(lessopen) != 1) 874 { 875 error("LESSOPEN ignored: must contain exactly one %%s", NULL_PARG); 876 return (NULL); 877 } 878 879 qfilename = shell_quote(filename); 880 len = (int) (strlen(lessopen) + strlen(qfilename) + 2); 881 cmd = (char *) ecalloc(len, sizeof(char)); 882 SNPRINTF1(cmd, len, lessopen, qfilename); 883 free(qfilename); 884 fd = shellcmd(cmd); 885 free(cmd); 886 if (fd == NULL) 887 { 888 /* 889 * Cannot create the pipe. 890 */ 891 return (NULL); 892 } 893 #if HAVE_FILENO 894 if (returnfd) 895 { 896 char c; 897 int f; 898 899 /* 900 * The alt file is a pipe. Read one char 901 * to see if the pipe will produce any data. 902 * If it does, push the char back on the pipe. 903 */ 904 f = fileno(fd); 905 SET_BINARY(f); 906 if (read(f, &c, 1) != 1) 907 { 908 /* 909 * Pipe is empty. 910 * If more than 1 pipe char was specified, 911 * the exit status tells whether the file itself 912 * is empty, or if there is no alt file. 913 * If only one pipe char, just assume no alt file. 914 */ 915 int status = pclose(fd); 916 if (returnfd > 1 && status == 0) { 917 /* File is empty. */ 918 *pfd = NULL; 919 *pf = -1; 920 return (save(FAKE_EMPTYFILE)); 921 } 922 /* No alt file. */ 923 return (NULL); 924 } 925 /* Alt pipe contains data, so use it. */ 926 ch_ungetchar(c); 927 *pfd = (void *) fd; 928 *pf = f; 929 return (save("-")); 930 } 931 #endif 932 /* The alt file is a regular file. Read its name from LESSOPEN. */ 933 cmd = readfd(fd); 934 pclose(fd); 935 if (*cmd == '\0') 936 { 937 /* 938 * Pipe is empty. This means there is no alt file. 939 */ 940 free(cmd); 941 return (NULL); 942 } 943 return (cmd); 944 #endif /* HAVE_POPEN */ 945 } 946 947 /* 948 * Close a replacement file. 949 */ 950 public void close_altfile(char *altfilename, char *filename) 951 { 952 #if HAVE_POPEN 953 char *lessclose; 954 char *qfilename; 955 char *qaltfilename; 956 FILE *fd; 957 char *cmd; 958 int len; 959 960 if (secure) 961 return; 962 ch_ungetchar(-1); 963 if ((lessclose = lgetenv("LESSCLOSE")) == NULL) 964 return; 965 if (num_pct_s(lessclose) > 2) 966 { 967 error("LESSCLOSE ignored; must contain no more than 2 %%s", NULL_PARG); 968 return; 969 } 970 qfilename = shell_quote(filename); 971 qaltfilename = shell_quote(altfilename); 972 len = (int) (strlen(lessclose) + strlen(qfilename) + strlen(qaltfilename) + 2); 973 cmd = (char *) ecalloc(len, sizeof(char)); 974 SNPRINTF2(cmd, len, lessclose, qfilename, qaltfilename); 975 free(qaltfilename); 976 free(qfilename); 977 fd = shellcmd(cmd); 978 free(cmd); 979 if (fd != NULL) 980 pclose(fd); 981 #endif 982 } 983 984 /* 985 * Is the specified file a directory? 986 */ 987 public int is_dir(char *filename) 988 { 989 int isdir = 0; 990 991 #if HAVE_STAT 992 { 993 int r; 994 struct stat statbuf; 995 996 r = stat(filename, &statbuf); 997 isdir = (r >= 0 && S_ISDIR(statbuf.st_mode)); 998 } 999 #else 1000 #ifdef _OSK 1001 { 1002 int f; 1003 1004 f = open(filename, S_IREAD | S_IFDIR); 1005 if (f >= 0) 1006 close(f); 1007 isdir = (f >= 0); 1008 } 1009 #endif 1010 #endif 1011 return (isdir); 1012 } 1013 1014 /* 1015 * Returns NULL if the file can be opened and 1016 * is an ordinary file, otherwise an error message 1017 * (if it cannot be opened or is a directory, etc.) 1018 */ 1019 public char * bad_file(char *filename) 1020 { 1021 char *m = NULL; 1022 1023 if (!force_open && is_dir(filename)) 1024 { 1025 static char is_a_dir[] = " is a directory"; 1026 1027 m = (char *) ecalloc(strlen(filename) + sizeof(is_a_dir), 1028 sizeof(char)); 1029 strcpy(m, filename); 1030 strcat(m, is_a_dir); 1031 } else 1032 { 1033 #if HAVE_STAT 1034 int r; 1035 struct stat statbuf; 1036 1037 r = stat(filename, &statbuf); 1038 if (r < 0) 1039 { 1040 m = errno_message(filename); 1041 } else if (force_open) 1042 { 1043 m = NULL; 1044 } else if (!S_ISREG(statbuf.st_mode)) 1045 { 1046 static char not_reg[] = " is not a regular file (use -f to see it)"; 1047 m = (char *) ecalloc(strlen(filename) + sizeof(not_reg), 1048 sizeof(char)); 1049 strcpy(m, filename); 1050 strcat(m, not_reg); 1051 } 1052 #endif 1053 } 1054 return (m); 1055 } 1056 1057 /* 1058 * Return the size of a file, as cheaply as possible. 1059 * In Unix, we can stat the file. 1060 */ 1061 public POSITION filesize(int f) 1062 { 1063 #if HAVE_STAT 1064 struct stat statbuf; 1065 1066 if (fstat(f, &statbuf) >= 0) 1067 return ((POSITION) statbuf.st_size); 1068 #else 1069 #ifdef _OSK 1070 long size; 1071 1072 if ((size = (long) _gs_size(f)) >= 0) 1073 return ((POSITION) size); 1074 #endif 1075 #endif 1076 return (seek_filesize(f)); 1077 } 1078 1079 public int curr_ifile_changed(void) 1080 { 1081 #if HAVE_STAT_INO 1082 /* 1083 * If the file's i-number or device has changed, 1084 * or if the file is smaller than it previously was, 1085 * the file must be different. 1086 */ 1087 struct stat st; 1088 POSITION curr_pos = ch_tell(); 1089 int r = stat(get_filename(curr_ifile), &st); 1090 if (r == 0 && (st.st_ino != curr_ino || 1091 st.st_dev != curr_dev || 1092 (curr_pos != NULL_POSITION && st.st_size < curr_pos))) 1093 return (TRUE); 1094 #endif 1095 return (FALSE); 1096 } 1097 1098 /* 1099 * 1100 */ 1101 public char * shell_coption(void) 1102 { 1103 return ("-c"); 1104 } 1105 1106 /* 1107 * Return last component of a pathname. 1108 */ 1109 public char * last_component(char *name) 1110 { 1111 char *slash; 1112 1113 for (slash = name + strlen(name); slash > name; ) 1114 { 1115 --slash; 1116 if (*slash == *PATHNAME_SEP || *slash == '/') 1117 return (slash + 1); 1118 } 1119 return (name); 1120 } 1121