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