xref: /freebsd/contrib/libdiff/diff/diff.c (revision ce4dcb97ca433b2a2f03fbae957dae0ff16f6f51)
1  /* Commandline diff utility to test diff implementations. */
2  /*
3   * Copyright (c) 2018 Martin Pieuchot
4   * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de>
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  
19  #include <sys/mman.h>
20  #include <sys/stat.h>
21  #include <sys/types.h>
22  
23  #include <err.h>
24  #include <fcntl.h>
25  #include <stdint.h>
26  #include <stdio.h>
27  #include <stdlib.h>
28  #include <stdbool.h>
29  #include <string.h>
30  #include <unistd.h>
31  
32  #include <arraylist.h>
33  #include <diff_main.h>
34  #include <diff_output.h>
35  
36  enum diffreg_algo {
37  	DIFFREG_ALGO_MYERS_THEN_MYERS_DIVIDE = 0,
38  	DIFFREG_ALGO_MYERS_THEN_PATIENCE = 1,
39  	DIFFREG_ALGO_PATIENCE = 2,
40  	DIFFREG_ALGO_NONE = 3,
41  };
42  
43  __dead void	 usage(void);
44  int		 diffreg(char *, char *, enum diffreg_algo, bool, bool, bool,
45  			 int, bool);
46  FILE *		 openfile(const char *, char **, struct stat *);
47  
48  __dead void
49  usage(void)
50  {
51  	fprintf(stderr,
52  		"usage: %s [-apPQTwe] [-U n] file1 file2\n"
53  		"\n"
54  		"  -a   Treat input as ASCII even if binary data is detected\n"
55  		"  -p   Show function prototypes in hunk headers\n"
56  		"  -P   Use Patience Diff (slower but often nicer)\n"
57  		"  -Q   Use forward-Myers for small files, otherwise Patience\n"
58  		"  -T   Trivial algo: detect similar start and end only\n"
59  		"  -w   Ignore Whitespace\n"
60  		"  -U n Number of Context Lines\n"
61  		"  -e   Produce ed script output\n"
62  		, getprogname());
63  	exit(1);
64  }
65  
66  int
67  main(int argc, char *argv[])
68  {
69  	int ch, rc;
70  	bool force_text = false;
71  	bool ignore_whitespace = false;
72  	bool show_function_prototypes = false;
73  	bool edscript = false;
74  	int context_lines = 3;
75  	enum diffreg_algo algo = DIFFREG_ALGO_MYERS_THEN_MYERS_DIVIDE;
76  
77  	while ((ch = getopt(argc, argv, "apPQTwU:e")) != -1) {
78  		switch (ch) {
79  		case 'a':
80  			force_text = true;
81  			break;
82  		case 'p':
83  			show_function_prototypes = true;
84  			break;
85  		case 'P':
86  			algo = DIFFREG_ALGO_PATIENCE;
87  			break;
88  		case 'Q':
89  			algo = DIFFREG_ALGO_MYERS_THEN_PATIENCE;
90  			break;
91  		case 'T':
92  			algo = DIFFREG_ALGO_NONE;
93  			break;
94  		case 'w':
95  			ignore_whitespace = true;
96  			break;
97  		case 'U':
98  			context_lines = atoi(optarg);
99  			break;
100  		case 'e':
101  			edscript = true;
102  			break;
103  		default:
104  			usage();
105  		}
106  	}
107  
108  	argc -= optind;
109  	argv += optind;
110  
111  	if (argc != 2)
112  		usage();
113  
114  	rc = diffreg(argv[0], argv[1], algo, force_text, ignore_whitespace,
115  	    show_function_prototypes, context_lines, edscript);
116  	if (rc != DIFF_RC_OK) {
117  		fprintf(stderr, "diff: %s\n", strerror(rc));
118  		return 1;
119  	}
120  	return 0;
121  }
122  
123  const struct diff_algo_config myers_then_patience;
124  const struct diff_algo_config myers_then_myers_divide;
125  const struct diff_algo_config patience;
126  const struct diff_algo_config myers_divide;
127  
128  const struct diff_algo_config myers_then_patience = (struct diff_algo_config){
129  	.impl = diff_algo_myers,
130  	.permitted_state_size = 1024 * 1024 * sizeof(int),
131  	.fallback_algo = &patience,
132  };
133  
134  const struct diff_algo_config myers_then_myers_divide =
135  	(struct diff_algo_config){
136  	.impl = diff_algo_myers,
137  	.permitted_state_size = 1024 * 1024 * sizeof(int),
138  	.fallback_algo = &myers_divide,
139  };
140  
141  const struct diff_algo_config patience = (struct diff_algo_config){
142  	.impl = diff_algo_patience,
143  	/* After subdivision, do Patience again: */
144  	.inner_algo = &patience,
145  	/* If subdivision failed, do Myers Divide et Impera: */
146  	.fallback_algo = &myers_then_myers_divide,
147  };
148  
149  const struct diff_algo_config myers_divide = (struct diff_algo_config){
150  	.impl = diff_algo_myers_divide,
151  	/* When division succeeded, start from the top: */
152  	.inner_algo = &myers_then_myers_divide,
153  	/* (fallback_algo = NULL implies diff_algo_none). */
154  };
155  
156  const struct diff_algo_config no_algo = (struct diff_algo_config){
157  	.impl = diff_algo_none,
158  };
159  
160  /* If the state for a forward-Myers is small enough, use Myers, otherwise first
161   * do a Myers-divide. */
162  const struct diff_config diff_config_myers_then_myers_divide = {
163  	.atomize_func = diff_atomize_text_by_line,
164  	.algo = &myers_then_myers_divide,
165  };
166  
167  /* If the state for a forward-Myers is small enough, use Myers, otherwise first
168   * do a Patience. */
169  const struct diff_config diff_config_myers_then_patience = {
170  	.atomize_func = diff_atomize_text_by_line,
171  	.algo = &myers_then_patience,
172  };
173  
174  /* Directly force Patience as a first divider of the source file. */
175  const struct diff_config diff_config_patience = {
176  	.atomize_func = diff_atomize_text_by_line,
177  	.algo = &patience,
178  };
179  
180  /* Directly force Patience as a first divider of the source file. */
181  const struct diff_config diff_config_no_algo = {
182  	.atomize_func = diff_atomize_text_by_line,
183  };
184  
185  int
186  diffreg(char *file1, char *file2, enum diffreg_algo algo, bool force_text,
187      bool ignore_whitespace, bool show_function_prototypes, int context_lines,
188      bool edscript)
189  {
190  	char *str1, *str2;
191  	FILE *f1, *f2;
192  	struct stat st1, st2;
193  	struct diff_input_info info = {
194  		.left_path = file1,
195  		.right_path = file2,
196  	};
197  	struct diff_data left = {}, right = {};
198  	struct diff_result *result = NULL;
199  	int rc;
200  	const struct diff_config *cfg;
201  	int diff_flags = 0;
202  
203  	switch (algo) {
204  	default:
205  	case DIFFREG_ALGO_MYERS_THEN_MYERS_DIVIDE:
206  		cfg = &diff_config_myers_then_myers_divide;
207  		break;
208  	case DIFFREG_ALGO_MYERS_THEN_PATIENCE:
209  		cfg = &diff_config_myers_then_patience;
210  		break;
211  	case DIFFREG_ALGO_PATIENCE:
212  		cfg = &diff_config_patience;
213  		break;
214  	case DIFFREG_ALGO_NONE:
215  		cfg = &diff_config_no_algo;
216  		break;
217  	}
218  
219  	f1 = openfile(file1, &str1, &st1);
220  	f2 = openfile(file2, &str2, &st2);
221  
222  	if (force_text)
223  		diff_flags |= DIFF_FLAG_FORCE_TEXT_DATA;
224  	if (ignore_whitespace)
225  		diff_flags |= DIFF_FLAG_IGNORE_WHITESPACE;
226  	if (show_function_prototypes)
227  		diff_flags |= DIFF_FLAG_SHOW_PROTOTYPES;
228  
229  	rc = diff_atomize_file(&left, cfg, f1, str1, st1.st_size, diff_flags);
230  	if (rc)
231  		goto done;
232  	rc = diff_atomize_file(&right, cfg, f2, str2, st2.st_size, diff_flags);
233  	if (rc)
234  		goto done;
235  
236  	result = diff_main(cfg, &left, &right);
237  #if 0
238  	rc = diff_output_plain(stdout, &info, result);
239  #else
240  	if (edscript)
241  		rc = diff_output_edscript(NULL, stdout, &info, result);
242  	else {
243  		rc = diff_output_unidiff(NULL, stdout, &info, result,
244  		    context_lines);
245  	}
246  #endif
247  done:
248  	diff_result_free(result);
249  	diff_data_free(&left);
250  	diff_data_free(&right);
251  	if (str1)
252  		munmap(str1, st1.st_size);
253  	if (str2)
254  		munmap(str2, st2.st_size);
255  	fclose(f1);
256  	fclose(f2);
257  
258  	return rc;
259  }
260  
261  FILE *
262  openfile(const char *path, char **p, struct stat *st)
263  {
264  	FILE *f = NULL;
265  
266  	f = fopen(path, "r");
267  	if (f == NULL)
268  		err(2, "%s", path);
269  
270  	if (fstat(fileno(f), st) == -1)
271  		err(2, "%s", path);
272  
273  #ifndef DIFF_NO_MMAP
274  	*p = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE, fileno(f), 0);
275  	if (*p == MAP_FAILED)
276  #endif
277  		*p = NULL; /* fall back on file I/O */
278  
279  	return f;
280  }
281