1 /* $OpenBSD: diff.c,v 1.67 2019/06/28 13:35:00 deraadt Exp $ */
2
3 /*
4 * Copyright (c) 2003 Todd C. Miller <Todd.Miller@courtesan.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 * Sponsored in part by the Defense Advanced Research Projects
19 * Agency (DARPA) and Air Force Research Laboratory, Air Force
20 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
21 */
22
23 #include <sys/stat.h>
24
25 #include <ctype.h>
26 #include <err.h>
27 #include <errno.h>
28 #include <getopt.h>
29 #include <limits.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34
35 #include "diff.h"
36 #include "xmalloc.h"
37
38 static const char diff_version[] = "FreeBSD diff 20260206";
39 bool lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag;
40 bool ignore_file_case, suppress_common, color, noderef;
41 static bool help = false;
42 int diff_format, diff_context, diff_algorithm, status;
43 bool diff_algorithm_set;
44 int tabsize = 8, width = 130;
45 static int colorflag = COLORFLAG_NEVER;
46 char *start, *ifdefname, *diffargs, *label[2];
47 char *ignore_pats, *most_recent_pat;
48 char *group_format = NULL;
49 const char *add_code, *del_code;
50 struct stat stb1, stb2;
51 struct excludes *excludes_list;
52 regex_t ignore_re, most_recent_re;
53
54 static struct algorithm {
55 const char *name;
56 int id;
57 } algorithms[] = {
58 {"stone", D_DIFFSTONE},
59 {"myers", D_DIFFMYERS},
60 {"patience", D_DIFFPATIENCE},
61 {NULL, D_DIFFNONE}
62 };
63
64 #define OPTIONS "0123456789A:aBbC:cdD:efF:HhI:iL:lnNPpqrS:sTtU:uwW:X:x:y"
65 enum {
66 OPT_TSIZE = CHAR_MAX + 1,
67 OPT_STRIPCR,
68 OPT_IGN_FN_CASE,
69 OPT_NO_IGN_FN_CASE,
70 OPT_NORMAL,
71 OPT_HELP,
72 OPT_HORIZON_LINES,
73 OPT_CHANGED_GROUP_FORMAT,
74 OPT_SUPPRESS_COMMON,
75 OPT_COLOR,
76 OPT_NO_DEREFERENCE,
77 OPT_VERSION,
78 };
79
80 static struct option longopts[] = {
81 { "algorithm", required_argument, 0, 'A' },
82 { "text", no_argument, 0, 'a' },
83 { "ignore-space-change", no_argument, 0, 'b' },
84 { "context", optional_argument, 0, 'C' },
85 { "ifdef", required_argument, 0, 'D' },
86 { "minimal", no_argument, 0, 'd' },
87 { "ed", no_argument, 0, 'e' },
88 { "forward-ed", no_argument, 0, 'f' },
89 { "show-function-line", required_argument, 0, 'F' },
90 { "speed-large-files", no_argument, NULL, 'H' },
91 { "ignore-blank-lines", no_argument, 0, 'B' },
92 { "ignore-matching-lines", required_argument, 0, 'I' },
93 { "ignore-case", no_argument, 0, 'i' },
94 { "paginate", no_argument, NULL, 'l' },
95 { "label", required_argument, 0, 'L' },
96 { "new-file", no_argument, 0, 'N' },
97 { "rcs", no_argument, 0, 'n' },
98 { "unidirectional-new-file", no_argument, 0, 'P' },
99 { "show-c-function", no_argument, 0, 'p' },
100 { "brief", no_argument, 0, 'q' },
101 { "recursive", no_argument, 0, 'r' },
102 { "report-identical-files", no_argument, 0, 's' },
103 { "starting-file", required_argument, 0, 'S' },
104 { "expand-tabs", no_argument, 0, 't' },
105 { "initial-tab", no_argument, 0, 'T' },
106 { "unified", optional_argument, 0, 'U' },
107 { "ignore-all-space", no_argument, 0, 'w' },
108 { "width", required_argument, 0, 'W' },
109 { "exclude", required_argument, 0, 'x' },
110 { "exclude-from", required_argument, 0, 'X' },
111 { "side-by-side", no_argument, NULL, 'y' },
112 { "ignore-file-name-case", no_argument, NULL, OPT_IGN_FN_CASE },
113 { "help", no_argument, NULL, OPT_HELP},
114 { "horizon-lines", required_argument, NULL, OPT_HORIZON_LINES },
115 { "no-dereference", no_argument, NULL, OPT_NO_DEREFERENCE},
116 { "no-ignore-file-name-case", no_argument, NULL, OPT_NO_IGN_FN_CASE },
117 { "normal", no_argument, NULL, OPT_NORMAL },
118 { "strip-trailing-cr", no_argument, NULL, OPT_STRIPCR },
119 { "tabsize", required_argument, NULL, OPT_TSIZE },
120 { "changed-group-format", required_argument, NULL, OPT_CHANGED_GROUP_FORMAT},
121 { "suppress-common-lines", no_argument, NULL, OPT_SUPPRESS_COMMON },
122 { "color", optional_argument, NULL, OPT_COLOR },
123 { "version", no_argument, NULL, OPT_VERSION},
124 { NULL, 0, 0, '\0'}
125 };
126
127 static void checked_regcomp(char const *, regex_t *);
128 static void usage(void) __dead2;
129 static void conflicting_format(void) __dead2;
130 static void push_excludes(char *);
131 static void push_ignore_pats(char *);
132 static void read_excludes_file(char *file);
133 static void set_argstr(char **, char **);
134 static char *splice(char *, char *);
135 static bool do_color(void);
136
137 int
main(int argc,char ** argv)138 main(int argc, char **argv)
139 {
140 const char *errstr;
141 char **oargv;
142 int ch, dflags, error, lastch, gotstdin, prevoptind, newarg;
143
144 oargv = argv;
145 gotstdin = 0;
146 dflags = 0;
147 lastch = '\0';
148 prevoptind = 1;
149 newarg = 1;
150 diff_context = 3;
151 diff_format = D_UNSET;
152 diff_algorithm = D_DIFFMYERS;
153 diff_algorithm_set = false;
154 #define FORMAT_MISMATCHED(type) \
155 (diff_format != D_UNSET && diff_format != (type))
156 while ((ch = getopt_long(argc, argv, OPTIONS, longopts, NULL)) != -1) {
157 switch (ch) {
158 case '0': case '1': case '2': case '3': case '4':
159 case '5': case '6': case '7': case '8': case '9':
160 if (newarg)
161 usage(); /* disallow -[0-9]+ */
162 else if (lastch == 'c' || lastch == 'u')
163 diff_context = 0;
164 else if (!isdigit(lastch) || diff_context > INT_MAX / 10)
165 usage();
166 diff_context = (diff_context * 10) + (ch - '0');
167 break;
168 case 'A':
169 diff_algorithm = D_DIFFNONE;
170 for (struct algorithm *a = algorithms; a->name;a++) {
171 if(strcasecmp(optarg, a->name) == 0) {
172 diff_algorithm = a->id;
173 diff_algorithm_set = true;
174 break;
175 }
176 }
177
178 if (diff_algorithm == D_DIFFNONE) {
179 warnx("unknown algorithm: %s", optarg);
180 usage();
181 }
182 break;
183 case 'a':
184 dflags |= D_FORCEASCII;
185 break;
186 case 'b':
187 dflags |= D_FOLDBLANKS;
188 break;
189 case 'C':
190 case 'c':
191 if (FORMAT_MISMATCHED(D_CONTEXT))
192 conflicting_format();
193 cflag = true;
194 diff_format = D_CONTEXT;
195 if (optarg != NULL) {
196 diff_context = (int) strtonum(optarg,
197 1, INT_MAX, &errstr);
198 if (errstr != NULL) {
199 warnx("context size is %s: %s",
200 errstr, optarg);
201 usage();
202 }
203 }
204 break;
205 case 'd':
206 dflags |= D_MINIMAL;
207 break;
208 case 'D':
209 if (FORMAT_MISMATCHED(D_IFDEF))
210 conflicting_format();
211 diff_format = D_IFDEF;
212 ifdefname = optarg;
213 break;
214 case 'e':
215 if (FORMAT_MISMATCHED(D_EDIT))
216 conflicting_format();
217 diff_format = D_EDIT;
218 break;
219 case 'f':
220 if (FORMAT_MISMATCHED(D_REVERSE))
221 conflicting_format();
222 diff_format = D_REVERSE;
223 break;
224 case 'H':
225 /* ignore but needed for compatibility with GNU diff */
226 break;
227 case 'h':
228 /* silently ignore for backwards compatibility */
229 break;
230 case 'B':
231 dflags |= D_SKIPBLANKLINES;
232 break;
233 case 'F':
234 if (dflags & D_PROTOTYPE)
235 conflicting_format();
236 dflags |= D_MATCHLAST;
237 most_recent_pat = xstrdup(optarg);
238 break;
239 case 'I':
240 push_ignore_pats(optarg);
241 break;
242 case 'i':
243 dflags |= D_IGNORECASE;
244 break;
245 case 'L':
246 if (label[0] == NULL)
247 label[0] = optarg;
248 else if (label[1] == NULL)
249 label[1] = optarg;
250 else
251 usage();
252 break;
253 case 'l':
254 dflags |= D_PAGINATION;
255 lflag = true;
256 break;
257 case 'N':
258 Nflag = true;
259 break;
260 case 'n':
261 if (FORMAT_MISMATCHED(D_NREVERSE))
262 conflicting_format();
263 diff_format = D_NREVERSE;
264 break;
265 case 'p':
266 if (dflags & D_MATCHLAST)
267 conflicting_format();
268 dflags |= D_PROTOTYPE;
269 break;
270 case 'P':
271 Pflag = true;
272 break;
273 case 'r':
274 rflag = true;
275 break;
276 case 'q':
277 if (FORMAT_MISMATCHED(D_BRIEF))
278 conflicting_format();
279 if (diff_format == D_BRIEF) {
280 diff_format = D_QUIET;
281 /* If already D_QUIET, avoid getting more verbose. */
282 } else if (diff_format != D_QUIET) {
283 diff_format = D_BRIEF;
284 }
285 break;
286 case 'S':
287 start = optarg;
288 break;
289 case 's':
290 sflag = true;
291 break;
292 case 'T':
293 Tflag = true;
294 break;
295 case 't':
296 dflags |= D_EXPANDTABS;
297 break;
298 case 'U':
299 case 'u':
300 if (FORMAT_MISMATCHED(D_UNIFIED))
301 conflicting_format();
302 diff_format = D_UNIFIED;
303 if (optarg != NULL) {
304 diff_context = (int) strtonum(optarg,
305 0, INT_MAX, &errstr);
306 if (errstr != NULL) {
307 warnx("context size is %s: %s",
308 errstr, optarg);
309 usage();
310 }
311 }
312 break;
313 case 'w':
314 dflags |= D_IGNOREBLANKS;
315 break;
316 case 'W':
317 width = (int) strtonum(optarg, 1, INT_MAX, &errstr);
318 if (errstr != NULL) {
319 warnx("width is %s: %s", errstr, optarg);
320 usage();
321 }
322 break;
323 case 'X':
324 read_excludes_file(optarg);
325 break;
326 case 'x':
327 push_excludes(optarg);
328 break;
329 case 'y':
330 if (FORMAT_MISMATCHED(D_SIDEBYSIDE))
331 conflicting_format();
332 diff_format = D_SIDEBYSIDE;
333 break;
334 case OPT_CHANGED_GROUP_FORMAT:
335 if (FORMAT_MISMATCHED(D_GFORMAT))
336 conflicting_format();
337 diff_format = D_GFORMAT;
338 group_format = optarg;
339 break;
340 case OPT_HELP:
341 help = true;
342 usage();
343 break;
344 case OPT_HORIZON_LINES:
345 break; /* XXX TODO for compatibility with GNU diff3 */
346 case OPT_IGN_FN_CASE:
347 ignore_file_case = true;
348 break;
349 case OPT_NO_IGN_FN_CASE:
350 ignore_file_case = false;
351 break;
352 case OPT_NORMAL:
353 if (FORMAT_MISMATCHED(D_NORMAL))
354 conflicting_format();
355 diff_format = D_NORMAL;
356 break;
357 case OPT_TSIZE:
358 tabsize = (int) strtonum(optarg, 1, INT_MAX, &errstr);
359 if (errstr != NULL) {
360 warnx("tabsize is %s: %s", errstr, optarg);
361 usage();
362 }
363 break;
364 case OPT_STRIPCR:
365 dflags |= D_STRIPCR;
366 break;
367 case OPT_SUPPRESS_COMMON:
368 suppress_common = 1;
369 break;
370 case OPT_COLOR:
371 if (optarg == NULL || strncmp(optarg, "auto", 4) == 0)
372 colorflag = COLORFLAG_AUTO;
373 else if (strncmp(optarg, "always", 6) == 0)
374 colorflag = COLORFLAG_ALWAYS;
375 else if (strncmp(optarg, "never", 5) == 0)
376 colorflag = COLORFLAG_NEVER;
377 else {
378 warnx("unsupported --color value "
379 "(must be always, auto, or never): "
380 "%s", optarg);
381 usage();
382 }
383 break;
384 case OPT_NO_DEREFERENCE:
385 noderef = true;
386 break;
387 case OPT_VERSION:
388 printf("%s\n", diff_version);
389 exit(0);
390 default:
391 usage();
392 break;
393 }
394 lastch = ch;
395 newarg = optind != prevoptind;
396 prevoptind = optind;
397 }
398 if (diff_format == D_UNSET && (dflags & D_PROTOTYPE) != 0)
399 diff_format = D_CONTEXT;
400 if (diff_format == D_UNSET)
401 diff_format = D_NORMAL;
402 argc -= optind;
403 argv += optind;
404
405 if (do_color()) {
406 char *p;
407 const char *env;
408
409 color = true;
410 add_code = "32";
411 del_code = "31";
412 env = getenv("DIFFCOLORS");
413 if (env != NULL && *env != '\0' && (p = strdup(env))) {
414 add_code = p;
415 strsep(&p, ":");
416 if (p != NULL)
417 del_code = p;
418 }
419 }
420
421 #ifdef __OpenBSD__
422 if (pledge("stdio rpath tmppath", NULL) == -1)
423 err(2, "pledge");
424 #endif
425
426 /*
427 * Do sanity checks, fill in stb1 and stb2 and call the appropriate
428 * driver routine. Both drivers use the contents of stb1 and stb2.
429 */
430 if (argc != 2)
431 usage();
432 checked_regcomp(ignore_pats, &ignore_re);
433 checked_regcomp(most_recent_pat, &most_recent_re);
434 if (strcmp(argv[0], "-") == 0) {
435 fstat(STDIN_FILENO, &stb1);
436 gotstdin = 1;
437 } else if (stat(argv[0], &stb1) != 0) {
438 if (!Nflag || errno != ENOENT)
439 err(2, "%s", argv[0]);
440 dflags |= D_EMPTY1;
441 memset(&stb1, 0, sizeof(struct stat));
442 }
443
444 if (strcmp(argv[1], "-") == 0) {
445 fstat(STDIN_FILENO, &stb2);
446 gotstdin = 1;
447 } else if (stat(argv[1], &stb2) != 0) {
448 if (!Nflag || errno != ENOENT)
449 err(2, "%s", argv[1]);
450 dflags |= D_EMPTY2;
451 memset(&stb2, 0, sizeof(stb2));
452 stb2.st_mode = stb1.st_mode;
453 }
454
455 if (dflags & D_EMPTY1 && dflags & D_EMPTY2){
456 warn("%s", argv[0]);
457 warn("%s", argv[1]);
458 exit(2);
459 }
460
461 if (stb1.st_mode == 0)
462 stb1.st_mode = stb2.st_mode;
463
464 if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode)))
465 errx(2, "can't compare - to a directory");
466 set_argstr(oargv, argv);
467 if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
468 if (diff_format == D_IFDEF)
469 errx(2, "-D option not supported with directories");
470 diffdir(argv[0], argv[1], dflags);
471 } else {
472 if (S_ISDIR(stb1.st_mode)) {
473 argv[0] = splice(argv[0], argv[1]);
474 if (stat(argv[0], &stb1) == -1)
475 err(2, "%s", argv[0]);
476 }
477 if (S_ISDIR(stb2.st_mode)) {
478 argv[1] = splice(argv[1], argv[0]);
479 if (stat(argv[1], &stb2) == -1)
480 err(2, "%s", argv[1]);
481 }
482
483 error = diffreg(argv[0], argv[1], dflags, 1);
484 if (diff_format != D_QUIET)
485 print_status(error, argv[0], argv[1], "");
486 }
487 if (fflush(stdout) != 0)
488 err(2, "stdout");
489 exit(status);
490 }
491
492 static void
checked_regcomp(char const * pattern,regex_t * comp)493 checked_regcomp(char const *pattern, regex_t *comp)
494 {
495 char buf[BUFSIZ];
496 int error;
497
498 if (pattern == NULL)
499 return;
500
501 error = regcomp(comp, pattern, REG_NEWLINE | REG_EXTENDED);
502 if (error != 0) {
503 regerror(error, comp, buf, sizeof(buf));
504 if (*pattern != '\0')
505 errx(2, "%s: %s", pattern, buf);
506 else
507 errx(2, "%s", buf);
508 }
509 }
510
511 static void
set_argstr(char ** av,char ** ave)512 set_argstr(char **av, char **ave)
513 {
514 size_t argsize;
515 char **ap;
516
517 argsize = 4 + *ave - *av + 1;
518 diffargs = xmalloc(argsize);
519 strlcpy(diffargs, "diff", argsize);
520 for (ap = av + 1; ap < ave; ap++) {
521 if (strcmp(*ap, "--") != 0) {
522 strlcat(diffargs, " ", argsize);
523 strlcat(diffargs, *ap, argsize);
524 }
525 }
526 }
527
528 /*
529 * Read in an excludes file and push each line.
530 */
531 static void
read_excludes_file(char * file)532 read_excludes_file(char *file)
533 {
534 FILE *fp;
535 char *pattern = NULL;
536 size_t blen = 0;
537 ssize_t len;
538
539 if (strcmp(file, "-") == 0)
540 fp = stdin;
541 else if ((fp = fopen(file, "r")) == NULL)
542 err(2, "%s", file);
543 while ((len = getline(&pattern, &blen, fp)) >= 0) {
544 if ((len > 0) && (pattern[len - 1] == '\n'))
545 pattern[len - 1] = '\0';
546 push_excludes(pattern);
547 /* we allocate a new string per line */
548 pattern = NULL;
549 blen = 0;
550 }
551 free(pattern);
552 if (strcmp(file, "-") != 0)
553 fclose(fp);
554 }
555
556 /*
557 * Push a pattern onto the excludes list.
558 */
559 static void
push_excludes(char * pattern)560 push_excludes(char *pattern)
561 {
562 struct excludes *entry;
563
564 entry = xmalloc(sizeof(*entry));
565 entry->pattern = pattern;
566 entry->next = excludes_list;
567 excludes_list = entry;
568 }
569
570 static void
push_ignore_pats(char * pattern)571 push_ignore_pats(char *pattern)
572 {
573 size_t len;
574
575 if (ignore_pats == NULL)
576 ignore_pats = xstrdup(pattern);
577 else {
578 /* old + "|" + new + NUL */
579 len = strlen(ignore_pats) + strlen(pattern) + 2;
580 ignore_pats = xreallocarray(ignore_pats, 1, len);
581 strlcat(ignore_pats, "|", len);
582 strlcat(ignore_pats, pattern, len);
583 }
584 }
585
586 void
print_status(int val,char * path1,char * path2,const char * entry)587 print_status(int val, char *path1, char *path2, const char *entry)
588 {
589 if (label[0] != NULL)
590 path1 = label[0];
591 if (label[1] != NULL)
592 path2 = label[1];
593
594 switch (val) {
595 case D_BINARY:
596 printf("Binary files %s%s and %s%s differ\n",
597 path1, entry, path2, entry);
598 break;
599 case D_DIFFER:
600 if (diff_format == D_BRIEF)
601 printf("Files %s%s and %s%s differ\n",
602 path1, entry, path2, entry);
603 break;
604 case D_SAME:
605 if (sflag)
606 printf("Files %s%s and %s%s are identical\n",
607 path1, entry, path2, entry);
608 break;
609 case D_MISMATCH1:
610 printf("File %s%s is a directory while file %s%s is a regular file\n",
611 path1, entry, path2, entry);
612 break;
613 case D_MISMATCH2:
614 printf("File %s%s is a regular file while file %s%s is a directory\n",
615 path1, entry, path2, entry);
616 break;
617 case D_SKIPPED1:
618 printf("File %s%s is not a regular file or directory and was skipped\n",
619 path1, entry);
620 break;
621 case D_SKIPPED2:
622 printf("File %s%s is not a regular file or directory and was skipped\n",
623 path2, entry);
624 break;
625 case D_ERROR:
626 break;
627 }
628 }
629
630 static void
usage(void)631 usage(void)
632 {
633 (void)fprintf(help ? stdout : stderr,
634 "usage: diff [-aBbdilpTtw] [-c | -e | -f | -n | -q | -u] [--ignore-case]\n"
635 " [--no-ignore-case] [--normal] [--strip-trailing-cr] [--tabsize]\n"
636 " [-I pattern] [-F pattern] [-L label] file1 file2\n"
637 " diff [-aBbdilpTtw] [-I pattern] [-L label] [--ignore-case]\n"
638 " [--no-ignore-case] [--normal] [--strip-trailing-cr] [--tabsize]\n"
639 " [-F pattern] -C number file1 file2\n"
640 " diff [-aBbdiltw] [-I pattern] [--ignore-case] [--no-ignore-case]\n"
641 " [--normal] [--strip-trailing-cr] [--tabsize] -D string file1 file2\n"
642 " diff [-aBbdilpTtw] [-I pattern] [-L label] [--ignore-case]\n"
643 " [--no-ignore-case] [--normal] [--tabsize] [--strip-trailing-cr]\n"
644 " [-F pattern] -U number file1 file2\n"
645 " diff [-aBbdilNPprsTtw] [-c | -e | -f | -n | -q | -u] [--ignore-case]\n"
646 " [--no-ignore-case] [--normal] [--tabsize] [-I pattern] [-L label]\n"
647 " [-F pattern] [-S name] [-X file] [-x pattern] dir1 dir2\n"
648 " diff [-aBbditwW] [--expand-tabs] [--ignore-all-space]\n"
649 " [--ignore-blank-lines] [--ignore-case] [--minimal]\n"
650 " [--no-ignore-file-name-case] [--strip-trailing-cr]\n"
651 " [--suppress-common-lines] [--tabsize] [--text] [--width]\n"
652 " -y | --side-by-side file1 file2\n"
653 " diff [--help] [--version]\n");
654
655 if (help)
656 exit(0);
657 else
658 exit(2);
659 }
660
661 static void
conflicting_format(void)662 conflicting_format(void)
663 {
664
665 fprintf(stderr, "error: conflicting output format options.\n");
666 usage();
667 }
668
669 static bool
do_color(void)670 do_color(void)
671 {
672 const char *p, *p2;
673
674 switch (colorflag) {
675 case COLORFLAG_AUTO:
676 p = getenv("CLICOLOR");
677 p2 = getenv("COLORTERM");
678 if ((p != NULL && *p != '\0') || (p2 != NULL && *p2 != '\0'))
679 return isatty(STDOUT_FILENO);
680 break;
681 case COLORFLAG_ALWAYS:
682 return (true);
683 case COLORFLAG_NEVER:
684 return (false);
685 }
686
687 return (false);
688 }
689
690 static char *
splice(char * dir,char * path)691 splice(char *dir, char *path)
692 {
693 char *tail, *buf;
694 size_t dirlen;
695
696 dirlen = strlen(dir);
697 while (dirlen != 0 && dir[dirlen - 1] == '/')
698 dirlen--;
699 if ((tail = strrchr(path, '/')) == NULL)
700 tail = path;
701 else
702 tail++;
703 xasprintf(&buf, "%.*s/%s", (int)dirlen, dir, tail);
704 return (buf);
705 }
706