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