1 /* Common parts for printing diff output */ 2 /* 3 * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de> 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 */ 17 18 #include <ctype.h> 19 #include <errno.h> 20 #include <stdbool.h> 21 #include <stdint.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include <arraylist.h> 28 #include <diff_main.h> 29 #include <diff_output.h> 30 31 #include "diff_internal.h" 32 33 static bool color; 34 static const char *del_code = "31"; 35 static const char *add_code = "32"; 36 37 void 38 diff_output_set_colors(bool _color, 39 const char *_del_code, 40 const char *_add_code) 41 { 42 color = _color; 43 if (_del_code) 44 del_code = _del_code; 45 if (_add_code) 46 add_code = _add_code; 47 } 48 49 static int 50 get_atom_byte(int *ch, struct diff_atom *atom, off_t off) 51 { 52 off_t cur; 53 54 if (atom->at != NULL) { 55 *ch = atom->at[off]; 56 return 0; 57 } 58 59 cur = ftello(atom->root->f); 60 if (cur == -1) 61 return errno; 62 63 if (cur != atom->pos + off && 64 fseeko(atom->root->f, atom->pos + off, SEEK_SET) == -1) 65 return errno; 66 67 *ch = fgetc(atom->root->f); 68 if (*ch == EOF && ferror(atom->root->f)) 69 return errno; 70 71 return 0; 72 } 73 74 #define DIFF_OUTPUT_BUF_SIZE 512 75 76 int 77 diff_output_lines(struct diff_output_info *outinfo, FILE *dest, 78 const char *prefix, struct diff_atom *start_atom, 79 unsigned int count) 80 { 81 struct diff_atom *atom; 82 off_t outoff = 0, *offp; 83 uint8_t *typep; 84 int rc; 85 bool colored; 86 87 if (outinfo && outinfo->line_offsets.len > 0) { 88 unsigned int idx = outinfo->line_offsets.len - 1; 89 outoff = outinfo->line_offsets.head[idx]; 90 } 91 92 if (color) { 93 colored = true; 94 if (*prefix == '-' || *prefix == '<') 95 printf("\033[%sm", del_code); 96 else if (*prefix == '+' || *prefix == '>') 97 printf("\033[%sm", add_code); 98 else 99 colored = false; 100 } else { 101 colored = false; 102 } 103 104 foreach_diff_atom(atom, start_atom, count) { 105 off_t outlen = 0; 106 int i, ch, nbuf = 0; 107 size_t len = atom->len, wlen; 108 char buf[DIFF_OUTPUT_BUF_SIZE + 1 /* '\n' */]; 109 size_t n; 110 111 n = strlcpy(buf, prefix, sizeof(buf)); 112 if (n >= DIFF_OUTPUT_BUF_SIZE) { /* leave room for '\n' */ 113 rc = ENOBUFS; 114 goto out; 115 } 116 nbuf += n; 117 118 if (len) { 119 rc = get_atom_byte(&ch, atom, len - 1); 120 if (rc) 121 goto out; 122 if (ch == '\n') 123 len--; 124 } 125 126 for (i = 0; i < len; i++) { 127 rc = get_atom_byte(&ch, atom, i); 128 if (rc) 129 goto out; 130 if (nbuf >= DIFF_OUTPUT_BUF_SIZE) { 131 wlen = fwrite(buf, 1, nbuf, dest); 132 if (wlen != nbuf) { 133 rc = errno; 134 goto out; 135 } 136 outlen += wlen; 137 nbuf = 0; 138 } 139 buf[nbuf++] = ch; 140 } 141 buf[nbuf++] = '\n'; 142 wlen = fwrite(buf, 1, nbuf, dest); 143 if (wlen != nbuf) { 144 rc = errno; 145 goto out; 146 } 147 outlen += wlen; 148 if (outinfo) { 149 ARRAYLIST_ADD(offp, outinfo->line_offsets); 150 if (offp == NULL) { 151 rc = ENOMEM; 152 goto out; 153 } 154 outoff += outlen; 155 *offp = outoff; 156 ARRAYLIST_ADD(typep, outinfo->line_types); 157 if (typep == NULL) { 158 rc = ENOMEM; 159 goto out; 160 } 161 *typep = *prefix == ' ' ? DIFF_LINE_CONTEXT : 162 *prefix == '-' ? DIFF_LINE_MINUS : 163 *prefix == '+' ? DIFF_LINE_PLUS : DIFF_LINE_NONE; 164 } 165 } 166 167 rc = DIFF_RC_OK; 168 out: 169 if (colored) 170 printf("\033[m"); 171 return rc; 172 } 173 174 int 175 diff_output_chunk_left_version(struct diff_output_info **output_info, 176 FILE *dest, 177 const struct diff_input_info *info, 178 const struct diff_result *result, 179 const struct diff_chunk_context *cc) 180 { 181 int rc, c_idx; 182 struct diff_output_info *outinfo = NULL; 183 184 if (diff_range_empty(&cc->left)) 185 return DIFF_RC_OK; 186 187 if (output_info) { 188 *output_info = diff_output_info_alloc(); 189 if (*output_info == NULL) 190 return ENOMEM; 191 outinfo = *output_info; 192 } 193 194 /* Write out all chunks on the left side. */ 195 for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) { 196 const struct diff_chunk *c = &result->chunks.head[c_idx]; 197 198 if (c->left_count) { 199 rc = diff_output_lines(outinfo, dest, "", 200 c->left_start, c->left_count); 201 if (rc) 202 return rc; 203 } 204 } 205 206 return DIFF_RC_OK; 207 } 208 209 int 210 diff_output_chunk_right_version(struct diff_output_info **output_info, 211 FILE *dest, 212 const struct diff_input_info *info, 213 const struct diff_result *result, 214 const struct diff_chunk_context *cc) 215 { 216 int rc, c_idx; 217 struct diff_output_info *outinfo = NULL; 218 219 if (diff_range_empty(&cc->right)) 220 return DIFF_RC_OK; 221 222 if (output_info) { 223 *output_info = diff_output_info_alloc(); 224 if (*output_info == NULL) 225 return ENOMEM; 226 outinfo = *output_info; 227 } 228 229 /* Write out all chunks on the right side. */ 230 for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) { 231 const struct diff_chunk *c = &result->chunks.head[c_idx]; 232 233 if (c->right_count) { 234 rc = diff_output_lines(outinfo, dest, "", c->right_start, 235 c->right_count); 236 if (rc) 237 return rc; 238 } 239 } 240 241 return DIFF_RC_OK; 242 } 243 244 int 245 diff_output_trailing_newline_msg(struct diff_output_info *outinfo, FILE *dest, 246 const struct diff_chunk *c) 247 { 248 enum diff_chunk_type chunk_type = diff_chunk_type(c); 249 struct diff_atom *atom, *start_atom; 250 unsigned int atom_count; 251 int rc, ch; 252 off_t outoff = 0, *offp; 253 uint8_t *typep; 254 255 256 if (chunk_type == CHUNK_MINUS || chunk_type == CHUNK_SAME) { 257 start_atom = c->left_start; 258 atom_count = c->left_count; 259 } else if (chunk_type == CHUNK_PLUS) { 260 start_atom = c->right_start; 261 atom_count = c->right_count; 262 } else 263 return EINVAL; 264 265 /* Locate the last atom. */ 266 if (atom_count == 0) 267 return EINVAL; 268 atom = &start_atom[atom_count - 1]; 269 270 rc = get_atom_byte(&ch, atom, atom->len - 1); 271 if (rc != DIFF_RC_OK) 272 return rc; 273 274 if (ch != '\n') { 275 if (outinfo && outinfo->line_offsets.len > 0) { 276 unsigned int idx = outinfo->line_offsets.len - 1; 277 outoff = outinfo->line_offsets.head[idx]; 278 } 279 rc = fprintf(dest, "\\ No newline at end of file\n"); 280 if (rc < 0) 281 return errno; 282 if (outinfo) { 283 ARRAYLIST_ADD(offp, outinfo->line_offsets); 284 if (offp == NULL) 285 return ENOMEM; 286 outoff += rc; 287 *offp = outoff; 288 ARRAYLIST_ADD(typep, outinfo->line_types); 289 if (typep == NULL) 290 return ENOMEM; 291 *typep = DIFF_LINE_NONE; 292 } 293 } 294 295 return DIFF_RC_OK; 296 } 297 298 static bool 299 is_function_prototype(char ch) 300 { 301 return (isalpha((unsigned char)ch) || ch == '_' || ch == '$' || 302 ch == '-' || ch == '+'); 303 } 304 305 #define begins_with(s, pre) (strncmp(s, pre, sizeof(pre)-1) == 0) 306 307 int 308 diff_output_match_function_prototype(char *prototype, size_t prototype_size, 309 int *last_prototype_idx, const struct diff_result *result, 310 int chunk_start_line) 311 { 312 struct diff_atom *start_atom, *atom; 313 const struct diff_data *data; 314 char buf[DIFF_FUNCTION_CONTEXT_SIZE]; 315 const char *state = NULL; 316 int rc, i, ch; 317 318 if (result->left->atoms.len > 0 && chunk_start_line > 0) { 319 data = result->left; 320 start_atom = &data->atoms.head[chunk_start_line - 1]; 321 } else 322 return DIFF_RC_OK; 323 324 diff_data_foreach_atom_backwards_from(start_atom, atom, data) { 325 int atom_idx = diff_atom_root_idx(data, atom); 326 if (atom_idx < *last_prototype_idx) 327 break; 328 rc = get_atom_byte(&ch, atom, 0); 329 if (rc) 330 return rc; 331 buf[0] = ch; 332 if (!is_function_prototype(buf[0])) 333 continue; 334 for (i = 1; i < atom->len && i < sizeof(buf) - 1; i++) { 335 rc = get_atom_byte(&ch, atom, i); 336 if (rc) 337 return rc; 338 if (ch == '\n') 339 break; 340 buf[i] = ch; 341 } 342 buf[i] = '\0'; 343 if (begins_with(buf, "private:")) { 344 if (!state) 345 state = " (private)"; 346 } else if (begins_with(buf, "protected:")) { 347 if (!state) 348 state = " (protected)"; 349 } else if (begins_with(buf, "public:")) { 350 if (!state) 351 state = " (public)"; 352 } else { 353 if (state) /* don't care about truncation */ 354 strlcat(buf, state, sizeof(buf)); 355 strlcpy(prototype, buf, prototype_size); 356 break; 357 } 358 } 359 360 *last_prototype_idx = diff_atom_root_idx(data, start_atom); 361 return DIFF_RC_OK; 362 } 363 364 struct diff_output_info * 365 diff_output_info_alloc(void) 366 { 367 struct diff_output_info *output_info; 368 off_t *offp; 369 uint8_t *typep; 370 371 output_info = malloc(sizeof(*output_info)); 372 if (output_info != NULL) { 373 ARRAYLIST_INIT(output_info->line_offsets, 128); 374 ARRAYLIST_ADD(offp, output_info->line_offsets); 375 if (offp == NULL) { 376 diff_output_info_free(output_info); 377 return NULL; 378 } 379 *offp = 0; 380 ARRAYLIST_INIT(output_info->line_types, 128); 381 ARRAYLIST_ADD(typep, output_info->line_types); 382 if (typep == NULL) { 383 diff_output_info_free(output_info); 384 return NULL; 385 } 386 *typep = DIFF_LINE_NONE; 387 } 388 return output_info; 389 } 390 391 void 392 diff_output_info_free(struct diff_output_info *output_info) 393 { 394 ARRAYLIST_FREE(output_info->line_offsets); 395 ARRAYLIST_FREE(output_info->line_types); 396 free(output_info); 397 } 398 399 const char * 400 diff_output_get_label_left(const struct diff_input_info *info) 401 { 402 if (info->flags & DIFF_INPUT_LEFT_NONEXISTENT) 403 return "/dev/null"; 404 405 return info->left_path ? info->left_path : "a"; 406 } 407 408 const char * 409 diff_output_get_label_right(const struct diff_input_info *info) 410 { 411 if (info->flags & DIFF_INPUT_RIGHT_NONEXISTENT) 412 return "/dev/null"; 413 414 return info->right_path ? info->right_path : "b"; 415 } 416