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