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