1 /* 2 * Copyright (C) 1984-2012 Mark Nudelman 3 * Modified for use with illumos by Garrett D'Amore. 4 * Copyright 2014 Garrett D'Amore <garrett@damore.org> 5 * 6 * You may distribute under the terms of either the GNU General Public 7 * License or the Less License, as specified in the README file. 8 * 9 * For more information, see the README file. 10 */ 11 12 /* 13 * Routines to mess around with filenames (and files). 14 * Much of this is very OS dependent. 15 * 16 * Modified for illumos/POSIX -- it uses native glob(3C) rather than 17 * popen to a shell to perform the expansion. 18 */ 19 20 #include <sys/stat.h> 21 22 #include <glob.h> 23 #include <stdarg.h> 24 25 #include "less.h" 26 27 extern int force_open; 28 extern int secure; 29 extern int ctldisp; 30 extern int utf_mode; 31 extern IFILE curr_ifile; 32 extern IFILE old_ifile; 33 extern char openquote; 34 extern char closequote; 35 36 /* 37 * Remove quotes around a filename. 38 */ 39 char * 40 shell_unquote(char *str) 41 { 42 char *name; 43 char *p; 44 45 name = p = ecalloc(strlen(str)+1, sizeof (char)); 46 if (*str == openquote) { 47 str++; 48 while (*str != '\0') { 49 if (*str == closequote) { 50 if (str[1] != closequote) 51 break; 52 str++; 53 } 54 *p++ = *str++; 55 } 56 } else { 57 char *esc = get_meta_escape(); 58 int esclen = strlen(esc); 59 while (*str != '\0') { 60 if (esclen > 0 && strncmp(str, esc, esclen) == 0) 61 str += esclen; 62 *p++ = *str++; 63 } 64 } 65 *p = '\0'; 66 return (name); 67 } 68 69 /* 70 * Get the shell's escape character. 71 */ 72 char * 73 get_meta_escape(void) 74 { 75 char *s; 76 77 s = lgetenv("LESSMETAESCAPE"); 78 if (s == NULL) 79 s = "\\"; 80 return (s); 81 } 82 83 /* 84 * Get the characters which the shell considers to be "metacharacters". 85 */ 86 static char * 87 metachars(void) 88 { 89 static char *mchars = NULL; 90 91 if (mchars == NULL) { 92 mchars = lgetenv("LESSMETACHARS"); 93 if (mchars == NULL) 94 mchars = DEF_METACHARS; 95 } 96 return (mchars); 97 } 98 99 /* 100 * Is this a shell metacharacter? 101 */ 102 static int 103 metachar(char c) 104 { 105 return (strchr(metachars(), c) != NULL); 106 } 107 108 /* 109 * Must use quotes rather than escape characters for this meta character. 110 */ 111 static int 112 must_quote(char c) 113 { 114 return (c == '\n'); 115 } 116 117 /* 118 * Insert a backslash before each metacharacter in a string. 119 */ 120 char * 121 shell_quote(const char *s) 122 { 123 const char *p; 124 char *r; 125 char *newstr; 126 int len; 127 char *esc = get_meta_escape(); 128 int esclen = strlen(esc); 129 int use_quotes = 0; 130 int have_quotes = 0; 131 132 /* 133 * Determine how big a string we need to allocate. 134 */ 135 len = 1; /* Trailing null byte */ 136 for (p = s; *p != '\0'; p++) { 137 len++; 138 if (*p == openquote || *p == closequote) 139 have_quotes = 1; 140 if (metachar(*p)) { 141 if (esclen == 0) { 142 /* 143 * We've got a metachar, but this shell 144 * doesn't support escape chars. Use quotes. 145 */ 146 use_quotes = 1; 147 } else if (must_quote(*p)) { 148 /* Opening quote + character + closing quote. */ 149 len += 3; 150 } else { 151 /* 152 * Allow space for the escape char. 153 */ 154 len += esclen; 155 } 156 } 157 } 158 /* 159 * Allocate and construct the new string. 160 */ 161 if (use_quotes) { 162 /* We can't quote a string that contains quotes. */ 163 if (have_quotes) 164 return (NULL); 165 newstr = easprintf("%c%s%c", openquote, s, closequote); 166 } else { 167 newstr = r = ecalloc(len, sizeof (char)); 168 while (*s != '\0') { 169 if (!metachar(*s)) { 170 *r++ = *s++; 171 } else if (must_quote(*s)) { 172 /* Surround the character with quotes. */ 173 *r++ = openquote; 174 *r++ = *s++; 175 *r++ = closequote; 176 } else { 177 /* Escape the character. */ 178 (void) strlcpy(r, esc, newstr + len - p); 179 r += esclen; 180 *r++ = *s++; 181 } 182 } 183 *r = '\0'; 184 } 185 return (newstr); 186 } 187 188 /* 189 * Return a pathname that points to a specified file in a specified directory. 190 * Return NULL if the file does not exist in the directory. 191 */ 192 static char * 193 dirfile(const char *dirname, const char *filename) 194 { 195 char *pathname; 196 char *qpathname; 197 int f; 198 199 if (dirname == NULL || *dirname == '\0') 200 return (NULL); 201 /* 202 * Construct the full pathname. 203 */ 204 pathname = easprintf("%s/%s", dirname, filename); 205 /* 206 * Make sure the file exists. 207 */ 208 qpathname = shell_unquote(pathname); 209 f = open(qpathname, O_RDONLY); 210 if (f == -1) { 211 free(pathname); 212 pathname = NULL; 213 } else { 214 (void) close(f); 215 } 216 free(qpathname); 217 return (pathname); 218 } 219 220 /* 221 * Return the full pathname of the given file in the "home directory". 222 */ 223 char * 224 homefile(char *filename) 225 { 226 return (dirfile(lgetenv("HOME"), filename)); 227 } 228 229 /* 230 * Expand a string, substituting any "%" with the current filename, 231 * and any "#" with the previous filename. 232 * But a string of N "%"s is just replaced with N-1 "%"s. 233 * Likewise for a string of N "#"s. 234 * {{ This is a lot of work just to support % and #. }} 235 */ 236 char * 237 fexpand(char *s) 238 { 239 char *fr, *to; 240 int n; 241 char *e; 242 IFILE ifile; 243 244 #define fchar_ifile(c) \ 245 ((c) == '%' ? curr_ifile : (c) == '#' ? old_ifile : NULL) 246 247 /* 248 * Make one pass to see how big a buffer we 249 * need to allocate for the expanded string. 250 */ 251 n = 0; 252 for (fr = s; *fr != '\0'; fr++) { 253 switch (*fr) { 254 case '%': 255 case '#': 256 if (fr > s && fr[-1] == *fr) { 257 /* 258 * Second (or later) char in a string 259 * of identical chars. Treat as normal. 260 */ 261 n++; 262 } else if (fr[1] != *fr) { 263 /* 264 * Single char (not repeated). Treat specially. 265 */ 266 ifile = fchar_ifile(*fr); 267 if (ifile == NULL) 268 n++; 269 else 270 n += strlen(get_filename(ifile)); 271 } 272 /* 273 * Else it is the first char in a string of 274 * identical chars. Just discard it. 275 */ 276 break; 277 default: 278 n++; 279 break; 280 } 281 } 282 283 e = ecalloc(n+1, sizeof (char)); 284 285 /* 286 * Now copy the string, expanding any "%" or "#". 287 */ 288 to = e; 289 for (fr = s; *fr != '\0'; fr++) { 290 switch (*fr) { 291 case '%': 292 case '#': 293 if (fr > s && fr[-1] == *fr) { 294 *to++ = *fr; 295 } else if (fr[1] != *fr) { 296 ifile = fchar_ifile(*fr); 297 if (ifile == NULL) { 298 *to++ = *fr; 299 } else { 300 (void) strlcpy(to, get_filename(ifile), 301 e + n + 1 - to); 302 to += strlen(to); 303 } 304 } 305 break; 306 default: 307 *to++ = *fr; 308 break; 309 } 310 } 311 *to = '\0'; 312 return (e); 313 } 314 315 /* 316 * Return a blank-separated list of filenames which "complete" 317 * the given string. 318 */ 319 char * 320 fcomplete(char *s) 321 { 322 char *fpat; 323 char *qs; 324 325 if (secure) 326 return (NULL); 327 /* 328 * Complete the filename "s" by globbing "s*". 329 */ 330 fpat = easprintf("%s*", s); 331 332 qs = lglob(fpat); 333 s = shell_unquote(qs); 334 if (strcmp(s, fpat) == 0) { 335 /* 336 * The filename didn't expand. 337 */ 338 free(qs); 339 qs = NULL; 340 } 341 free(s); 342 free(fpat); 343 return (qs); 344 } 345 346 /* 347 * Try to determine if a file is "binary". 348 * This is just a guess, and we need not try too hard to make it accurate. 349 */ 350 int 351 bin_file(int f) 352 { 353 char data[256]; 354 ssize_t i, n; 355 wchar_t ch; 356 int bin_count, len; 357 358 if (!seekable(f)) 359 return (0); 360 if (lseek(f, (off_t)0, SEEK_SET) == (off_t)-1) 361 return (0); 362 n = read(f, data, sizeof (data)); 363 bin_count = 0; 364 for (i = 0; i < n; i += len) { 365 len = mbtowc(&ch, data + i, n - i); 366 if (len <= 0) { 367 bin_count++; 368 len = 1; 369 } else if (iswprint(ch) == 0 && iswspace(ch) == 0 && 370 data[i] != '\b' && 371 (ctldisp != OPT_ONPLUS || data[i] != ESC)) 372 bin_count++; 373 } 374 /* 375 * Call it a binary file if there are more than 5 binary characters 376 * in the first 256 bytes of the file. 377 */ 378 return (bin_count > 5); 379 } 380 381 /* 382 * Expand a filename, doing any system-specific metacharacter substitutions. 383 */ 384 char * 385 lglob(char *filename) 386 { 387 char *gfilename; 388 char *ofilename; 389 glob_t list; 390 int i; 391 int length; 392 char *p; 393 char *qfilename; 394 395 ofilename = fexpand(filename); 396 if (secure) 397 return (ofilename); 398 filename = shell_unquote(ofilename); 399 400 /* 401 * The globbing function returns a list of names. 402 */ 403 404 #ifndef GLOB_TILDE 405 #define GLOB_TILDE 0 406 #endif 407 #ifndef GLOB_LIMIT 408 #define GLOB_LIMIT 0 409 #endif 410 if (glob(filename, GLOB_TILDE | GLOB_LIMIT, NULL, &list) != 0) { 411 free(filename); 412 return (ofilename); 413 } 414 length = 1; /* Room for trailing null byte */ 415 for (i = 0; i < list.gl_pathc; i++) { 416 p = list.gl_pathv[i]; 417 qfilename = shell_quote(p); 418 if (qfilename != NULL) { 419 length += strlen(qfilename) + 1; 420 free(qfilename); 421 } 422 } 423 gfilename = ecalloc(length, sizeof (char)); 424 for (i = 0; i < list.gl_pathc; i++) { 425 p = list.gl_pathv[i]; 426 qfilename = shell_quote(p); 427 if (qfilename != NULL) { 428 if (i != 0) { 429 (void) strlcat(gfilename, " ", length); 430 } 431 (void) strlcat(gfilename, qfilename, length); 432 free(qfilename); 433 } 434 } 435 globfree(&list); 436 free(filename); 437 free(ofilename); 438 return (gfilename); 439 } 440 441 /* 442 * Is the specified file a directory? 443 */ 444 int 445 is_dir(char *filename) 446 { 447 int isdir = 0; 448 int r; 449 struct stat statbuf; 450 451 filename = shell_unquote(filename); 452 453 r = stat(filename, &statbuf); 454 isdir = (r >= 0 && S_ISDIR(statbuf.st_mode)); 455 free(filename); 456 return (isdir); 457 } 458 459 /* 460 * Returns NULL if the file can be opened and 461 * is an ordinary file, otherwise an error message 462 * (if it cannot be opened or is a directory, etc.) 463 */ 464 char * 465 bad_file(char *filename) 466 { 467 char *m = NULL; 468 469 filename = shell_unquote(filename); 470 if (!force_open && is_dir(filename)) { 471 m = easprintf("%s is a directory", filename); 472 } else { 473 int r; 474 struct stat statbuf; 475 476 r = stat(filename, &statbuf); 477 if (r == -1) { 478 m = errno_message(filename); 479 } else if (force_open) { 480 m = NULL; 481 } else if (!S_ISREG(statbuf.st_mode)) { 482 m = easprintf("%s is not a regular file (use -f to " 483 "see it)", filename); 484 } 485 } 486 free(filename); 487 return (m); 488 } 489 490 /* 491 * Return the size of a file, as cheaply as possible. 492 */ 493 off_t 494 filesize(int f) 495 { 496 struct stat statbuf; 497 498 if (fstat(f, &statbuf) >= 0) 499 return (statbuf.st_size); 500 return (-1); 501 } 502 503 /* 504 * Return last component of a pathname. 505 */ 506 char * 507 last_component(char *name) 508 { 509 char *slash; 510 511 for (slash = name + strlen(name); slash > name; ) { 512 --slash; 513 if (*slash == '/') 514 return (slash + 1); 515 } 516 return (name); 517 } 518