ntpd: default to FEATURE_NTP_AUTH=y
[oweals/busybox.git] / editors / diff.c
index 83de5275302d2f29e6e0010294fbc7e4513ec736..1462a9b18656372cd102882641a93a6666273e57 100644 (file)
@@ -12,7 +12,6 @@
  *
  * 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 */
@@ -230,7 +275,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;;) {
@@ -248,17 +293,6 @@ static int search(const int *c, int k, int y, const struct cand *list)
        }
 }
 
-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);
@@ -315,7 +349,7 @@ static void stone(const int *a, int n, const int *b, int *J, int pref)
 }
 
 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 {
@@ -385,7 +419,7 @@ static void fetch(FILE_and_pos_t *ft, const off_t *ix, int a, int b, int ch)
                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++;
@@ -610,8 +644,8 @@ static bool diff(FILE* fp[2], char *file[2])
                                }
 
                                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));
@@ -644,7 +678,7 @@ static bool diff(FILE* fp[2], char *file[2])
                                        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.
@@ -672,32 +706,45 @@ 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)
-                       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;
@@ -795,7 +842,9 @@ static int FAST_FUNC skip_dir(const char *filename,
                        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;
                        }
                }
@@ -819,7 +868,7 @@ static void diffdir(char *p[2], const char *s_start)
                 * 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
@@ -847,9 +896,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++) {
@@ -915,6 +965,11 @@ static const char diff_longopts[] ALIGN1 =
        "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;
@@ -927,29 +982,57 @@ int diff_main(int argc UNUSED_PARAM, char **argv)
        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);