X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=editors%2Fdiff.c;h=a78a0ee283732b359d4e001c09c519e9f264d909;hb=7c47b560a8fc97956dd8132bd7f1863d83c19866;hp=07594e8d8f8ecfde0c2efd8d5b6486357d41dd54;hpb=1821d188ca674b42bf0f384b0c2332ff95701bba;p=oweals%2Fbusybox.git diff --git a/editors/diff.c b/editors/diff.c index 07594e8d8..a78a0ee28 100644 --- a/editors/diff.c +++ b/editors/diff.c @@ -10,7 +10,7 @@ * 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. */ /* @@ -76,12 +76,60 @@ * 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 */ @@ -121,6 +169,7 @@ typedef struct FILE_and_pos_t { struct globals { smallint exit_status; int opt_U_context; + const char *other_dir; char *label[2]; struct stat stb[2]; }; @@ -229,7 +278,7 @@ static int search(const int *c, int k, int y, const struct cand *list) { 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;;) { @@ -478,7 +527,7 @@ start: 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] */ @@ -671,10 +720,12 @@ static bool diff(FILE* fp[2], char *file[2]) 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) @@ -684,9 +735,8 @@ static int diffreg(char *file[2]) */ 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(); @@ -760,9 +810,11 @@ static int FAST_FUNC add_to_dirlist(const char *filename, 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; } @@ -778,6 +830,27 @@ static int FAST_FUNC skip_dir(const char *filename, 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; } @@ -791,12 +864,13 @@ static void diffdir(char *p[2], const char *s_start) /*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 @@ -824,9 +898,10 @@ static void diffdir(char *p[2], const char *s_start) 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++) { @@ -927,6 +1002,31 @@ int diff_main(int argc UNUSED_PARAM, char **argv) 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);