Annotation of src/usr.bin/less/filename.c, Revision 1.31
1.1 etheisen 1: /*
1.16 shadchin 2: * Copyright (C) 1984-2012 Mark Nudelman
1.18 nicm 3: * Modified for use with illumos by Garrett D'Amore.
4: * Copyright 2014 Garrett D'Amore <garrett@damore.org>
1.1 etheisen 5: *
1.8 millert 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.
1.1 etheisen 8: *
1.16 shadchin 9: * For more information, see the README file.
1.17 nicm 10: */
1.1 etheisen 11:
12: /*
13: * Routines to mess around with filenames (and files).
14: * Much of this is very OS dependent.
1.17 nicm 15: *
16: * Modified for illumos/POSIX -- it uses native glob(3C) rather than
17: * popen to a shell to perform the expansion.
1.1 etheisen 18: */
19:
1.24 mmcc 20: #include <sys/stat.h>
1.8 millert 21:
1.17 nicm 22: #include <glob.h>
23: #include <stdarg.h>
1.24 mmcc 24:
25: #include "less.h"
1.8 millert 26:
1.1 etheisen 27: extern int force_open;
1.8 millert 28: extern int secure;
1.15 shadchin 29: extern int ctldisp;
30: extern int utf_mode;
1.1 etheisen 31: extern IFILE curr_ifile;
32: extern IFILE old_ifile;
1.8 millert 33: extern char openquote;
34: extern char closequote;
35:
36: /*
37: * Remove quotes around a filename.
38: */
1.17 nicm 39: char *
40: shell_unquote(char *str)
1.8 millert 41: {
42: char *name;
43: char *p;
44:
1.17 nicm 45: name = p = ecalloc(strlen(str)+1, sizeof (char));
46: if (*str == openquote) {
1.8 millert 47: str++;
1.17 nicm 48: while (*str != '\0') {
49: if (*str == closequote) {
1.8 millert 50: if (str[1] != closequote)
51: break;
52: str++;
53: }
54: *p++ = *str++;
55: }
1.17 nicm 56: } else {
1.8 millert 57: char *esc = get_meta_escape();
58: int esclen = strlen(esc);
1.17 nicm 59: while (*str != '\0') {
1.8 millert 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: */
1.17 nicm 72: char *
73: get_meta_escape(void)
1.8 millert 74: {
75: char *s;
76:
77: s = lgetenv("LESSMETAESCAPE");
78: if (s == NULL)
1.23 mmcc 79: s = "\\";
1.8 millert 80: return (s);
81: }
82:
83: /*
84: * Get the characters which the shell considers to be "metacharacters".
85: */
1.17 nicm 86: static char *
87: metachars(void)
1.8 millert 88: {
89: static char *mchars = NULL;
90:
1.17 nicm 91: if (mchars == NULL) {
1.8 millert 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: */
1.17 nicm 102: static int
103: metachar(char c)
1.8 millert 104: {
105: return (strchr(metachars(), c) != NULL);
106: }
107:
108: /*
1.30 tb 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: /*
1.8 millert 118: * Insert a backslash before each metacharacter in a string.
119: */
1.17 nicm 120: char *
121: shell_quote(const char *s)
1.8 millert 122: {
1.17 nicm 123: const char *p;
124: char *r;
1.8 millert 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 */
1.25 deraadt 136: for (p = s; *p != '\0'; p++) {
1.8 millert 137: len++;
138: if (*p == openquote || *p == closequote)
139: have_quotes = 1;
1.17 nicm 140: if (metachar(*p)) {
141: if (esclen == 0) {
1.8 millert 142: /*
1.17 nicm 143: * We've got a metachar, but this shell
1.8 millert 144: * doesn't support escape chars. Use quotes.
145: */
146: use_quotes = 1;
1.30 tb 147: } else if (must_quote(*p)) {
148: /* Opening quote + character + closing quote. */
149: len += 3;
1.17 nicm 150: } else {
1.8 millert 151: /*
152: * Allow space for the escape char.
153: */
154: len += esclen;
155: }
156: }
157: }
158: /*
159: * Allocate and construct the new string.
160: */
1.17 nicm 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') {
1.30 tb 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. */
1.17 nicm 178: (void) strlcpy(r, esc, newstr + len - p);
179: r += esclen;
1.30 tb 180: *r++ = *s++;
1.8 millert 181: }
182: }
1.17 nicm 183: *r = '\0';
1.8 millert 184: }
185: return (newstr);
186: }
1.1 etheisen 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: */
1.17 nicm 192: static char *
193: dirfile(const char *dirname, const char *filename)
1.1 etheisen 194: {
195: char *pathname;
1.8 millert 196: char *qpathname;
197: int f;
1.1 etheisen 198:
199: if (dirname == NULL || *dirname == '\0')
200: return (NULL);
201: /*
202: * Construct the full pathname.
203: */
1.17 nicm 204: pathname = easprintf("%s/%s", dirname, filename);
1.1 etheisen 205: /*
206: * Make sure the file exists.
207: */
1.8 millert 208: qpathname = shell_unquote(pathname);
1.17 nicm 209: f = open(qpathname, O_RDONLY);
1.29 deraadt 210: if (f == -1) {
1.1 etheisen 211: free(pathname);
212: pathname = NULL;
1.17 nicm 213: } else {
214: (void) close(f);
1.1 etheisen 215: }
1.8 millert 216: free(qpathname);
1.1 etheisen 217: return (pathname);
218: }
219:
220: /*
221: * Return the full pathname of the given file in the "home directory".
222: */
1.17 nicm 223: char *
224: homefile(char *filename)
1.1 etheisen 225: {
1.17 nicm 226: return (dirfile(lgetenv("HOME"), filename));
1.1 etheisen 227: }
1.9 millert 228:
1.1 etheisen 229: /*
230: * Expand a string, substituting any "%" with the current filename,
231: * and any "#" with the previous filename.
1.8 millert 232: * But a string of N "%"s is just replaced with N-1 "%"s.
233: * Likewise for a string of N "#"s.
1.1 etheisen 234: * {{ This is a lot of work just to support % and #. }}
235: */
1.17 nicm 236: char *
237: fexpand(char *s)
1.1 etheisen 238: {
1.17 nicm 239: char *fr, *to;
240: int n;
241: char *e;
1.8 millert 242: IFILE ifile;
243:
244: #define fchar_ifile(c) \
1.22 deraadt 245: ((c) == '%' ? curr_ifile : (c) == '#' ? old_ifile : NULL)
1.1 etheisen 246:
247: /*
1.17 nicm 248: * Make one pass to see how big a buffer we
1.1 etheisen 249: * need to allocate for the expanded string.
250: */
251: n = 0;
1.25 deraadt 252: for (fr = s; *fr != '\0'; fr++) {
1.17 nicm 253: switch (*fr) {
1.1 etheisen 254: case '%':
1.8 millert 255: case '#':
1.17 nicm 256: if (fr > s && fr[-1] == *fr) {
1.8 millert 257: /*
258: * Second (or later) char in a string
259: * of identical chars. Treat as normal.
260: */
261: n++;
1.17 nicm 262: } else if (fr[1] != *fr) {
1.8 millert 263: /*
264: * Single char (not repeated). Treat specially.
265: */
266: ifile = fchar_ifile(*fr);
1.22 deraadt 267: if (ifile == NULL)
1.8 millert 268: n++;
269: else
270: n += strlen(get_filename(ifile));
1.1 etheisen 271: }
1.8 millert 272: /*
273: * Else it is the first char in a string of
274: * identical chars. Just discard it.
275: */
1.1 etheisen 276: break;
277: default:
278: n++;
279: break;
280: }
281: }
282:
1.17 nicm 283: e = ecalloc(n+1, sizeof (char));
1.1 etheisen 284:
285: /*
286: * Now copy the string, expanding any "%" or "#".
287: */
288: to = e;
1.25 deraadt 289: for (fr = s; *fr != '\0'; fr++) {
1.17 nicm 290: switch (*fr) {
1.1 etheisen 291: case '%':
292: case '#':
1.17 nicm 293: if (fr > s && fr[-1] == *fr) {
1.8 millert 294: *to++ = *fr;
1.17 nicm 295: } else if (fr[1] != *fr) {
1.8 millert 296: ifile = fchar_ifile(*fr);
1.22 deraadt 297: if (ifile == NULL) {
1.8 millert 298: *to++ = *fr;
1.17 nicm 299: } else {
300: (void) strlcpy(to, get_filename(ifile),
1.8 millert 301: e + n + 1 - to);
302: to += strlen(to);
303: }
304: }
1.1 etheisen 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: */
1.17 nicm 319: char *
320: fcomplete(char *s)
1.1 etheisen 321: {
322: char *fpat;
1.8 millert 323: char *qs;
1.7 deraadt 324:
1.8 millert 325: if (secure)
326: return (NULL);
1.1 etheisen 327: /*
328: * Complete the filename "s" by globbing "s*".
329: */
1.17 nicm 330: fpat = easprintf("%s*", s);
331:
1.8 millert 332: qs = lglob(fpat);
333: s = shell_unquote(qs);
1.17 nicm 334: if (strcmp(s, fpat) == 0) {
1.1 etheisen 335: /*
336: * The filename didn't expand.
337: */
1.8 millert 338: free(qs);
339: qs = NULL;
1.1 etheisen 340: }
1.8 millert 341: free(s);
1.1 etheisen 342: free(fpat);
1.8 millert 343: return (qs);
1.1 etheisen 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: */
1.17 nicm 350: int
351: bin_file(int f)
1.1 etheisen 352: {
1.15 shadchin 353: char data[256];
1.28 schwarze 354: ssize_t i, n;
355: wchar_t ch;
356: int bin_count, len;
1.1 etheisen 357:
358: if (!seekable(f))
359: return (0);
1.21 deraadt 360: if (lseek(f, (off_t)0, SEEK_SET) == (off_t)-1)
1.1 etheisen 361: return (0);
1.17 nicm 362: n = read(f, data, sizeof (data));
1.28 schwarze 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))
1.15 shadchin 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);
1.1 etheisen 379: }
380:
381: /*
382: * Read a string from a file.
383: * Return a pointer to the string in memory.
384: */
1.17 nicm 385: static char *
386: readfd(FILE *fd)
1.1 etheisen 387: {
388: int len;
389: int ch;
390: char *buf;
391: char *p;
1.17 nicm 392:
393: /*
1.1 etheisen 394: * Make a guess about how many chars in the string
395: * and allocate a buffer to hold it.
396: */
397: len = 100;
1.17 nicm 398: buf = ecalloc(len, sizeof (char));
399: for (p = buf; ; p++) {
1.1 etheisen 400: if ((ch = getc(fd)) == '\n' || ch == EOF)
401: break;
1.17 nicm 402: if (p >= buf + len-1) {
1.1 etheisen 403: /*
404: * The string is too big to fit in the buffer we have.
405: * Allocate a new buffer, twice as big.
406: */
407: len *= 2;
408: *p = '\0';
1.17 nicm 409: p = ecalloc(len, sizeof (char));
1.5 deraadt 410: strlcpy(p, buf, len);
1.1 etheisen 411: free(buf);
412: buf = p;
413: p = buf + strlen(buf);
414: }
1.17 nicm 415: *p = (char)ch;
1.1 etheisen 416: }
417: *p = '\0';
418: return (buf);
419: }
420:
421: /*
422: * Execute a shell command.
423: * Return a pointer to a pipe connected to the shell command's standard output.
424: */
1.17 nicm 425: static FILE *
426: shellcmd(char *cmd)
1.1 etheisen 427: {
428: FILE *fd;
1.8 millert 429:
430: char *shell;
431:
432: shell = lgetenv("SHELL");
1.17 nicm 433: if (shell != NULL && *shell != '\0') {
1.8 millert 434: char *scmd;
435: char *esccmd;
436:
1.1 etheisen 437: /*
1.17 nicm 438: * Read the output of <$SHELL -c cmd>.
1.8 millert 439: * Escape any metacharacters in the command.
1.1 etheisen 440: */
1.8 millert 441: esccmd = shell_quote(cmd);
1.17 nicm 442: if (esccmd == NULL) {
1.8 millert 443: fd = popen(cmd, "r");
1.17 nicm 444: } else {
445: scmd = easprintf("%s -c %s", shell, esccmd);
1.8 millert 446: free(esccmd);
447: fd = popen(scmd, "r");
448: free(scmd);
449: }
1.17 nicm 450: } else {
1.8 millert 451: fd = popen(cmd, "r");
1.1 etheisen 452: }
1.8 millert 453: /*
454: * Redirection in `popen' might have messed with the
455: * standard devices. Restore binary input mode.
456: */
1.1 etheisen 457: return (fd);
458: }
459:
460: /*
1.8 millert 461: * Expand a filename, doing any system-specific metacharacter substitutions.
1.1 etheisen 462: */
1.17 nicm 463: char *
464: lglob(char *filename)
1.1 etheisen 465: {
466: char *gfilename;
1.8 millert 467: char *ofilename;
1.17 nicm 468: glob_t list;
469: int i;
470: int length;
471: char *p;
472: char *qfilename;
1.1 etheisen 473:
1.8 millert 474: ofilename = fexpand(filename);
475: if (secure)
476: return (ofilename);
477: filename = shell_unquote(ofilename);
478:
479: /*
480: * The globbing function returns a list of names.
481: */
1.1 etheisen 482:
1.17 nicm 483: #ifndef GLOB_TILDE
484: #define GLOB_TILDE 0
485: #endif
486: #ifndef GLOB_LIMIT
487: #define GLOB_LIMIT 0
488: #endif
489: if (glob(filename, GLOB_TILDE | GLOB_LIMIT, NULL, &list) != 0) {
1.8 millert 490: free(filename);
491: return (ofilename);
492: }
493: length = 1; /* Room for trailing null byte */
1.17 nicm 494: for (i = 0; i < list.gl_pathc; i++) {
495: p = list.gl_pathv[i];
1.8 millert 496: qfilename = shell_quote(p);
1.17 nicm 497: if (qfilename != NULL) {
498: length += strlen(qfilename) + 1;
1.8 millert 499: free(qfilename);
500: }
501: }
1.17 nicm 502: gfilename = ecalloc(length, sizeof (char));
503: for (i = 0; i < list.gl_pathc; i++) {
504: p = list.gl_pathv[i];
1.8 millert 505: qfilename = shell_quote(p);
1.17 nicm 506: if (qfilename != NULL) {
507: if (i != 0) {
508: (void) strlcat(gfilename, " ", length);
509: }
510: (void) strlcat(gfilename, qfilename, length);
1.8 millert 511: free(qfilename);
512: }
1.1 etheisen 513: }
1.17 nicm 514: globfree(&list);
1.8 millert 515: free(filename);
516: free(ofilename);
1.1 etheisen 517: return (gfilename);
518: }
1.17 nicm 519:
1.8 millert 520: /*
521: * Is the specified file a directory?
522: */
1.17 nicm 523: int
524: is_dir(char *filename)
1.1 etheisen 525: {
1.8 millert 526: int isdir = 0;
1.17 nicm 527: int r;
528: struct stat statbuf;
1.8 millert 529:
530: filename = shell_unquote(filename);
1.1 etheisen 531:
1.8 millert 532: r = stat(filename, &statbuf);
533: isdir = (r >= 0 && S_ISDIR(statbuf.st_mode));
534: free(filename);
535: return (isdir);
536: }
1.1 etheisen 537:
538: /*
539: * Returns NULL if the file can be opened and
540: * is an ordinary file, otherwise an error message
541: * (if it cannot be opened or is a directory, etc.)
542: */
1.17 nicm 543: char *
544: bad_file(char *filename)
1.1 etheisen 545: {
1.17 nicm 546: char *m = NULL;
1.1 etheisen 547:
1.8 millert 548: filename = shell_unquote(filename);
1.17 nicm 549: if (!force_open && is_dir(filename)) {
550: m = easprintf("%s is a directory", filename);
551: } else {
1.8 millert 552: int r;
553: struct stat statbuf;
1.5 deraadt 554:
1.8 millert 555: r = stat(filename, &statbuf);
1.29 deraadt 556: if (r == -1) {
1.8 millert 557: m = errno_message(filename);
1.17 nicm 558: } else if (force_open) {
1.8 millert 559: m = NULL;
1.17 nicm 560: } else if (!S_ISREG(statbuf.st_mode)) {
561: m = easprintf("%s is not a regular file (use -f to "
562: "see it)", filename);
1.8 millert 563: }
1.1 etheisen 564: }
1.8 millert 565: free(filename);
566: return (m);
1.1 etheisen 567: }
568:
569: /*
570: * Return the size of a file, as cheaply as possible.
571: */
1.17 nicm 572: off_t
573: filesize(int f)
1.1 etheisen 574: {
575: struct stat statbuf;
576:
1.8 millert 577: if (fstat(f, &statbuf) >= 0)
1.17 nicm 578: return (statbuf.st_size);
1.26 schwarze 579: return (-1);
1.1 etheisen 580: }
581:
582: /*
1.15 shadchin 583: * Return last component of a pathname.
584: */
1.17 nicm 585: char *
586: last_component(char *name)
1.15 shadchin 587: {
588: char *slash;
589:
1.25 deraadt 590: for (slash = name + strlen(name); slash > name; ) {
1.15 shadchin 591: --slash;
1.17 nicm 592: if (*slash == '/')
1.15 shadchin 593: return (slash + 1);
594: }
595: return (name);
596: }