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