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 use_lessopen; |
30 |
|
|
extern int ctldisp; |
31 |
|
|
extern int utf_mode; |
32 |
|
|
extern IFILE curr_ifile; |
33 |
|
|
extern IFILE old_ifile; |
34 |
|
|
extern char openquote; |
35 |
|
|
extern char closequote; |
36 |
|
|
|
37 |
|
|
/* |
38 |
|
|
* Remove quotes around a filename. |
39 |
|
|
*/ |
40 |
|
|
char * |
41 |
|
|
shell_unquote(char *str) |
42 |
|
|
{ |
43 |
|
|
char *name; |
44 |
|
|
char *p; |
45 |
|
|
|
46 |
|
80 |
name = p = ecalloc(strlen(str)+1, sizeof (char)); |
47 |
✗✓ |
40 |
if (*str == openquote) { |
48 |
|
|
str++; |
49 |
|
|
while (*str != '\0') { |
50 |
|
|
if (*str == closequote) { |
51 |
|
|
if (str[1] != closequote) |
52 |
|
|
break; |
53 |
|
|
str++; |
54 |
|
|
} |
55 |
|
|
*p++ = *str++; |
56 |
|
|
} |
57 |
|
|
} else { |
58 |
|
40 |
char *esc = get_meta_escape(); |
59 |
|
40 |
int esclen = strlen(esc); |
60 |
✓✓ |
1150 |
while (*str != '\0') { |
61 |
✓✗✗✓
|
1070 |
if (esclen > 0 && strncmp(str, esc, esclen) == 0) |
62 |
|
|
str += esclen; |
63 |
|
535 |
*p++ = *str++; |
64 |
|
|
} |
65 |
|
|
} |
66 |
|
40 |
*p = '\0'; |
67 |
|
40 |
return (name); |
68 |
|
|
} |
69 |
|
|
|
70 |
|
|
/* |
71 |
|
|
* Get the shell's escape character. |
72 |
|
|
*/ |
73 |
|
|
char * |
74 |
|
|
get_meta_escape(void) |
75 |
|
|
{ |
76 |
|
|
char *s; |
77 |
|
|
|
78 |
|
96 |
s = lgetenv("LESSMETAESCAPE"); |
79 |
|
48 |
if (s == NULL) |
80 |
|
|
s = "\\"; |
81 |
|
48 |
return (s); |
82 |
|
|
} |
83 |
|
|
|
84 |
|
|
/* |
85 |
|
|
* Get the characters which the shell considers to be "metacharacters". |
86 |
|
|
*/ |
87 |
|
|
static char * |
88 |
|
|
metachars(void) |
89 |
|
|
{ |
90 |
|
|
static char *mchars = NULL; |
91 |
|
|
|
92 |
✓✓ |
468 |
if (mchars == NULL) { |
93 |
|
8 |
mchars = lgetenv("LESSMETACHARS"); |
94 |
|
8 |
if (mchars == NULL) |
95 |
|
|
mchars = DEF_METACHARS; |
96 |
|
8 |
} |
97 |
|
234 |
return (mchars); |
98 |
|
|
} |
99 |
|
|
|
100 |
|
|
/* |
101 |
|
|
* Is this a shell metacharacter? |
102 |
|
|
*/ |
103 |
|
|
static int |
104 |
|
|
metachar(char c) |
105 |
|
|
{ |
106 |
|
468 |
return (strchr(metachars(), c) != NULL); |
107 |
|
|
} |
108 |
|
|
|
109 |
|
|
/* |
110 |
|
|
* Insert a backslash before each metacharacter in a string. |
111 |
|
|
*/ |
112 |
|
|
char * |
113 |
|
|
shell_quote(const char *s) |
114 |
|
|
{ |
115 |
|
|
const char *p; |
116 |
|
|
char *r; |
117 |
|
|
char *newstr; |
118 |
|
|
int len; |
119 |
|
16 |
char *esc = get_meta_escape(); |
120 |
|
8 |
int esclen = strlen(esc); |
121 |
|
|
int use_quotes = 0; |
122 |
|
|
int have_quotes = 0; |
123 |
|
|
|
124 |
|
|
/* |
125 |
|
|
* Determine how big a string we need to allocate. |
126 |
|
|
*/ |
127 |
|
|
len = 1; /* Trailing null byte */ |
128 |
✓✓ |
250 |
for (p = s; *p != '\0'; p++) { |
129 |
|
117 |
len++; |
130 |
✓✗✗✓
|
234 |
if (*p == openquote || *p == closequote) |
131 |
|
|
have_quotes = 1; |
132 |
✗✓ |
117 |
if (metachar(*p)) { |
133 |
|
|
if (esclen == 0) { |
134 |
|
|
/* |
135 |
|
|
* We've got a metachar, but this shell |
136 |
|
|
* doesn't support escape chars. Use quotes. |
137 |
|
|
*/ |
138 |
|
|
use_quotes = 1; |
139 |
|
|
} else { |
140 |
|
|
/* |
141 |
|
|
* Allow space for the escape char. |
142 |
|
|
*/ |
143 |
|
|
len += esclen; |
144 |
|
|
} |
145 |
|
|
} |
146 |
|
|
} |
147 |
|
|
/* |
148 |
|
|
* Allocate and construct the new string. |
149 |
|
|
*/ |
150 |
✗✓ |
8 |
if (use_quotes) { |
151 |
|
|
/* We can't quote a string that contains quotes. */ |
152 |
|
|
if (have_quotes) |
153 |
|
|
return (NULL); |
154 |
|
|
newstr = easprintf("%c%s%c", openquote, s, closequote); |
155 |
|
|
} else { |
156 |
|
8 |
newstr = r = ecalloc(len, sizeof (char)); |
157 |
✓✓ |
250 |
while (*s != '\0') { |
158 |
✗✓ |
117 |
if (metachar(*s)) { |
159 |
|
|
/* |
160 |
|
|
* Add the escape char. |
161 |
|
|
*/ |
162 |
|
|
(void) strlcpy(r, esc, newstr + len - p); |
163 |
|
|
r += esclen; |
164 |
|
|
} |
165 |
|
117 |
*r++ = *s++; |
166 |
|
|
} |
167 |
|
8 |
*r = '\0'; |
168 |
|
|
} |
169 |
|
8 |
return (newstr); |
170 |
|
8 |
} |
171 |
|
|
|
172 |
|
|
/* |
173 |
|
|
* Return a pathname that points to a specified file in a specified directory. |
174 |
|
|
* Return NULL if the file does not exist in the directory. |
175 |
|
|
*/ |
176 |
|
|
static char * |
177 |
|
|
dirfile(const char *dirname, const char *filename) |
178 |
|
|
{ |
179 |
|
|
char *pathname; |
180 |
|
|
char *qpathname; |
181 |
|
|
int f; |
182 |
|
|
|
183 |
✓✗✗✓
|
24 |
if (dirname == NULL || *dirname == '\0') |
184 |
|
|
return (NULL); |
185 |
|
|
/* |
186 |
|
|
* Construct the full pathname. |
187 |
|
|
*/ |
188 |
|
8 |
pathname = easprintf("%s/%s", dirname, filename); |
189 |
|
|
/* |
190 |
|
|
* Make sure the file exists. |
191 |
|
|
*/ |
192 |
|
8 |
qpathname = shell_unquote(pathname); |
193 |
|
8 |
f = open(qpathname, O_RDONLY); |
194 |
✓✗ |
8 |
if (f < 0) { |
195 |
|
8 |
free(pathname); |
196 |
|
|
pathname = NULL; |
197 |
|
8 |
} else { |
198 |
|
|
(void) close(f); |
199 |
|
|
} |
200 |
|
8 |
free(qpathname); |
201 |
|
8 |
return (pathname); |
202 |
|
8 |
} |
203 |
|
|
|
204 |
|
|
/* |
205 |
|
|
* Return the full pathname of the given file in the "home directory". |
206 |
|
|
*/ |
207 |
|
|
char * |
208 |
|
|
homefile(char *filename) |
209 |
|
|
{ |
210 |
|
16 |
return (dirfile(lgetenv("HOME"), filename)); |
211 |
|
|
} |
212 |
|
|
|
213 |
|
|
/* |
214 |
|
|
* Expand a string, substituting any "%" with the current filename, |
215 |
|
|
* and any "#" with the previous filename. |
216 |
|
|
* But a string of N "%"s is just replaced with N-1 "%"s. |
217 |
|
|
* Likewise for a string of N "#"s. |
218 |
|
|
* {{ This is a lot of work just to support % and #. }} |
219 |
|
|
*/ |
220 |
|
|
char * |
221 |
|
|
fexpand(char *s) |
222 |
|
|
{ |
223 |
|
|
char *fr, *to; |
224 |
|
|
int n; |
225 |
|
|
char *e; |
226 |
|
|
IFILE ifile; |
227 |
|
|
|
228 |
|
|
#define fchar_ifile(c) \ |
229 |
|
|
((c) == '%' ? curr_ifile : (c) == '#' ? old_ifile : NULL) |
230 |
|
|
|
231 |
|
|
/* |
232 |
|
|
* Make one pass to see how big a buffer we |
233 |
|
|
* need to allocate for the expanded string. |
234 |
|
|
*/ |
235 |
|
|
n = 0; |
236 |
|
|
for (fr = s; *fr != '\0'; fr++) { |
237 |
|
|
switch (*fr) { |
238 |
|
|
case '%': |
239 |
|
|
case '#': |
240 |
|
|
if (fr > s && fr[-1] == *fr) { |
241 |
|
|
/* |
242 |
|
|
* Second (or later) char in a string |
243 |
|
|
* of identical chars. Treat as normal. |
244 |
|
|
*/ |
245 |
|
|
n++; |
246 |
|
|
} else if (fr[1] != *fr) { |
247 |
|
|
/* |
248 |
|
|
* Single char (not repeated). Treat specially. |
249 |
|
|
*/ |
250 |
|
|
ifile = fchar_ifile(*fr); |
251 |
|
|
if (ifile == NULL) |
252 |
|
|
n++; |
253 |
|
|
else |
254 |
|
|
n += strlen(get_filename(ifile)); |
255 |
|
|
} |
256 |
|
|
/* |
257 |
|
|
* Else it is the first char in a string of |
258 |
|
|
* identical chars. Just discard it. |
259 |
|
|
*/ |
260 |
|
|
break; |
261 |
|
|
default: |
262 |
|
|
n++; |
263 |
|
|
break; |
264 |
|
|
} |
265 |
|
|
} |
266 |
|
|
|
267 |
|
|
e = ecalloc(n+1, sizeof (char)); |
268 |
|
|
|
269 |
|
|
/* |
270 |
|
|
* Now copy the string, expanding any "%" or "#". |
271 |
|
|
*/ |
272 |
|
|
to = e; |
273 |
|
|
for (fr = s; *fr != '\0'; fr++) { |
274 |
|
|
switch (*fr) { |
275 |
|
|
case '%': |
276 |
|
|
case '#': |
277 |
|
|
if (fr > s && fr[-1] == *fr) { |
278 |
|
|
*to++ = *fr; |
279 |
|
|
} else if (fr[1] != *fr) { |
280 |
|
|
ifile = fchar_ifile(*fr); |
281 |
|
|
if (ifile == NULL) { |
282 |
|
|
*to++ = *fr; |
283 |
|
|
} else { |
284 |
|
|
(void) strlcpy(to, get_filename(ifile), |
285 |
|
|
e + n + 1 - to); |
286 |
|
|
to += strlen(to); |
287 |
|
|
} |
288 |
|
|
} |
289 |
|
|
break; |
290 |
|
|
default: |
291 |
|
|
*to++ = *fr; |
292 |
|
|
break; |
293 |
|
|
} |
294 |
|
|
} |
295 |
|
|
*to = '\0'; |
296 |
|
|
return (e); |
297 |
|
|
} |
298 |
|
|
|
299 |
|
|
/* |
300 |
|
|
* Return a blank-separated list of filenames which "complete" |
301 |
|
|
* the given string. |
302 |
|
|
*/ |
303 |
|
|
char * |
304 |
|
|
fcomplete(char *s) |
305 |
|
|
{ |
306 |
|
|
char *fpat; |
307 |
|
|
char *qs; |
308 |
|
|
|
309 |
|
|
if (secure) |
310 |
|
|
return (NULL); |
311 |
|
|
/* |
312 |
|
|
* Complete the filename "s" by globbing "s*". |
313 |
|
|
*/ |
314 |
|
|
fpat = easprintf("%s*", s); |
315 |
|
|
|
316 |
|
|
qs = lglob(fpat); |
317 |
|
|
s = shell_unquote(qs); |
318 |
|
|
if (strcmp(s, fpat) == 0) { |
319 |
|
|
/* |
320 |
|
|
* The filename didn't expand. |
321 |
|
|
*/ |
322 |
|
|
free(qs); |
323 |
|
|
qs = NULL; |
324 |
|
|
} |
325 |
|
|
free(s); |
326 |
|
|
free(fpat); |
327 |
|
|
return (qs); |
328 |
|
|
} |
329 |
|
|
|
330 |
|
|
/* |
331 |
|
|
* Try to determine if a file is "binary". |
332 |
|
|
* This is just a guess, and we need not try too hard to make it accurate. |
333 |
|
|
*/ |
334 |
|
|
int |
335 |
|
|
bin_file(int f) |
336 |
|
|
{ |
337 |
|
|
int n; |
338 |
|
|
int bin_count = 0; |
339 |
|
16 |
char data[256]; |
340 |
|
8 |
char *p; |
341 |
|
|
char *pend; |
342 |
|
|
|
343 |
✗✓ |
8 |
if (!seekable(f)) |
344 |
|
|
return (0); |
345 |
✗✓ |
8 |
if (lseek(f, (off_t)0, SEEK_SET) == (off_t)-1) |
346 |
|
|
return (0); |
347 |
|
8 |
n = read(f, data, sizeof (data)); |
348 |
|
8 |
pend = &data[n]; |
349 |
✓✓ |
4112 |
for (p = data; p < pend; ) { |
350 |
|
2048 |
LWCHAR c = step_char(&p, +1, pend); |
351 |
✗✓✗✗
|
2048 |
if (ctldisp == OPT_ONPLUS && IS_CSI_START(c)) { |
352 |
|
|
do { |
353 |
|
|
c = step_char(&p, +1, pend); |
354 |
|
|
} while (p < pend && is_ansi_middle(c)); |
355 |
✗✓ |
2048 |
} else if (binary_char(c)) |
356 |
|
|
bin_count++; |
357 |
|
|
} |
358 |
|
|
/* |
359 |
|
|
* Call it a binary file if there are more than 5 binary characters |
360 |
|
|
* in the first 256 bytes of the file. |
361 |
|
|
*/ |
362 |
|
8 |
return (bin_count > 5); |
363 |
|
8 |
} |
364 |
|
|
|
365 |
|
|
/* |
366 |
|
|
* Read a string from a file. |
367 |
|
|
* Return a pointer to the string in memory. |
368 |
|
|
*/ |
369 |
|
|
static char * |
370 |
|
|
readfd(FILE *fd) |
371 |
|
|
{ |
372 |
|
|
int len; |
373 |
|
|
int ch; |
374 |
|
|
char *buf; |
375 |
|
|
char *p; |
376 |
|
|
|
377 |
|
|
/* |
378 |
|
|
* Make a guess about how many chars in the string |
379 |
|
|
* and allocate a buffer to hold it. |
380 |
|
|
*/ |
381 |
|
|
len = 100; |
382 |
|
|
buf = ecalloc(len, sizeof (char)); |
383 |
|
|
for (p = buf; ; p++) { |
384 |
|
|
if ((ch = getc(fd)) == '\n' || ch == EOF) |
385 |
|
|
break; |
386 |
|
|
if (p >= buf + len-1) { |
387 |
|
|
/* |
388 |
|
|
* The string is too big to fit in the buffer we have. |
389 |
|
|
* Allocate a new buffer, twice as big. |
390 |
|
|
*/ |
391 |
|
|
len *= 2; |
392 |
|
|
*p = '\0'; |
393 |
|
|
p = ecalloc(len, sizeof (char)); |
394 |
|
|
strlcpy(p, buf, len); |
395 |
|
|
free(buf); |
396 |
|
|
buf = p; |
397 |
|
|
p = buf + strlen(buf); |
398 |
|
|
} |
399 |
|
|
*p = (char)ch; |
400 |
|
|
} |
401 |
|
|
*p = '\0'; |
402 |
|
|
return (buf); |
403 |
|
|
} |
404 |
|
|
|
405 |
|
|
/* |
406 |
|
|
* Execute a shell command. |
407 |
|
|
* Return a pointer to a pipe connected to the shell command's standard output. |
408 |
|
|
*/ |
409 |
|
|
static FILE * |
410 |
|
|
shellcmd(char *cmd) |
411 |
|
|
{ |
412 |
|
|
FILE *fd; |
413 |
|
|
|
414 |
|
|
char *shell; |
415 |
|
|
|
416 |
|
|
shell = lgetenv("SHELL"); |
417 |
|
|
if (shell != NULL && *shell != '\0') { |
418 |
|
|
char *scmd; |
419 |
|
|
char *esccmd; |
420 |
|
|
|
421 |
|
|
/* |
422 |
|
|
* Read the output of <$SHELL -c cmd>. |
423 |
|
|
* Escape any metacharacters in the command. |
424 |
|
|
*/ |
425 |
|
|
esccmd = shell_quote(cmd); |
426 |
|
|
if (esccmd == NULL) { |
427 |
|
|
fd = popen(cmd, "r"); |
428 |
|
|
} else { |
429 |
|
|
scmd = easprintf("%s -c %s", shell, esccmd); |
430 |
|
|
free(esccmd); |
431 |
|
|
fd = popen(scmd, "r"); |
432 |
|
|
free(scmd); |
433 |
|
|
} |
434 |
|
|
} else { |
435 |
|
|
fd = popen(cmd, "r"); |
436 |
|
|
} |
437 |
|
|
/* |
438 |
|
|
* Redirection in `popen' might have messed with the |
439 |
|
|
* standard devices. Restore binary input mode. |
440 |
|
|
*/ |
441 |
|
|
return (fd); |
442 |
|
|
} |
443 |
|
|
|
444 |
|
|
/* |
445 |
|
|
* Expand a filename, doing any system-specific metacharacter substitutions. |
446 |
|
|
*/ |
447 |
|
|
char * |
448 |
|
|
lglob(char *filename) |
449 |
|
|
{ |
450 |
|
|
char *gfilename; |
451 |
|
|
char *ofilename; |
452 |
|
|
glob_t list; |
453 |
|
|
int i; |
454 |
|
|
int length; |
455 |
|
|
char *p; |
456 |
|
|
char *qfilename; |
457 |
|
|
|
458 |
|
|
ofilename = fexpand(filename); |
459 |
|
|
if (secure) |
460 |
|
|
return (ofilename); |
461 |
|
|
filename = shell_unquote(ofilename); |
462 |
|
|
|
463 |
|
|
/* |
464 |
|
|
* The globbing function returns a list of names. |
465 |
|
|
*/ |
466 |
|
|
|
467 |
|
|
#ifndef GLOB_TILDE |
468 |
|
|
#define GLOB_TILDE 0 |
469 |
|
|
#endif |
470 |
|
|
#ifndef GLOB_LIMIT |
471 |
|
|
#define GLOB_LIMIT 0 |
472 |
|
|
#endif |
473 |
|
|
if (glob(filename, GLOB_TILDE | GLOB_LIMIT, NULL, &list) != 0) { |
474 |
|
|
free(filename); |
475 |
|
|
return (ofilename); |
476 |
|
|
} |
477 |
|
|
length = 1; /* Room for trailing null byte */ |
478 |
|
|
for (i = 0; i < list.gl_pathc; i++) { |
479 |
|
|
p = list.gl_pathv[i]; |
480 |
|
|
qfilename = shell_quote(p); |
481 |
|
|
if (qfilename != NULL) { |
482 |
|
|
length += strlen(qfilename) + 1; |
483 |
|
|
free(qfilename); |
484 |
|
|
} |
485 |
|
|
} |
486 |
|
|
gfilename = ecalloc(length, sizeof (char)); |
487 |
|
|
for (i = 0; i < list.gl_pathc; i++) { |
488 |
|
|
p = list.gl_pathv[i]; |
489 |
|
|
qfilename = shell_quote(p); |
490 |
|
|
if (qfilename != NULL) { |
491 |
|
|
if (i != 0) { |
492 |
|
|
(void) strlcat(gfilename, " ", length); |
493 |
|
|
} |
494 |
|
|
(void) strlcat(gfilename, qfilename, length); |
495 |
|
|
free(qfilename); |
496 |
|
|
} |
497 |
|
|
} |
498 |
|
|
globfree(&list); |
499 |
|
|
free(filename); |
500 |
|
|
free(ofilename); |
501 |
|
|
return (gfilename); |
502 |
|
|
} |
503 |
|
|
|
504 |
|
|
/* |
505 |
|
|
* Expand LESSOPEN or LESSCLOSE. Returns a newly allocated string |
506 |
|
|
* on success, NULL otherwise. |
507 |
|
|
*/ |
508 |
|
|
static char * |
509 |
|
|
expand_pct_s(const char *fmt, ...) |
510 |
|
|
{ |
511 |
|
|
int n; |
512 |
|
|
int len; |
513 |
|
|
char *r, *d; |
514 |
|
|
const char *f[3]; /* max expansions + 1 for NULL */ |
515 |
|
|
va_list ap; |
516 |
|
|
|
517 |
|
|
va_start(ap, fmt); |
518 |
|
|
for (n = 0; n < ((sizeof (f)/sizeof (f[0])) - 1); n++) { |
519 |
|
|
f[n] = (const char *)va_arg(ap, const char *); |
520 |
|
|
if (f[n] == NULL) { |
521 |
|
|
break; |
522 |
|
|
} |
523 |
|
|
} |
524 |
|
|
va_end(ap); |
525 |
|
|
f[n] = NULL; /* terminate list */ |
526 |
|
|
|
527 |
|
|
len = strlen(fmt) + 1; |
528 |
|
|
for (n = 0; f[n] != NULL; n++) { |
529 |
|
|
len += strlen(f[n]); /* technically could -2 for "%s" */ |
530 |
|
|
} |
531 |
|
|
r = ecalloc(len, sizeof (char)); |
532 |
|
|
|
533 |
|
|
for (n = 0, d = r; *fmt != 0; ) { |
534 |
|
|
if (*fmt != '%') { |
535 |
|
|
*d++ = *fmt++; |
536 |
|
|
continue; |
537 |
|
|
} |
538 |
|
|
fmt++; |
539 |
|
|
/* Permit embedded "%%" */ |
540 |
|
|
switch (*fmt) { |
541 |
|
|
case '%': |
542 |
|
|
*d++ = '%'; |
543 |
|
|
fmt++; |
544 |
|
|
break; |
545 |
|
|
case 's': |
546 |
|
|
if (f[n] == NULL) { |
547 |
|
|
va_end(ap); |
548 |
|
|
free(r); |
549 |
|
|
return (NULL); |
550 |
|
|
} |
551 |
|
|
(void) strlcpy(d, f[n++], r + len - d); |
552 |
|
|
fmt++; |
553 |
|
|
d += strlen(d); |
554 |
|
|
break; |
555 |
|
|
default: |
556 |
|
|
va_end(ap); |
557 |
|
|
free(r); |
558 |
|
|
return (NULL); |
559 |
|
|
} |
560 |
|
|
} |
561 |
|
|
*d = '\0'; |
562 |
|
|
return (r); |
563 |
|
|
} |
564 |
|
|
|
565 |
|
|
/* |
566 |
|
|
* See if we should open a "replacement file" |
567 |
|
|
* instead of the file we're about to open. |
568 |
|
|
*/ |
569 |
|
|
char * |
570 |
|
|
open_altfile(char *filename, int *pf, void **pfd) |
571 |
|
|
{ |
572 |
|
|
char *lessopen; |
573 |
|
|
char *cmd; |
574 |
|
|
FILE *fd; |
575 |
|
|
int returnfd = 0; |
576 |
|
|
|
577 |
✗✓ |
16 |
if (!use_lessopen || secure) |
578 |
|
|
return (NULL); |
579 |
|
8 |
ch_ungetchar(-1); |
580 |
✓✗ |
8 |
if ((lessopen = lgetenv("LESSOPEN")) == NULL) |
581 |
|
8 |
return (NULL); |
582 |
|
|
while (*lessopen == '|') { |
583 |
|
|
/* |
584 |
|
|
* If LESSOPEN starts with a |, it indicates |
585 |
|
|
* a "pipe preprocessor". |
586 |
|
|
*/ |
587 |
|
|
lessopen++; |
588 |
|
|
returnfd++; |
589 |
|
|
} |
590 |
|
|
if (*lessopen == '-') { |
591 |
|
|
/* |
592 |
|
|
* Lessopen preprocessor will accept "-" as a filename. |
593 |
|
|
*/ |
594 |
|
|
lessopen++; |
595 |
|
|
} else { |
596 |
|
|
if (strcmp(filename, "-") == 0) |
597 |
|
|
return (NULL); |
598 |
|
|
} |
599 |
|
|
|
600 |
|
|
if ((cmd = expand_pct_s(lessopen, filename, NULL)) == NULL) { |
601 |
|
|
error("Invalid LESSOPEN variable", NULL); |
602 |
|
|
return (NULL); |
603 |
|
|
} |
604 |
|
|
fd = shellcmd(cmd); |
605 |
|
|
free(cmd); |
606 |
|
|
if (fd == NULL) { |
607 |
|
|
/* |
608 |
|
|
* Cannot create the pipe. |
609 |
|
|
*/ |
610 |
|
|
return (NULL); |
611 |
|
|
} |
612 |
|
|
if (returnfd) { |
613 |
|
|
int f; |
614 |
|
|
char c; |
615 |
|
|
|
616 |
|
|
/* |
617 |
|
|
* Read one char to see if the pipe will produce any data. |
618 |
|
|
* If it does, push the char back on the pipe. |
619 |
|
|
*/ |
620 |
|
|
f = fileno(fd); |
621 |
|
|
if (read(f, &c, 1) != 1) { |
622 |
|
|
/* |
623 |
|
|
* Pipe is empty. |
624 |
|
|
* If more than 1 pipe char was specified, |
625 |
|
|
* the exit status tells whether the file itself |
626 |
|
|
* is empty, or if there is no alt file. |
627 |
|
|
* If only one pipe char, just assume no alt file. |
628 |
|
|
*/ |
629 |
|
|
int status = pclose(fd); |
630 |
|
|
if (returnfd > 1 && status == 0) { |
631 |
|
|
*pfd = NULL; |
632 |
|
|
*pf = -1; |
633 |
|
|
return (estrdup(FAKE_EMPTYFILE)); |
634 |
|
|
} |
635 |
|
|
return (NULL); |
636 |
|
|
} |
637 |
|
|
ch_ungetchar(c); |
638 |
|
|
*pfd = (void *) fd; |
639 |
|
|
*pf = f; |
640 |
|
|
return (estrdup("-")); |
641 |
|
|
} |
642 |
|
|
cmd = readfd(fd); |
643 |
|
|
pclose(fd); |
644 |
|
|
if (*cmd == '\0') |
645 |
|
|
/* |
646 |
|
|
* Pipe is empty. This means there is no alt file. |
647 |
|
|
*/ |
648 |
|
|
return (NULL); |
649 |
|
|
return (cmd); |
650 |
|
8 |
} |
651 |
|
|
|
652 |
|
|
/* |
653 |
|
|
* Close a replacement file. |
654 |
|
|
*/ |
655 |
|
|
void |
656 |
|
|
close_altfile(char *altfilename, char *filename, void *pipefd) |
657 |
|
|
{ |
658 |
|
|
char *lessclose; |
659 |
|
|
FILE *fd; |
660 |
|
|
char *cmd; |
661 |
|
|
|
662 |
|
|
if (secure) |
663 |
|
|
return; |
664 |
|
|
if (pipefd != NULL) { |
665 |
|
|
pclose((FILE *)pipefd); |
666 |
|
|
} |
667 |
|
|
if ((lessclose = lgetenv("LESSCLOSE")) == NULL) |
668 |
|
|
return; |
669 |
|
|
cmd = expand_pct_s(lessclose, filename, altfilename, NULL); |
670 |
|
|
if (cmd == NULL) { |
671 |
|
|
error("Invalid LESSCLOSE variable", NULL); |
672 |
|
|
return; |
673 |
|
|
} |
674 |
|
|
fd = shellcmd(cmd); |
675 |
|
|
free(cmd); |
676 |
|
|
if (fd != NULL) |
677 |
|
|
(void) pclose(fd); |
678 |
|
|
} |
679 |
|
|
|
680 |
|
|
/* |
681 |
|
|
* Is the specified file a directory? |
682 |
|
|
*/ |
683 |
|
|
int |
684 |
|
|
is_dir(char *filename) |
685 |
|
|
{ |
686 |
|
|
int isdir = 0; |
687 |
|
|
int r; |
688 |
|
16 |
struct stat statbuf; |
689 |
|
|
|
690 |
|
8 |
filename = shell_unquote(filename); |
691 |
|
|
|
692 |
|
8 |
r = stat(filename, &statbuf); |
693 |
✓✗ |
24 |
isdir = (r >= 0 && S_ISDIR(statbuf.st_mode)); |
694 |
|
8 |
free(filename); |
695 |
|
8 |
return (isdir); |
696 |
|
8 |
} |
697 |
|
|
|
698 |
|
|
/* |
699 |
|
|
* Returns NULL if the file can be opened and |
700 |
|
|
* is an ordinary file, otherwise an error message |
701 |
|
|
* (if it cannot be opened or is a directory, etc.) |
702 |
|
|
*/ |
703 |
|
|
char * |
704 |
|
|
bad_file(char *filename) |
705 |
|
|
{ |
706 |
|
|
char *m = NULL; |
707 |
|
|
|
708 |
|
16 |
filename = shell_unquote(filename); |
709 |
✓✗✗✓
|
16 |
if (!force_open && is_dir(filename)) { |
710 |
|
|
m = easprintf("%s is a directory", filename); |
711 |
|
|
} else { |
712 |
|
|
int r; |
713 |
|
8 |
struct stat statbuf; |
714 |
|
|
|
715 |
|
8 |
r = stat(filename, &statbuf); |
716 |
✗✓ |
8 |
if (r < 0) { |
717 |
|
|
m = errno_message(filename); |
718 |
✗✓ |
8 |
} else if (force_open) { |
719 |
|
|
m = NULL; |
720 |
✗✓ |
8 |
} else if (!S_ISREG(statbuf.st_mode)) { |
721 |
|
|
m = easprintf("%s is not a regular file (use -f to " |
722 |
|
|
"see it)", filename); |
723 |
|
|
} |
724 |
|
8 |
} |
725 |
|
8 |
free(filename); |
726 |
|
8 |
return (m); |
727 |
|
|
} |
728 |
|
|
|
729 |
|
|
/* |
730 |
|
|
* Return the size of a file, as cheaply as possible. |
731 |
|
|
*/ |
732 |
|
|
off_t |
733 |
|
|
filesize(int f) |
734 |
|
|
{ |
735 |
|
16 |
struct stat statbuf; |
736 |
|
|
|
737 |
✓✗ |
8 |
if (fstat(f, &statbuf) >= 0) |
738 |
|
8 |
return (statbuf.st_size); |
739 |
|
|
return (-1); |
740 |
|
8 |
} |
741 |
|
|
|
742 |
|
|
/* |
743 |
|
|
* Return last component of a pathname. |
744 |
|
|
*/ |
745 |
|
|
char * |
746 |
|
|
last_component(char *name) |
747 |
|
|
{ |
748 |
|
|
char *slash; |
749 |
|
|
|
750 |
|
|
for (slash = name + strlen(name); slash > name; ) { |
751 |
|
|
--slash; |
752 |
|
|
if (*slash == '/') |
753 |
|
|
return (slash + 1); |
754 |
|
|
} |
755 |
|
|
return (name); |
756 |
|
|
} |