* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
*
- * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
/*
* 6n words for files of length n.
*/
+//config:config DIFF
+//config: bool "diff"
+//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: help
+//config: Enable use of long options.
+//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.
+
+//kbuild:lib-$(CONFIG_DIFF) += diff.o
+
+//applet:IF_DIFF(APPLET(diff, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//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"
#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 */
struct globals {
smallint exit_status;
int opt_U_context;
+ const char *other_dir;
char *label[2];
struct stat stb[2];
};
{
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;;) {
ix[i][j] = nfile[i][j].offset;
}
- /* lenght of prefix and suffix is calculated */
+ /* length of prefix and suffix is calculated */
for (; pref < nlen[0] && pref < nlen[1] &&
nfile[0][pref + 1].value == nfile[1][pref + 1].value;
pref++);
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]
*/
free(nfile[1]);
class = xmalloc((slen[0] + 1) * sizeof(class[0]));
- for (int i = 1; i <= slen[0]; i++) /* Unsorting */
+ for (i = 1; i <= slen[0]; i++) /* Unsorting */
class[sfile[0][i].serial] = sfile[0][i].value;
free(nfile[0]);
#else
FILE_and_pos_t ft[2];
typedef struct { int a, b; } vec_t[2];
vec_t *vec = NULL;
- int i = 1, idx = -1;
+ int i = 1, j, k, idx = -1;
bool anychange = false;
int *J;
break;
}
- for (int j = 0; j < 2; j++)
- for (int k = v[j].a; k < v[j].b; k++)
+ 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);
vec = xrealloc_vector(vec, 6, ++idx);
if (idx < 0 || ((option_mask32 & FLAG(B)) && !nonempty))
goto cont;
if (!(option_mask32 & FLAG(q))) {
+ int lowa;
vec_t span, *cvp = vec;
if (!anychange) {
}
printf("@@");
- for (int j = 0; j < 2; j++) {
+ for (j = 0; j < 2; j++) {
int a = span[j].a = MAX(1, (*cvp)[j].a - opt_U_context);
int b = span[j].b = MIN(nlen[j], vec[idx][j].b + opt_U_context);
* Output changes in "unified" diff format--the old and new lines
* are printed together.
*/
- for (int lowa = span[0].a; ; lowa = (*cvp++)[0].b + 1) {
+ for (lowa = span[0].a; ; lowa = (*cvp++)[0].b + 1) {
bool end = cvp > &vec[idx];
fetch(&ft[0], ix[0], lowa, end ? span[0].b : (*cvp)[0].a - 1, ' ');
if (end)
break;
- for (int j = 0; j < 2; j++)
+ for (j = 0; j < 2; j++)
fetch(&ft[j], ix[j], (*cvp)[j].a, (*cvp)[j].b, j ? '+' : '-');
}
}
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)
*/
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();
void *userdata, int depth UNUSED_PARAM)
{
struct dlist *const l = userdata;
+ const char *file = filename + l->len;
+ while (*file == '/')
+ file++;
l->dl = xrealloc_vector(l->dl, 6, l->e);
- /* + 1 skips "/" after dirname */
- l->dl[l->e] = xstrdup(filename + l->len + 1);
+ l->dl[l->e] = xstrdup(file);
l->e++;
return TRUE;
}
add_to_dirlist(filename, sb, userdata, depth);
return SKIP;
}
+ if (!(option_mask32 & FLAG(N))) {
+ /* -r without -N: no need to recurse into dirs
+ * which do not exist on the "other side".
+ * Testcase: diff -r /tmp /
+ * (it would recurse deep into /proc without this code) */
+ struct dlist *const l = userdata;
+ filename += l->len;
+ if (filename[0]) {
+ struct stat osb;
+ char *othername = concat_path_file(G.other_dir, filename);
+ int r = stat(othername, &osb);
+ free(othername);
+ if (r != 0 || !S_ISDIR(osb.st_mode)) {
+ /* other dir doesn't have similarly named
+ * directory, don't recurse; return 1 upon
+ * exit, just like diffutils' diff */
+ exit_status |= 1;
+ return SKIP;
+ }
+ }
+ }
return TRUE;
}
/*list[i].s = list[i].e = 0; - memset did it */
/*list[i].dl = NULL; */
+ G.other_dir = p[1 - i];
/* We need to trim root directory prefix.
* Using list.len to specify its length,
* 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++) {
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);