Blob


1 /*
2 * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de>
3 * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
18 #include <sys/mman.h>
19 #include <sys/stat.h>
21 #include <errno.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #include "got_compat.h"
28 #include "got_object.h"
29 #include "got_opentemp.h"
30 #include "got_error.h"
32 #include "got_lib_diff.h"
34 const struct diff_algo_config myers_then_patience;
35 const struct diff_algo_config myers_then_myers_divide;
36 const struct diff_algo_config patience;
37 const struct diff_algo_config myers_divide;
39 const struct diff_algo_config myers_then_patience = (struct diff_algo_config){
40 .impl = diff_algo_myers,
41 .permitted_state_size = 1024 * 1024 * sizeof(int),
42 .fallback_algo = &patience,
43 };
45 const struct diff_algo_config myers_then_myers_divide =
46 (struct diff_algo_config){
47 .impl = diff_algo_myers,
48 .permitted_state_size = 1024 * 1024 * sizeof(int),
49 .fallback_algo = &myers_divide,
50 };
52 const struct diff_algo_config patience = (struct diff_algo_config){
53 .impl = diff_algo_patience,
54 /* After subdivision, do Patience again: */
55 .inner_algo = &patience,
56 /* If subdivision failed, do Myers Divide et Impera: */
57 .fallback_algo = &myers_then_myers_divide,
58 };
60 const struct diff_algo_config myers_divide = (struct diff_algo_config){
61 .impl = diff_algo_myers_divide,
62 /* When division succeeded, start from the top: */
63 .inner_algo = &myers_then_myers_divide,
64 /* (fallback_algo = NULL implies diff_algo_none). */
65 };
67 /* If the state for a forward-Myers is small enough, use Myers, otherwise first
68 * do a Myers-divide. */
69 const struct diff_config diff_config_myers_then_myers_divide = {
70 .atomize_func = diff_atomize_text_by_line,
71 .algo = &myers_then_myers_divide,
72 };
74 /* If the state for a forward-Myers is small enough, use Myers, otherwise first
75 * do a Patience. */
76 const struct diff_config diff_config_myers_then_patience = {
77 .atomize_func = diff_atomize_text_by_line,
78 .algo = &myers_then_patience,
79 };
81 /* Directly force Patience as a first divider of the source file. */
82 const struct diff_config diff_config_patience = {
83 .atomize_func = diff_atomize_text_by_line,
84 .algo = &patience,
85 };
87 /* Directly force Patience as a first divider of the source file. */
88 const struct diff_config diff_config_no_algo = {
89 .atomize_func = diff_atomize_text_by_line,
90 };
92 const struct got_error *
93 got_diffreg_close(char *p1, size_t size1, char *p2, size_t size2)
94 {
95 const struct got_error *err = NULL;
97 if (p1 && munmap(p1, size1) == -1 && err == NULL)
98 err = got_error_from_errno("munmap");
99 if (p2 && munmap(p2, size2) == -1 && err == NULL)
100 err = got_error_from_errno("munmap");
101 return err;
104 const struct got_error *
105 got_diff_get_config(struct diff_config **cfg,
106 enum got_diff_algorithm algorithm,
107 diff_atomize_func_t atomize_func, void *atomize_func_data)
109 *cfg = calloc(1, sizeof(**cfg));
110 if (*cfg == NULL)
111 return got_error_from_errno("calloc");
113 switch (algorithm) {
114 case GOT_DIFF_ALGORITHM_PATIENCE:
115 (*cfg)->algo = &patience;
116 break;
117 case GOT_DIFF_ALGORITHM_MYERS:
118 (*cfg)->algo = &myers_then_myers_divide;
119 break;
120 default:
121 return got_error_msg(GOT_ERR_NOT_IMPL, "bad diff algorithm");
124 if (atomize_func) {
125 (*cfg)->atomize_func = atomize_func;
126 (*cfg)->atomize_func_data = atomize_func_data;
127 } else
128 (*cfg)->atomize_func = diff_atomize_text_by_line;
130 (*cfg)->max_recursion_depth = 0; /* use default recursion depth */
132 return NULL;
135 const struct got_error *
136 got_diff_prepare_file(FILE *f, char **p, size_t *size,
137 struct diff_data *diff_data, const struct diff_config *cfg,
138 int ignore_whitespace, int force_text_diff)
140 const struct got_error *err = NULL;
141 struct stat st;
142 int diff_flags = 0, rc;
144 *size = 0;
146 diff_flags |= DIFF_FLAG_SHOW_PROTOTYPES;
147 if (ignore_whitespace)
148 diff_flags |= DIFF_FLAG_IGNORE_WHITESPACE;
149 if (force_text_diff)
150 diff_flags |= DIFF_FLAG_FORCE_TEXT_DATA;
152 if (fstat(fileno(f), &st) == -1) {
153 err = got_error_from_errno("fstat");
154 goto done;
156 #ifndef GOT_DIFF_NO_MMAP
157 *p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE,
158 fileno(f), 0);
159 if (*p == MAP_FAILED)
160 #endif
161 *p = NULL; /* fall back on file I/O */
163 rc = diff_atomize_file(diff_data, cfg, f, *p, st.st_size, diff_flags);
164 if (rc) {
165 err = got_error_set_errno(rc, "diff_atomize_file");
166 goto done;
168 done:
169 if (err)
170 diff_data_free(diff_data);
171 else
172 *size = st.st_size;
173 return err;
176 const struct got_error *
177 got_diffreg(struct got_diffreg_result **diffreg_result, FILE *f1, FILE *f2,
178 enum got_diff_algorithm algorithm, int ignore_whitespace,
179 int force_text_diff)
181 const struct got_error *err = NULL;
182 struct diff_config *cfg = NULL;
183 char *p1 = NULL, *p2 = NULL;
184 size_t size1, size2;
185 struct diff_data d_left, d_right;
186 struct diff_data *left, *right;
187 struct diff_result *diff_result;
189 if (diffreg_result) {
190 *diffreg_result = calloc(1, sizeof(**diffreg_result));
191 if (*diffreg_result == NULL)
192 return got_error_from_errno("calloc");
193 left = &(*diffreg_result)->left;
194 right = &(*diffreg_result)->right;
195 } else {
196 memset(&d_left, 0, sizeof(d_left));
197 memset(&d_right, 0, sizeof(d_right));
198 left = &d_left;
199 right = &d_right;
202 err = got_diff_get_config(&cfg, algorithm, NULL, NULL);
203 if (err)
204 goto done;
206 err = got_diff_prepare_file(f1, &p1, &size1, left, cfg,
207 ignore_whitespace, force_text_diff);
208 if (err)
209 goto done;
211 err = got_diff_prepare_file(f2, &p2, &size2, right, cfg,
212 ignore_whitespace, force_text_diff);
213 if (err)
214 goto done;
216 diff_result = diff_main(cfg, left, right);
217 if (diff_result == NULL) {
218 err = got_error_set_errno(ENOMEM, "malloc");
219 goto done;
221 if (diff_result->rc != DIFF_RC_OK) {
222 err = got_error_set_errno(diff_result->rc, "diff");
223 goto done;
226 if (diffreg_result) {
227 (*diffreg_result)->result = diff_result;
228 (*diffreg_result)->map1 = p1;
229 (*diffreg_result)->size1 = size1;
230 (*diffreg_result)->map2 = p2;
231 (*diffreg_result)->size2 = size2;
233 done:
234 free(cfg);
235 if (diffreg_result == NULL) {
236 diff_data_free(left);
237 diff_data_free(right);
239 if (err) {
240 got_diffreg_close(p1, size1, p2, size2);
241 if (diffreg_result) {
242 diff_data_free(left);
243 diff_data_free(right);
244 free(*diffreg_result);
245 *diffreg_result = NULL;
249 return err;
252 const struct got_error *
253 got_diffreg_output(off_t **line_offsets, size_t *nlines,
254 struct got_diffreg_result *diff_result, int f1_exists, int f2_exists,
255 const char *path1, const char *path2,
256 enum got_diff_output_format output_format, int context_lines, FILE *outfile)
258 struct diff_input_info info = {
259 .left_path = path1,
260 .right_path = path2,
261 .flags = 0,
262 };
263 int rc;
264 struct diff_output_info *output_info;
266 if (!f1_exists)
267 info.flags |= DIFF_INPUT_LEFT_NONEXISTENT;
268 if (!f2_exists)
269 info.flags |= DIFF_INPUT_RIGHT_NONEXISTENT;
271 switch (output_format) {
272 case GOT_DIFF_OUTPUT_UNIDIFF:
273 rc = diff_output_unidiff(
274 line_offsets ? &output_info : NULL, outfile, &info,
275 diff_result->result, context_lines);
276 if (rc != DIFF_RC_OK)
277 return got_error_set_errno(rc, "diff_output_unidiff");
278 break;
279 case GOT_DIFF_OUTPUT_EDSCRIPT:
280 rc = diff_output_edscript(line_offsets ? &output_info : NULL,
281 outfile, &info, diff_result->result);
282 if (rc != DIFF_RC_OK)
283 return got_error_set_errno(rc, "diff_output_edscript");
284 break;
288 if (line_offsets && *line_offsets) {
289 if (output_info->line_offsets.len > 0) {
290 off_t prev_offset = 0, *p, *o;
291 int i, len;
292 if (*nlines > 0) {
293 prev_offset = (*line_offsets)[*nlines - 1];
294 /*
295 * First line offset is always zero. Skip it
296 * when appending to a pre-populated array.
297 */
298 o = &output_info->line_offsets.head[1];
299 len = output_info->line_offsets.len - 1;
300 } else {
301 o = &output_info->line_offsets.head[0];
302 len = output_info->line_offsets.len;
304 p = reallocarray(*line_offsets, *nlines + len,
305 sizeof(off_t));
306 if (p == NULL)
307 return got_error_from_errno("calloc");
308 for (i = 0; i < len; i++)
309 p[*nlines + i] = o[i] + prev_offset;
310 *line_offsets = p;
311 *nlines += len;
313 diff_output_info_free(output_info);
316 return NULL;
319 const struct got_error *
320 got_diffreg_result_free(struct got_diffreg_result *diffreg_result)
322 const struct got_error *err;
324 diff_result_free(diffreg_result->result);
325 diff_data_free(&diffreg_result->left);
326 diff_data_free(&diffreg_result->right);
327 err = got_diffreg_close(diffreg_result->map1, diffreg_result->size1,
328 diffreg_result->map2, diffreg_result->size2);
329 free(diffreg_result);
330 return err;
333 const struct got_error *
334 got_diffreg_result_free_left(struct got_diffreg_result *diffreg_result)
336 diff_data_free(&diffreg_result->left);
337 memset(&diffreg_result->left, 0, sizeof(diffreg_result->left));
338 return got_diffreg_close(diffreg_result->map1, diffreg_result->size1,
339 NULL, 0);
342 const struct got_error *
343 got_diffreg_result_free_right(struct got_diffreg_result *diffreg_result)
345 diff_data_free(&diffreg_result->right);
346 memset(&diffreg_result->right, 0, sizeof(diffreg_result->right));
347 return got_diffreg_close(NULL, 0, diffreg_result->map2,
348 diffreg_result->size2);