1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini cmp implementation for busybox
4  *
5  * Copyright (C) 2000,2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9 //config:config CMP
10 //config:	bool "cmp (4.9 kb)"
11 //config:	default y
12 //config:	help
13 //config:	cmp is used to compare two files and returns the result
14 //config:	to standard output.
15 
16 //applet:IF_CMP(APPLET(cmp, BB_DIR_USR_BIN, BB_SUID_DROP))
17 
18 //kbuild:lib-$(CONFIG_CMP) += cmp.o
19 
20 //usage:#define cmp_trivial_usage
21 //usage:       "[-ls] [-n NUM] FILE1 [FILE2" IF_DESKTOP(" [SKIP1 [SKIP2]]") "]"
22 //usage:#define cmp_full_usage "\n\n"
23 //usage:       "Compare FILE1 with FILE2 (or stdin)\n"
24 //usage:     "\n	-l	Write the byte numbers (decimal) and values (octal)"
25 //usage:     "\n		for all differing bytes"
26 //usage:     "\n	-s	Quiet"
27 //usage:     "\n	-n NUM	Compare at most NUM bytes"
28 
29 /* BB_AUDIT SUSv3 (virtually) compliant -- uses nicer GNU format for -l. */
30 /* http://www.opengroup.org/onlinepubs/007904975/utilities/cmp.html */
31 
32 #include "libbb.h"
33 
34 static const char fmt_eof[] ALIGN1 = "cmp: EOF on %s\n";
35 static const char fmt_differ[] ALIGN1 = "%s %s differ: char %"OFF_FMT"u, line %u\n";
36 // This fmt_l_opt uses gnu-isms.  SUSv3 would be "%.0s%.0s%"OFF_FMT"u %o %o\n"
37 static const char fmt_l_opt[] ALIGN1 = "%.0s%.0s%"OFF_FMT"u %3o %3o\n";
38 
39 #define OPT_STR "sln:+"
40 #define CMP_OPT_s (1<<0)
41 #define CMP_OPT_l (1<<1)
42 #define CMP_OPT_n (1<<2)
43 
44 int cmp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
cmp_main(int argc UNUSED_PARAM,char ** argv)45 int cmp_main(int argc UNUSED_PARAM, char **argv)
46 {
47 	FILE *fp1, *fp2, *outfile = stdout;
48 	const char *filename1, *filename2 = "-";
49 	off_t skip1 = 0, skip2 = 0, char_pos = 0;
50 	int line_pos = 1; /* Hopefully won't overflow... */
51 	const char *fmt;
52 	int c1, c2;
53 	unsigned opt;
54 	int retval = 0;
55 	int max_count = -1;
56 
57 	opt = getopt32(argv, "^"
58 			OPT_STR
59 			"\0" "-1"
60 			IF_DESKTOP(":?4")
61 			IF_NOT_DESKTOP(":?2")
62 			":l--s:s--l",
63 			&max_count
64 	);
65 	argv += optind;
66 
67 	filename1 = *argv;
68 	if (*++argv) {
69 		filename2 = *argv;
70 		if (ENABLE_DESKTOP && *++argv) {
71 			skip1 = XATOOFF(*argv);
72 			if (*++argv) {
73 				skip2 = XATOOFF(*argv);
74 			}
75 		}
76 	}
77 
78 	xfunc_error_retval = 2;  /* missing file results in exitcode 2 */
79 	if (opt & CMP_OPT_s)
80 		logmode = 0;  /* -s suppresses open error messages */
81 	fp1 = xfopen_stdin(filename1);
82 	fp2 = xfopen_stdin(filename2);
83 	if (fp1 == fp2) {		/* Paranoia check... stdin == stdin? */
84 		/* Note that we don't bother reading stdin.  Neither does gnu wc.
85 		 * But perhaps we should, so that other apps down the chain don't
86 		 * get the input.  Consider 'echo hello | (cmp - - && cat -)'.
87 		 */
88 		return 0;
89 	}
90 	logmode = LOGMODE_STDIO;
91 
92 	if (opt & CMP_OPT_l)
93 		fmt = fmt_l_opt;
94 	else
95 		fmt = fmt_differ;
96 
97 	if (ENABLE_DESKTOP) {
98 		while (skip1) { getc(fp1); skip1--; }
99 		while (skip2) { getc(fp2); skip2--; }
100 	}
101 	do {
102 		if (max_count >= 0 && --max_count < 0)
103 			break;
104 		c1 = getc(fp1);
105 		c2 = getc(fp2);
106 		++char_pos;
107 		if (c1 != c2) {			/* Remember: a read error may have occurred. */
108 			retval = 1;		/* But assume the files are different for now. */
109 			if (c2 == EOF) {
110 				/* We know that fp1 isn't at EOF or in an error state.  But to
111 				 * save space below, things are setup to expect an EOF in fp1
112 				 * if an EOF occurred.  So, swap things around.
113 				 */
114 				fp1 = fp2;
115 				filename1 = filename2;
116 				c1 = c2;
117 			}
118 			if (c1 == EOF) {
119 				die_if_ferror(fp1, filename1);
120 				fmt = fmt_eof;	/* Well, no error, so it must really be EOF. */
121 				outfile = stderr;
122 				/* There may have been output to stdout (option -l), so
123 				 * make sure we fflush before writing to stderr. */
124 				fflush_all();
125 			}
126 			if (!(opt & CMP_OPT_s)) {
127 				if (opt & CMP_OPT_l) {
128 					line_pos = c1;	/* line_pos is unused in the -l case. */
129 				}
130 				fprintf(outfile, fmt, filename1, filename2, char_pos, line_pos, c2);
131 				if (opt) {	/* This must be -l since not -s. */
132 					/* If we encountered an EOF,
133 					 * the while check will catch it. */
134 					continue;
135 				}
136 			}
137 			break;
138 		}
139 		if (c1 == '\n') {
140 			++line_pos;
141 		}
142 	} while (c1 != EOF);
143 
144 	die_if_ferror(fp1, filename1);
145 	die_if_ferror(fp2, filename2);
146 
147 	fflush_stdout_and_exit(retval);
148 }
149