raw
vtools_genesis          1 #define GDIFF_MAIN
vtools_genesis 2
vtools_genesis 3 #include "diff.h"
vtools_genesis 4 #include <dirname.h>
vtools_genesis 5 #include <error.h>
vtools_genesis 6 #include <filenamecat.h>
vtools_genesis 7 #include <progname.h>
vtools_genesis 8 #include <xalloc.h>
vtools_genesis 9 #include <getopt.h>
vtools_genesis 10 #include <fcntl.h>
vtools_genesis 11 #include <stdlib.h>
vtools_genesis 12 #include <inttypes.h>
vtools_genesis 13 #include <filetype.h>
vtools_genesis 14
vtools_genesis 15 #define STREQ(a, b) (strcmp (a, b) == 0)
vtools_genesis 16 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
vtools_genesis 17
vtools_genesis 18 static int compare_files(struct comparison const *, char const *, char const *);
vtools_genesis 19
vtools_genesis 20 static void try_help(char const *, char const *);
vtools_genesis 21
vtools_genesis 22 static void check_stdout(void);
vtools_genesis 23
vtools_genesis 24 static char const shortopts[] = "0123456789aU:dHL:qurN";
vtools_genesis 25
vtools_genesis 26 /* Return a string containing the command options with which diff was
vtools_genesis 27 invoked. Spaces appear between what were separate |argv|-elements.
vtools_genesis 28 There is a space at the beginning but none at the end. If there
vtools_genesis 29 were no options, the result is an empty string.
vtools_genesis 30
vtools_genesis 31 Arguments: |optionvec|, a vector containing separate
vtools_genesis 32 |argv|-elements, and |count|, the length of that vector.
vtools_genesis 33
vtools_genesis 34 todo phf: Implicitly add the -uNr since that's the defaults, and
vtools_genesis 35 this combination of flags is what the original vdiff has.*/
vtools_genesis 36
vtools_genesis 37 static char *
vtools_genesis 38 option_list(char **optionvec, int count) {
vtools_genesis 39 char prefix[] = " -uNr";
vtools_genesis 40 int i;
vtools_genesis 41 size_t size = 1 + sizeof prefix;
vtools_genesis 42 char *result;
vtools_genesis 43 char *p;
vtools_genesis 44
vtools_genesis 45 for (i = 0; i < count; i++)
vtools_genesis 46 size += strlen(optionvec[i]);
vtools_genesis 47
vtools_genesis 48 p = result = xmalloc(size);
vtools_genesis 49 p = stpncpy(p, prefix, sizeof prefix - 1);
vtools_genesis 50
vtools_genesis 51 for (i = 0; i < count; i++) {
vtools_genesis 52 *p++ = ' ';
vtools_genesis 53 p = stpncpy(p, optionvec[i], strlen(optionvec[i])-1);
vtools_genesis 54 }
vtools_genesis 55
vtools_genesis 56 *p = '\0';
vtools_genesis 57 return result;
vtools_genesis 58 }
vtools_genesis 59
vtools_genesis 60 int
vtools_genesis 61 main(int argc, char **argv) {
vtools_genesis 62 int exit_status = EXIT_SUCCESS;
vtools_genesis 63 int c;
vtools_genesis 64 int prev = -1;
vtools_genesis 65 lin ocontext = -1;
vtools_genesis 66 bool explicit_context = false;
vtools_genesis 67 char const *from_file = NULL;
vtools_genesis 68 char const *to_file = NULL;
vtools_genesis 69 uintmax_t numval;
vtools_genesis 70 char *numend;
vtools_genesis 71 set_program_name(argv[0]);
vdiff_keccak 72 adainit();
vtools_genesis 73
vtools_genesis 74 /* Decode the options. */
vtools_genesis 75
vtools_genesis 76 while ((c = getopt(argc, argv, shortopts)) != -1) {
vtools_genesis 77 switch (c) {
vtools_genesis 78 case 0:
vtools_genesis 79 break;
vtools_genesis 80
vtools_genesis 81 case '0':
vtools_genesis 82 case '1':
vtools_genesis 83 case '2':
vtools_genesis 84 case '3':
vtools_genesis 85 case '4':
vtools_genesis 86 case '5':
vtools_genesis 87 case '6':
vtools_genesis 88 case '7':
vtools_genesis 89 case '8':
vtools_genesis 90 case '9':
vtools_genesis 91 ocontext = (!ISDIGIT (prev)
vtools_genesis 92 ? c - '0'
vtools_genesis 93 : (ocontext - (c - '0' <= CONTEXT_MAX % 10)
vtools_genesis 94 < CONTEXT_MAX / 10)
vtools_genesis 95 ? 10 * ocontext + (c - '0')
vtools_genesis 96 : CONTEXT_MAX);
vtools_genesis 97 break;
vtools_genesis 98
vtools_genesis 99 case 'a':
vtools_genesis 100 text = true;
vtools_genesis 101 break;
vtools_genesis 102
vtools_genesis 103 case 'U': {
vtools_genesis 104 if (optarg) {
vtools_genesis 105 numval = strtoumax(optarg, &numend, 10);
vtools_genesis 106 if (*numend)
vtools_genesis 107 try_help("invalid context length '%s'", optarg);
vtools_genesis 108 if (CONTEXT_MAX < numval)
vtools_genesis 109 numval = CONTEXT_MAX;
vtools_genesis 110 } else
vtools_genesis 111 numval = 3;
vtools_genesis 112
vtools_genesis 113 if (context < numval)
vtools_genesis 114 context = numval;
vtools_genesis 115 explicit_context = true;
vtools_genesis 116 }
vtools_genesis 117 break;
vtools_genesis 118
vtools_genesis 119 case 'd':
vtools_genesis 120 minimal = true;
vtools_genesis 121 break;
vtools_genesis 122
vtools_genesis 123 case 'H':
vtools_genesis 124 speed_large_files = true;
vtools_genesis 125 break;
vtools_genesis 126
vtools_genesis 127 case 'L':
vtools_genesis 128 if (!file_label[0])
vtools_genesis 129 file_label[0] = optarg;
vtools_genesis 130 else if (!file_label[1])
vtools_genesis 131 file_label[1] = optarg;
vtools_genesis 132 else
vtools_genesis 133 fatal("too many file label options");
vtools_genesis 134 break;
vtools_genesis 135
vtools_genesis 136 case 'q':
vtools_genesis 137 brief = true;
vtools_genesis 138 break;
vtools_genesis 139
vtools_genesis 140 case 'u':
vtools_genesis 141 case 'r':
vtools_genesis 142 case 'N':
vtools_genesis 143 /* compat */
vtools_genesis 144 break;
vtools_genesis 145
vtools_genesis 146 default:
vtools_genesis 147 try_help(NULL, NULL);
vtools_genesis 148 }
vtools_genesis 149 prev = c;
vtools_genesis 150 }
vtools_genesis 151
vtools_genesis 152 if (context < 3)
vtools_genesis 153 context = 3;
vtools_genesis 154 suppress_blank_empty = false;
vtools_genesis 155
vtools_genesis 156 if (0 <= ocontext
vtools_genesis 157 && (context < ocontext
vtools_genesis 158 || (ocontext < context && !explicit_context)))
vtools_genesis 159 context = ocontext;
vtools_genesis 160
vtools_genesis 161 /* Make the horizon at least as large as the context, so that
vtools_genesis 162 |shift_boundaries| has more freedom to shift the first and last
vtools_genesis 163 hunks. */
vtools_genesis 164 if (horizon_lines < context)
vtools_genesis 165 horizon_lines = context;
vtools_genesis 166
vtools_genesis 167 files_can_be_treated_as_binary = false;
vtools_genesis 168
vtools_genesis 169 switch_string = option_list(argv + 1, optind - 1);
vtools_genesis 170
vtools_genesis 171 if (from_file) {
vtools_genesis 172 if (to_file)
vtools_genesis 173 fatal("--from-file and --to-file both specified");
vtools_genesis 174 else
vtools_genesis 175 for (; optind < argc; optind++) {
vtools_genesis 176 int status = compare_files(NULL, from_file, argv[optind]);
vtools_genesis 177 if (exit_status < status)
vtools_genesis 178 exit_status = status;
vtools_genesis 179 }
vtools_genesis 180 } else {
vtools_genesis 181 if (to_file)
vtools_genesis 182 for (; optind < argc; optind++) {
vtools_genesis 183 int status = compare_files(NULL, argv[optind], to_file);
vtools_genesis 184 if (exit_status < status)
vtools_genesis 185 exit_status = status;
vtools_genesis 186 }
vtools_genesis 187 else {
vtools_genesis 188 if (argc - optind != 2) {
vtools_genesis 189 if (argc - optind < 2)
vtools_genesis 190 try_help("missing operand after '%s'", argv[argc - 1]);
vtools_genesis 191 else
vtools_genesis 192 try_help("extra operand '%s'", argv[optind + 2]);
vtools_genesis 193 }
vtools_genesis 194
vtools_genesis 195 exit_status = compare_files(NULL, argv[optind], argv[optind + 1]);
vtools_genesis 196 }
vtools_genesis 197 }
vtools_genesis 198
vdiff_keccak 199 adafinal();
vtools_genesis 200 check_stdout();
vtools_genesis 201 exit(exit_status);
vtools_genesis 202 return exit_status;
vtools_genesis 203 }
vtools_genesis 204
vtools_genesis 205 static void
vtools_genesis 206 try_help(char const *reason_msgid, char const *operand) {
vtools_genesis 207 if (reason_msgid)
vtools_genesis 208 error(0, 0, reason_msgid, operand);
vtools_genesis 209 exit(EXIT_TROUBLE);
vtools_genesis 210 }
vtools_genesis 211
vtools_genesis 212 static void
vtools_genesis 213 check_stdout(void) {
vtools_genesis 214 if (ferror(stdout))
vtools_genesis 215 fatal("write failed");
vtools_genesis 216 else if (fclose(stdout) != 0)
vtools_genesis 217 pfatal_with_name("standard output");
vtools_genesis 218 }
vtools_genesis 219
vtools_genesis 220 /* Compare two files (or dirs) with parent comparison |parent| and
vtools_genesis 221 names |name0| and |name1|. (If |parent| is null, then the first
vtools_genesis 222 name is just |name0|, etc.) This is self-contained; it opens the
vtools_genesis 223 files and closes them.
vtools_genesis 224
vtools_genesis 225 Value is |EXIT_SUCCESS| if files are the same, |EXIT_FAILURE| if
vtools_genesis 226 different, |EXIT_TROUBLE| if there is a problem opening them. */
vtools_genesis 227
vtools_genesis 228 static int
vtools_genesis 229 compare_files(struct comparison const *parent,
vtools_genesis 230 char const *name0,
vtools_genesis 231 char const *name1) {
vtools_genesis 232 struct comparison cmp;
vtools_genesis 233 #define DIR_P(f) (S_ISDIR (cmp.file[f].stat.st_mode) != 0)
vtools_genesis 234 register int f;
vtools_genesis 235 int status = EXIT_SUCCESS;
vtools_genesis 236 bool same_files;
vtools_genesis 237 char *free0;
vtools_genesis 238 char *free1;
vtools_genesis 239
vtools_genesis 240 memset (cmp.file, 0, sizeof cmp.file);
vtools_genesis 241 cmp.parent = parent;
vtools_genesis 242
vtools_genesis 243 /* |cmp.file[f].desc| markers */
vtools_genesis 244 #define NONEXISTENT (-1) /* nonexistent file */
vtools_genesis 245 #define UNOPENED (-2) /* unopened file (e.g. directory) */
vtools_genesis 246 #define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded |errno| value */
vtools_genesis 247 #define ERRNO_DECODE(desc) (-3 - (desc)) /* inverse of |ERRNO_ENCODE| */
vtools_genesis 248
vtools_genesis 249 cmp.file[0].desc = name0 ? UNOPENED : NONEXISTENT;
vtools_genesis 250 cmp.file[1].desc = name1 ? UNOPENED : NONEXISTENT;
vtools_genesis 251
vtools_genesis 252 /* Now record the full name of each file, including nonexistent ones. */
vtools_genesis 253
vtools_genesis 254 if (!name0)
vtools_genesis 255 name0 = name1;
vtools_genesis 256 if (!name1)
vtools_genesis 257 name1 = name0;
vtools_genesis 258
vtools_genesis 259 if (!parent) {
vtools_genesis 260 free0 = NULL;
vtools_genesis 261 free1 = NULL;
vtools_genesis 262 cmp.file[0].name = name0;
vtools_genesis 263 cmp.file[1].name = name1;
vtools_genesis 264 } else {
vtools_genesis 265 cmp.file[0].name = free0
vtools_genesis 266 = file_name_concat(parent->file[0].name, name0, NULL);
vtools_genesis 267 cmp.file[1].name = free1
vtools_genesis 268 = file_name_concat(parent->file[1].name, name1, NULL);
vtools_genesis 269 }
vtools_genesis 270
vtools_genesis 271 /* Stat the files. */
vtools_genesis 272
vtools_genesis 273 for (f = 0; f < 2; f++) {
vtools_genesis 274 if (cmp.file[f].desc != NONEXISTENT) {
vtools_genesis 275 if (f && file_name_cmp(cmp.file[f].name, cmp.file[0].name) == 0) {
vtools_genesis 276 cmp.file[f].desc = cmp.file[0].desc;
vtools_genesis 277 cmp.file[f].stat = cmp.file[0].stat;
vtools_genesis 278 } else if (STREQ (cmp.file[f].name, "-")) {
vtools_genesis 279 cmp.file[f].desc = STDIN_FILENO;
vtools_genesis 280 if (fstat(STDIN_FILENO, &cmp.file[f].stat) != 0)
vtools_genesis 281 cmp.file[f].desc = ERRNO_ENCODE (errno);
vtools_genesis 282 else {
vtools_genesis 283 if (S_ISREG (cmp.file[f].stat.st_mode)) {
vtools_genesis 284 off_t pos = lseek(STDIN_FILENO, 0, SEEK_CUR);
vtools_genesis 285 if (pos < 0)
vtools_genesis 286 cmp.file[f].desc = ERRNO_ENCODE (errno);
vtools_genesis 287 else
vtools_genesis 288 cmp.file[f].stat.st_size =
vtools_genesis 289 MAX (0, cmp.file[f].stat.st_size - pos);
vtools_genesis 290 }
vtools_genesis 291 }
vtools_genesis 292 } else if (stat(cmp.file[f].name, &cmp.file[f].stat) != 0)
vtools_genesis 293 cmp.file[f].desc = ERRNO_ENCODE (errno);
vtools_genesis 294 }
vtools_genesis 295 }
vtools_genesis 296
vtools_genesis 297 /* Mark files as nonexistent as needed for |-N| and |-P|, if they
vtools_genesis 298 are inaccessible empty regular files (the kind of files that
vtools_genesis 299 |patch| creates to indicate nonexistent backups), or if they
vtools_genesis 300 are top-level files that do not exist but their counterparts do
vtools_genesis 301 exist. */
vtools_genesis 302 for (f = 0; f < 2; f++)
vtools_genesis 303 if (cmp.file[f].desc == UNOPENED
vtools_genesis 304 ? (S_ISREG (cmp.file[f].stat.st_mode)
vtools_genesis 305 && !(cmp.file[f].stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))
vtools_genesis 306 && cmp.file[f].stat.st_size == 0)
vtools_genesis 307 : ((cmp.file[f].desc == ERRNO_ENCODE (ENOENT)
vtools_genesis 308 || cmp.file[f].desc == ERRNO_ENCODE (EBADF))
vtools_genesis 309 && !parent
vtools_genesis 310 && (cmp.file[1 - f].desc == UNOPENED
vtools_genesis 311 || cmp.file[1 - f].desc == STDIN_FILENO)))
vtools_genesis 312 cmp.file[f].desc = NONEXISTENT;
vtools_genesis 313
vtools_genesis 314 for (f = 0; f < 2; f++)
vtools_genesis 315 if (cmp.file[f].desc == NONEXISTENT) {
vtools_genesis 316 memset (&cmp.file[f].stat, 0, sizeof cmp.file[f].stat);
vtools_genesis 317 cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode;
vtools_genesis 318 }
vtools_genesis 319
vtools_genesis 320 for (f = 0; f < 2; f++) {
vtools_genesis 321 int e = ERRNO_DECODE (cmp.file[f].desc);
vtools_genesis 322 if (0 <= e) {
vtools_genesis 323 errno = e;
vtools_genesis 324 perror_with_name(cmp.file[f].name);
vtools_genesis 325 status = EXIT_TROUBLE;
vtools_genesis 326 }
vtools_genesis 327 }
vtools_genesis 328
vtools_genesis 329 if (status == EXIT_SUCCESS && !parent && DIR_P (0) != DIR_P (1)) {
vtools_genesis 330 /* If one is a directory, and it was specified in the command
vtools_genesis 331 line, use the file in that dir with the other file's
vtools_genesis 332 basename. */
vtools_genesis 333
vtools_genesis 334 int fnm_arg = DIR_P (0);
vtools_genesis 335 int dir_arg = 1 - fnm_arg;
vtools_genesis 336 char const *fnm = cmp.file[fnm_arg].name;
vtools_genesis 337 char const *dir = cmp.file[dir_arg].name;
vtools_genesis 338 char const *filename = cmp.file[dir_arg].name = free0
vtools_genesis 339 = find_dir_file_pathname(dir, last_component(fnm));
vtools_genesis 340
vtools_genesis 341 if (STREQ (fnm, "-"))
vtools_genesis 342 fatal("cannot compare '-' to a directory");
vtools_genesis 343
vtools_genesis 344 if (stat(filename, &cmp.file[dir_arg].stat)
vtools_genesis 345 != 0) {
vtools_genesis 346 perror_with_name(filename);
vtools_genesis 347 status = EXIT_TROUBLE;
vtools_genesis 348 }
vtools_genesis 349 }
vtools_genesis 350
vtools_genesis 351 if (status != EXIT_SUCCESS) {
vtools_genesis 352 /* One of the files should exist but does not. */
vtools_genesis 353 } else if (cmp.file[0].desc == NONEXISTENT
vtools_genesis 354 && cmp.file[1].desc == NONEXISTENT) {
vtools_genesis 355 /* Neither file "exists", so there's nothing to compare. */
vtools_genesis 356 } else if ((same_files
vtools_genesis 357 = (cmp.file[0].desc != NONEXISTENT
vtools_genesis 358 && cmp.file[1].desc != NONEXISTENT
vtools_genesis 359 && 0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat)
vtools_genesis 360 && same_file_attributes (&cmp.file[0].stat,
vtools_genesis 361 &cmp.file[1].stat)))) {
vtools_genesis 362 /* The two named files are actually the same physical file. We
vtools_genesis 363 know they are identical without actually reading them. */
vtools_genesis 364 } else if (DIR_P (0) & DIR_P (1)) {
vtools_genesis 365 /* If both are directories, compare the files in them. */
vtools_genesis 366
vtools_genesis 367 status = diff_dirs(&cmp, compare_files);
vtools_genesis 368 } else if ((DIR_P (0) | DIR_P (1))
vtools_genesis 369 || (parent
vtools_genesis 370 && !((S_ISREG (cmp.file[0].stat.st_mode)
vtools_genesis 371 || S_ISLNK (cmp.file[0].stat.st_mode))
vtools_genesis 372 && (S_ISREG (cmp.file[1].stat.st_mode)
vtools_genesis 373 || S_ISLNK (cmp.file[1].stat.st_mode))))) {
vtools_genesis 374 if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT) {
vtools_genesis 375 /* We have a subdirectory that exists only in one directory. */
vtools_genesis 376
vtools_genesis 377 status = diff_dirs(&cmp, compare_files);
vtools_genesis 378 } else {
vtools_genesis 379 /* We have two files that are not to be compared. */
vtools_genesis 380
vtools_genesis 381 message5("File %s is a %s while file %s is a %s\n",
vtools_genesis 382 file_label[0] ? file_label[0] : cmp.file[0].name,
vtools_genesis 383 file_type(&cmp.file[0].stat),
vtools_genesis 384 file_label[1] ? file_label[1] : cmp.file[1].name,
vtools_genesis 385 file_type(&cmp.file[1].stat));
vtools_genesis 386
vtools_genesis 387 /* This is a difference. */
vtools_genesis 388 status = EXIT_FAILURE;
vtools_genesis 389 }
vtools_genesis 390 } else if (S_ISLNK (cmp.file[0].stat.st_mode)
vtools_genesis 391 || S_ISLNK (cmp.file[1].stat.st_mode)) {
vtools_genesis 392 /* We get here only if we use |lstat()|, not |stat()|. */
vtools_genesis 393 status = EXIT_FAILURE;
vtools_genesis 394 } else if (files_can_be_treated_as_binary
vtools_genesis 395 && S_ISREG (cmp.file[0].stat.st_mode)
vtools_genesis 396 && S_ISREG (cmp.file[1].stat.st_mode)
vtools_genesis 397 && cmp.file[0].stat.st_size != cmp.file[1].stat.st_size
vtools_genesis 398 && 0 < cmp.file[0].stat.st_size
vtools_genesis 399 && 0 < cmp.file[1].stat.st_size) {
vtools_genesis 400 message("Files %s and %s differ\n",
vtools_genesis 401 file_label[0] ? file_label[0] : cmp.file[0].name,
vtools_genesis 402 file_label[1] ? file_label[1] : cmp.file[1].name);
vtools_genesis 403 status = EXIT_FAILURE;
vtools_genesis 404 } else {
vtools_genesis 405 /* Both exist and neither is a directory. */
vtools_genesis 406
vtools_genesis 407 /* Open the files and record their descriptors. */
vtools_genesis 408
vtools_genesis 409 if (cmp.file[0].desc == UNOPENED)
vtools_genesis 410 if ((cmp.file[0].desc = open(cmp.file[0].name, O_RDONLY, 0)) < 0) {
vtools_genesis 411 perror_with_name(cmp.file[0].name);
vtools_genesis 412 status = EXIT_TROUBLE;
vtools_genesis 413 }
vtools_genesis 414 if (cmp.file[1].desc == UNOPENED) {
vtools_genesis 415 if (same_files)
vtools_genesis 416 cmp.file[1].desc = cmp.file[0].desc;
vtools_genesis 417 else if ((cmp.file[1].desc = open(cmp.file[1].name, O_RDONLY, 0)) < 0) {
vtools_genesis 418 perror_with_name(cmp.file[1].name);
vtools_genesis 419 status = EXIT_TROUBLE;
vtools_genesis 420 }
vtools_genesis 421 }
vtools_genesis 422
vtools_genesis 423 /* Compare the files, if no error was found. */
vtools_genesis 424
vtools_genesis 425 if (status == EXIT_SUCCESS) {
vtools_genesis 426 status = diff_2_files(&cmp);
vtools_genesis 427 }
vtools_genesis 428
vtools_genesis 429 /* Close the file descriptors. */
vtools_genesis 430
vtools_genesis 431 if (0 <= cmp.file[0].desc && close(cmp.file[0].desc) != 0) {
vtools_genesis 432 perror_with_name(cmp.file[0].name);
vtools_genesis 433 status = EXIT_TROUBLE;
vtools_genesis 434 }
vtools_genesis 435 if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc
vtools_genesis 436 && close(cmp.file[1].desc) != 0) {
vtools_genesis 437 perror_with_name(cmp.file[1].name);
vtools_genesis 438 status = EXIT_TROUBLE;
vtools_genesis 439 }
vtools_genesis 440 }
vtools_genesis 441
vtools_genesis 442 /* Now the comparison has been done, if no error prevented it, and
vtools_genesis 443 |status| is the value this function will return. */
vtools_genesis 444
vtools_genesis 445 if (status != EXIT_SUCCESS) {
vtools_genesis 446 /* Flush stdout so that the user sees differences immediately.
vtools_genesis 447 This can hurt performance, unfortunately. */
vtools_genesis 448 if (fflush(stdout) != 0)
vtools_genesis 449 pfatal_with_name("standard output");
vtools_genesis 450 }
vtools_genesis 451
vtools_genesis 452 free(free0);
vtools_genesis 453 free(free1);
vtools_genesis 454
vtools_genesis 455 return status;
vtools_genesis 456 }