*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
-
/*
* The following code uses an algorithm due to Harold Stone,
* which finds a pair of longest identical subsequences in
* 3*(number of k-candidates installed), typically about
* 6n words for files of length n.
*/
+//config:config DIFF
+//config: bool "diff (13 kb)"
+//config: default y
+//config: help
+//config: diff compares two files or directories and outputs the
+//config: differences between them in a form that can be given to
+//config: the patch command.
+//config:
+//config:config FEATURE_DIFF_LONG_OPTIONS
+//config: bool "Enable long options"
+//config: default y
+//config: depends on DIFF && LONG_OPTS
+//config:
+//config:config FEATURE_DIFF_DIR
+//config: bool "Enable directory support"
+//config: default y
+//config: depends on DIFF
+//config: help
+//config: This option enables support for directory and subdirectory
+//config: comparison.
+
+//applet:IF_DIFF(APPLET(diff, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_DIFF) += diff.o
+
+//usage:#define diff_trivial_usage
+//usage: "[-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2"
+//usage:#define diff_full_usage "\n\n"
+//usage: "Compare files line by line and output the differences between them.\n"
+//usage: "This implementation supports unified diffs only.\n"
+//usage: "\n -a Treat all files as text"
+//usage: "\n -b Ignore changes in the amount of whitespace"
+//usage: "\n -B Ignore changes whose lines are all blank"
+//usage: "\n -d Try hard to find a smaller set of changes"
+//usage: "\n -i Ignore case differences"
+//usage: "\n -L Use LABEL instead of the filename in the unified header"
+//usage: "\n -N Treat absent files as empty"
+//usage: "\n -q Output only whether files differ"
+//usage: "\n -r Recurse"
+//usage: "\n -S Start with FILE when comparing directories"
+//usage: "\n -T Make tabs line up by prefixing a tab when necessary"
+//usage: "\n -s Report when two files are the same"
+//usage: "\n -t Expand tabs to spaces in output"
+//usage: "\n -U Output LINES lines of context"
+//usage: "\n -w Ignore all whitespace"
#include "libbb.h"
+#include "common_bufsiz.h"
#if 0
-//#define dbg_error_msg(...) bb_error_msg(__VA_ARGS__)
+# define dbg_error_msg(...) bb_error_msg(__VA_ARGS__)
#else
-#define dbg_error_msg(...) ((void)0)
+# define dbg_error_msg(...) ((void)0)
#endif
enum { /* print_status() and diffreg() return values */
{
int i, j;
- if (list[c[k]].y < y) /* quick look for typical case */
+ if (list[c[k]].y < y) /* quick look for typical case */
return k + 1;
for (i = 0, j = k + 1;;) {
}
}
-static unsigned isqrt(unsigned n)
-{
- unsigned x = 1;
- while (1) {
- const unsigned y = x;
- x = ((n / x) + x) >> 1;
- if (x <= (y + 1) && x >= (y - 1))
- return x;
- }
-}
-
static void stone(const int *a, int n, const int *b, int *J, int pref)
{
const unsigned isq = isqrt(n);
}
struct line {
- /* 'serial' is not used in the begining, so we reuse it
+ /* 'serial' is not used in the beginning, so we reuse it
* to store line offsets, thus reducing memory pressure
*/
union {
for (j = 0, col = 0; j < ix[i] - ix[i - 1]; j++) {
int c = fgetc(ft->ft_fp);
if (c == EOF) {
- printf("\n\\ No newline at end of file\n");
+ puts("\n\\ No newline at end of file");
return;
}
ft->ft_pos++;
for (; suff < nlen[0] - pref && suff < nlen[1] - pref &&
nfile[0][nlen[0] - suff].value == nfile[1][nlen[1] - suff].value;
suff++);
- /* Arrays are pruned by the suffix and prefix lenght,
+ /* Arrays are pruned by the suffix and prefix length,
* the result being sorted and stored in sfile[fileno],
* and their sizes are stored in slen[fileno]
*/
}
for (j = 0; j < 2; j++)
- for (k = v[j].a; k < v[j].b; k++)
- nonempty |= (ix[j][k+1] - ix[j][k] != 1);
+ for (k = v[j].a; k <= v[j].b; k++)
+ nonempty |= (ix[j][k] - ix[j][k - 1] != 1);
vec = xrealloc_vector(vec, 6, ++idx);
memcpy(vec[idx], v, sizeof(v));
continue;
printf(",%d", (a < b) ? b - a + 1 : 0);
}
- printf(" @@\n");
+ puts(" @@");
/*
* Output changes in "unified" diff format--the old and new lines
* are printed together.
static int diffreg(char *file[2])
{
- FILE *fp[2] = { stdin, stdin };
+ FILE *fp[2];
bool binary = false, differ = false;
int status = STATUS_SAME, i;
+ fp[0] = stdin;
+ fp[1] = stdin;
for (i = 0; i < 2; i++) {
- int fd = open_or_warn_stdin(file[i]);
- if (fd == -1)
- goto out;
+ int fd = STDIN_FILENO;
+ if (!LONE_DASH(file[i])) {
+ if (!(option_mask32 & FLAG(N))) {
+ fd = open_or_warn(file[i], O_RDONLY);
+ if (fd == -1)
+ goto out;
+ } else {
+ /* -N: if some file does not exist compare it like empty */
+ fd = open(file[i], O_RDONLY);
+ if (fd == -1)
+ fd = xopen("/dev/null", O_RDONLY);
+ }
+ }
/* Our diff implementation is using seek.
* When we meet non-seekable file, we must make a temp copy.
*/
if (lseek(fd, 0, SEEK_SET) == -1 && errno == ESPIPE) {
char name[] = "/tmp/difXXXXXX";
- int fd_tmp = mkstemp(name);
- if (fd_tmp < 0)
- bb_perror_msg_and_die("mkstemp");
+ int fd_tmp = xmkstemp(name);
+
unlink(name);
if (bb_copyfd_eof(fd, fd_tmp) < 0)
xfunc_die();
- if (fd) /* Prevents closing of stdin */
+ if (fd != STDIN_FILENO)
close(fd);
fd = fd_tmp;
+ xlseek(fd, 0, SEEK_SET);
}
fp[i] = fdopen(fd, "r");
}
+ setup_common_bufsiz();
while (1) {
const size_t sz = COMMON_BUFSIZE / 2;
char *const buf0 = bb_common_bufsiz1;
free(othername);
if (r != 0 || !S_ISDIR(osb.st_mode)) {
/* other dir doesn't have similarly named
- * directory, don't recurse */
+ * directory, don't recurse; return 1 upon
+ * exit, just like diffutils' diff */
+ exit_status |= 1;
return SKIP;
}
}
* add_to_dirlist will remove it. */
list[i].len = strlen(p[i]);
recursive_action(p[i], ACTION_RECURSE | ACTION_FOLLOWLINKS,
- add_to_dirlist, skip_dir, &list[i], 0);
+ add_to_dirlist, skip_dir, &list[i], 0);
/* Sort dl alphabetically.
* GNU diff does this ignoring any number of trailing dots.
* We don't, so for us dotted files almost always are
break;
pos = !dp[0] ? 1 : (!dp[1] ? -1 : strcmp(dp[0], dp[1]));
k = pos > 0;
- if (pos && !(option_mask32 & FLAG(N)))
+ if (pos && !(option_mask32 & FLAG(N))) {
printf("Only in %s: %s\n", p[k], dp[k]);
- else {
+ exit_status |= 1;
+ } else {
char *fullpath[2], *path[2]; /* if -N */
for (i = 0; i < 2; i++) {
"starting-file\0" Required_argument "S"
"minimal\0" No_argument "d"
;
+# define GETOPT32 getopt32long
+# define LONGOPTS ,diff_longopts
+#else
+# define GETOPT32 getopt32
+# define LONGOPTS
#endif
int diff_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
INIT_G();
/* exactly 2 params; collect multiple -L <label>; -U N */
- opt_complementary = "=2:L::U+";
-#if ENABLE_FEATURE_DIFF_LONG_OPTIONS
- applet_long_options = diff_longopts;
-#endif
- getopt32(argv, "abdiL:NqrsS:tTU:wupBE",
+ GETOPT32(argv, "^" "abdiL:*NqrsS:tTU:+wupBE" "\0" "=2"
+ LONGOPTS,
&L_arg, &s_start, &opt_U_context);
argv += optind;
while (L_arg)
label[!!label[0]] = llist_pop(&L_arg);
+
+ /* Compat: "diff file name_which_doesnt_exist" exits with 2 */
xfunc_error_retval = 2;
for (i = 0; i < 2; i++) {
file[i] = argv[i];
- /* Compat: "diff file name_which_doesnt_exist" exits with 2 */
if (LONE_DASH(file[i])) {
fstat(STDIN_FILENO, &stb[i]);
gotstdin++;
- } else
+ } else if (option_mask32 & FLAG(N)) {
+ if (stat(file[i], &stb[i]))
+ xstat("/dev/null", &stb[i]);
+ } else {
xstat(file[i], &stb[i]);
+ }
}
xfunc_error_retval = 1;
+
if (gotstdin && (S_ISDIR(stb[0].st_mode) || S_ISDIR(stb[1].st_mode)))
bb_error_msg_and_die("can't compare stdin to a directory");
+ /* Compare metadata to check if the files are the same physical file.
+ *
+ * Comment from diffutils source says:
+ * POSIX says that two files are identical if st_ino and st_dev are
+ * the same, but many file systems incorrectly assign the same (device,
+ * inode) pair to two distinct files, including:
+ * GNU/Linux NFS servers that export all local file systems as a
+ * single NFS file system, if a local device number (st_dev) exceeds
+ * 255, or if a local inode number (st_ino) exceeds 16777215.
+ */
+ if (ENABLE_DESKTOP
+ && stb[0].st_ino == stb[1].st_ino
+ && stb[0].st_dev == stb[1].st_dev
+ && stb[0].st_size == stb[1].st_size
+ && stb[0].st_mtime == stb[1].st_mtime
+ && stb[0].st_ctime == stb[1].st_ctime
+ && stb[0].st_mode == stb[1].st_mode
+ && stb[0].st_nlink == stb[1].st_nlink
+ && stb[0].st_uid == stb[1].st_uid
+ && stb[0].st_gid == stb[1].st_gid
+ ) {
+ /* files are physically the same; no need to compare them */
+ return STATUS_SAME;
+ }
+
if (S_ISDIR(stb[0].st_mode) && S_ISDIR(stb[1].st_mode)) {
#if ENABLE_FEATURE_DIFF_DIR
diffdir(file, s_start);