aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/diff
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/diff')
-rw-r--r--usr.bin/diff/diff.132
-rw-r--r--usr.bin/diff/diff.c3
-rw-r--r--usr.bin/diff/diff.h26
-rw-r--r--usr.bin/diff/diffdir.c85
-rw-r--r--usr.bin/diff/diffreg.c48
-rw-r--r--usr.bin/diff/diffreg_new.c10
-rw-r--r--usr.bin/diff/tests/Makefile1
-rwxr-xr-xusr.bin/diff/tests/diff_test.sh27
8 files changed, 172 insertions, 60 deletions
diff --git a/usr.bin/diff/diff.1 b/usr.bin/diff/diff.1
index 9bc46d07b58a..47f9c11eb3db 100644
--- a/usr.bin/diff/diff.1
+++ b/usr.bin/diff/diff.1
@@ -1,3 +1,6 @@
+.\"
+.\" SPDX-License-Identifier: BSD-3-Clause
+.\"
.\" $OpenBSD: diff.1,v 1.47 2015/11/24 19:35:41 jmc Exp $
.\"
.\" Copyright (c) 1980, 1990, 1993
@@ -27,7 +30,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd March 7, 2024
+.Dd January 7, 2025
.Dt DIFF 1
.Os
.Sh NAME
@@ -52,6 +55,7 @@
.Op Fl -ignore-space-change
.Op Fl -initial-tab
.Op Fl -minimal
+.Op Fl -no-dereference
.Op Fl -no-ignore-file-name-case
.Op Fl -normal
.Op Fl -rcs
@@ -83,6 +87,7 @@
.Op Fl -ignore-space-change
.Op Fl -initial-tab
.Op Fl -minimal
+.Op Fl -no-dereference
.Op Fl -no-ignore-file-name-case
.Op Fl -normal
.Op Fl -paginate
@@ -93,7 +98,7 @@
.Op Fl -strip-trailing-cr
.Op Fl -tabsize Ar number
.Op Fl -text
-.Fl C Ar number | -context Ar number
+.Fl C Ar number | Fl -context Ar number
.Ar file1 file2
.Nm diff
.Op Fl aBbdiltw
@@ -110,6 +115,7 @@
.Op Fl -ignore-space-change
.Op Fl -initial-tab
.Op Fl -minimal
+.Op Fl -no-dereference
.Op Fl -no-ignore-file-name-case
.Op Fl -normal
.Op Fl -paginate
@@ -139,6 +145,7 @@
.Op Fl -ignore-space-change
.Op Fl -initial-tab
.Op Fl -minimal
+.Op Fl -no-dereference
.Op Fl -no-ignore-file-name-case
.Op Fl -normal
.Op Fl -paginate
@@ -171,6 +178,7 @@
.Op Fl -initial-tab
.Op Fl -minimal
.Op Fl -new-file
+.Op Fl -no-dereference
.Op Fl -no-ignore-file-name-case
.Op Fl -normal
.Op Fl -paginate
@@ -197,10 +205,11 @@
.Op Fl aBbditwW
.Op Fl -color Ns = Ns Ar when
.Op Fl -expand-tabs
-.Op Fl -ignore-all-blanks
+.Op Fl -ignore-all-space
.Op Fl -ignore-blank-lines
.Op Fl -ignore-case
.Op Fl -minimal
+.Op Fl -no-dereference
.Op Fl -no-ignore-file-name-case
.Op Fl -strip-trailing-cr
.Op Fl -suppress-common-lines
@@ -348,7 +357,7 @@ Files differ and only the second file contains the line.
.Pp
Comparison options:
.Bl -tag -width Ds
-.It Fl A Ar algo, Fl -algorithm Ar algo
+.It Fl A Ar algo , Fl -algorithm Ar algo
Configure the algorithm used when comparing files.
.Nm
supports 3 algorithms:
@@ -414,9 +423,9 @@ environment variable is set to a non-empty string.
Try very hard to produce a diff as small as possible.
This may consume a lot of processing power and memory when processing
large files with many changes.
-.It Fl F Ar pattern, Fl -show-function-line Ar pattern
+.It Fl F Ar pattern , Fl -show-function-line Ar pattern
Like
-.Fl p,
+.Fl p ,
but display the last line that matches provided pattern.
.It Fl I Ar pattern Fl -ignore-matching-lines Ar pattern
Ignores changes, insertions, and deletions whose lines match the
@@ -463,7 +472,7 @@ output adds character(s) to the front of each line which may screw up
the indentation of the original source lines and make the output listing
difficult to interpret.
This option will preserve the original source's indentation.
-.It Fl w -ignore-all-blanks
+.It Fl w -ignore-all-space
Is similar to
.Fl b -ignore-space-change
but causes whitespace (blanks and tabs) to be totally ignored.
@@ -477,7 +486,7 @@ Output at most
columns when using side by side format.
The default value is 130.
Note that unless
-.It Fl t
+.Fl t
was specified,
.Nm
will always align the second column to a tab stop, so values of
@@ -497,6 +506,8 @@ lines from FILE2
.El
.It Fl -ignore-file-name-case
ignore case when comparing file names
+.It Fl -no-dereference
+do not follow symbolic links
.It Fl -no-ignore-file-name-case
do not ignore case when comparing file names (default)
.It Fl -normal
@@ -687,7 +698,7 @@ number.
.Bl -tag -width DIFFCOLORS
.It Ev DIFFCOLORS
The value of this variable is the form
-.Ar add Ns : Ns Ar rm ,
+.Ar add : Ns Ar rm ,
where
.Ar add
is the ASCII escape sequence for additions and
@@ -810,4 +821,5 @@ by a BSD-licensed implementation written by
Some GNUisms were lost in the process.
.Pp
libdiff was imported from the Game of Trees version control system and default
-algorithm was changed to Myers for FreeBSD 15.
+algorithm was changed to Myers for
+.Fx 15 .
diff --git a/usr.bin/diff/diff.c b/usr.bin/diff/diff.c
index c3fe255a294d..83aa20c52cf3 100644
--- a/usr.bin/diff/diff.c
+++ b/usr.bin/diff/diff.c
@@ -368,7 +368,6 @@ main(int argc, char **argv)
optarg);
break;
case OPT_NO_DEREFERENCE:
- rflag = true;
noderef = true;
break;
case OPT_VERSION:
@@ -630,7 +629,7 @@ usage(void)
" diff [-aBbdilNPprsTtw] [-c | -e | -f | -n | -q | -u] [--ignore-case]\n"
" [--no-ignore-case] [--normal] [--tabsize] [-I pattern] [-L label]\n"
" [-F pattern] [-S name] [-X file] [-x pattern] dir1 dir2\n"
- " diff [-aBbditwW] [--expand-tabs] [--ignore-all-blanks]\n"
+ " diff [-aBbditwW] [--expand-tabs] [--ignore-all-space]\n"
" [--ignore-blank-lines] [--ignore-case] [--minimal]\n"
" [--no-ignore-file-name-case] [--strip-trailing-cr]\n"
" [--suppress-common-lines] [--tabsize] [--text] [--width]\n"
diff --git a/usr.bin/diff/diff.h b/usr.bin/diff/diff.h
index 679b95e7ca94..74be55db8a33 100644
--- a/usr.bin/diff/diff.h
+++ b/usr.bin/diff/diff.h
@@ -108,21 +108,21 @@ struct excludes {
struct excludes *next;
};
-extern bool lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag;
-extern bool ignore_file_case, suppress_common, color, noderef, algorithm_set;
-extern int diff_format, diff_context, diff_algorithm, status;
-extern bool diff_algorithm_set;
-extern int tabsize, width;
+extern bool lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag;
+extern bool ignore_file_case, suppress_common, color, noderef, algorithm_set;
+extern int diff_format, diff_context, diff_algorithm, status;
+extern bool diff_algorithm_set;
+extern int tabsize, width;
extern char *start, *ifdefname, *diffargs, *label[2];
extern char *ignore_pats, *most_recent_pat;
extern char *group_format;
extern const char *add_code, *del_code;
-extern struct stat stb1, stb2;
-extern struct excludes *excludes_list;
-extern regex_t ignore_re, most_recent_re;
+extern struct stat stb1, stb2;
+extern struct excludes *excludes_list;
+extern regex_t ignore_re, most_recent_re;
-int diffreg(char *, char *, int, int);
-int diffreg_new(char *, char *, int, int);
-bool can_libdiff(int);
-void diffdir(char *, char *, int);
-void print_status(int, char *, char *, const char *);
+int diffreg(char *, char *, int, int);
+int diffreg_new(char *, char *, int, int);
+bool can_libdiff(int);
+void diffdir(char *, char *, int);
+void print_status(int, char *, char *, const char *);
diff --git a/usr.bin/diff/diffdir.c b/usr.bin/diff/diffdir.c
index 8d12e868f90e..a55a2bec70ee 100644
--- a/usr.bin/diff/diffdir.c
+++ b/usr.bin/diff/diffdir.c
@@ -21,10 +21,12 @@
*/
#include <sys/stat.h>
+#include <sys/tree.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
+#include <fcntl.h>
#include <fnmatch.h>
#include <limits.h>
#include <stdio.h>
@@ -41,6 +43,61 @@ static void print_only(const char *, size_t, const char *);
#define d_status d_type /* we need to store status for -l */
+struct inode {
+ dev_t dev;
+ ino_t ino;
+ RB_ENTRY(inode) entry;
+};
+
+static int
+inodecmp(struct inode *a, struct inode *b)
+{
+ return (a->dev < b->dev ? -1 : a->dev > b->dev ? 1 :
+ a->ino < b->ino ? -1 : a->ino > b->ino ? 1 : 0);
+}
+
+RB_HEAD(inodetree, inode);
+static struct inodetree v1 = RB_INITIALIZER(&v1);
+static struct inodetree v2 = RB_INITIALIZER(&v2);
+RB_GENERATE_STATIC(inodetree, inode, entry, inodecmp);
+
+static int
+vscandir(struct inodetree *tree, const char *path, struct dirent ***dirp,
+ int (*selectf)(const struct dirent *),
+ int (*comparf)(const struct dirent **, const struct dirent **))
+{
+ struct stat sb;
+ struct inode *ino = NULL;
+ int fd = -1, ret, serrno;
+
+ if ((fd = open(path, O_DIRECTORY | O_RDONLY)) < 0 ||
+ (ino = calloc(1, sizeof(*ino))) == NULL ||
+ fstat(fd, &sb) != 0)
+ goto fail;
+ ino->dev = sb.st_dev;
+ ino->ino = sb.st_ino;
+ if (RB_FIND(inodetree, tree, ino)) {
+ free(ino);
+ close(fd);
+ warnx("%s: Directory loop detected", path);
+ *dirp = NULL;
+ return (0);
+ }
+ if ((ret = fdscandir(fd, dirp, selectf, comparf)) < 0)
+ goto fail;
+ RB_INSERT(inodetree, tree, ino);
+ close(fd);
+ return (ret);
+fail:
+ serrno = errno;
+ if (ino != NULL)
+ free(ino);
+ if (fd >= 0)
+ close(fd);
+ errno = serrno;
+ return (-1);
+}
+
/*
* Diff directory traversal. Will be called recursively if -r was specified.
*/
@@ -61,26 +118,22 @@ diffdir(char *p1, char *p2, int flags)
status |= 2;
return;
}
- if (path1[dirlen1 - 1] != '/') {
- path1[dirlen1++] = '/';
- path1[dirlen1] = '\0';
- }
+ while (dirlen1 > 1 && path1[dirlen1 - 1] == '/')
+ path1[--dirlen1] = '\0';
dirlen2 = strlcpy(path2, *p2 ? p2 : ".", sizeof(path2));
if (dirlen2 >= sizeof(path2) - 1) {
warnc(ENAMETOOLONG, "%s", p2);
status |= 2;
return;
}
- if (path2[dirlen2 - 1] != '/') {
- path2[dirlen2++] = '/';
- path2[dirlen2] = '\0';
- }
+ while (dirlen2 > 1 && path2[dirlen2 - 1] == '/')
+ path2[--dirlen2] = '\0';
/*
* Get a list of entries in each directory, skipping "excluded" files
* and sorting alphabetically.
*/
- pos = scandir(path1, &dirp1, selectfile, alphasort);
+ pos = vscandir(&v1, path1, &dirp1, selectfile, alphasort);
if (pos == -1) {
if (errno == ENOENT && (Nflag || Pflag)) {
pos = 0;
@@ -92,7 +145,7 @@ diffdir(char *p1, char *p2, int flags)
dp1 = dirp1;
edp1 = dirp1 + pos;
- pos = scandir(path2, &dirp2, selectfile, alphasort);
+ pos = vscandir(&v2, path2, &dirp2, selectfile, alphasort);
if (pos == -1) {
if (errno == ENOENT && Nflag) {
pos = 0;
@@ -115,6 +168,18 @@ diffdir(char *p1, char *p2, int flags)
}
/*
+ * Append separator so children's names can be appended directly.
+ */
+ if (path1[dirlen1 - 1] != '/') {
+ path1[dirlen1++] = '/';
+ path1[dirlen1] = '\0';
+ }
+ if (path2[dirlen2 - 1] != '/') {
+ path2[dirlen2++] = '/';
+ path2[dirlen2] = '\0';
+ }
+
+ /*
* Iterate through the two directory lists, diffing as we go.
*/
while (dp1 != edp1 || dp2 != edp2) {
diff --git a/usr.bin/diff/diffreg.c b/usr.bin/diff/diffreg.c
index 06d914215b11..ffa5568bf442 100644
--- a/usr.bin/diff/diffreg.c
+++ b/usr.bin/diff/diffreg.c
@@ -203,9 +203,9 @@ static int *klist; /* will be overlaid on file[0] after class */
static int *member; /* will be overlaid on file[1] */
static int clen;
static int inifdef; /* whether or not we are in a #ifdef block */
-static int len[2];
-static int pref, suff; /* length of prefix and suffix */
-static int slen[2];
+static size_t len[2]; /* lengths of files in lines */
+static size_t pref, suff; /* lengths of prefix and suffix */
+static size_t slen[2]; /* lengths of files minus pref / suff */
static int anychange;
static int hw, lpad,rpad; /* half width and padding */
static int edoffset;
@@ -408,7 +408,8 @@ diffreg_stone(char *file1, char *file2, int flags, int capsicum)
}
if (diff_format == D_BRIEF && ignore_pats == NULL &&
- (flags & (D_FOLDBLANKS|D_IGNOREBLANKS|D_IGNORECASE|D_STRIPCR)) == 0)
+ (flags & (D_FOLDBLANKS|D_IGNOREBLANKS|D_IGNORECASE|
+ D_SKIPBLANKLINES|D_STRIPCR)) == 0)
{
rval = D_DIFFER;
status |= 1;
@@ -424,6 +425,10 @@ diffreg_stone(char *file1, char *file2, int flags, int capsicum)
status |= 1;
goto closem;
}
+ if (len[0] > INT_MAX - 2)
+ errc(1, EFBIG, "%s", file1);
+ if (len[1] > INT_MAX - 2)
+ errc(1, EFBIG, "%s", file2);
prune();
sort(sfile[0], slen[0]);
@@ -549,18 +554,17 @@ prepare(int i, FILE *fd, size_t filesize, int flags)
sz = 100;
p = xcalloc(sz + 3, sizeof(*p));
- while ((r = readhash(fd, flags, &h)) != RH_EOF)
- switch (r) {
- case RH_EOF: /* otherwise clang complains */
- case RH_BINARY:
+ while ((r = readhash(fd, flags, &h)) != RH_EOF) {
+ if (r == RH_BINARY)
return (false);
- case RH_OK:
- if (j == sz) {
- sz = sz * 3 / 2;
- p = xreallocarray(p, sz + 3, sizeof(*p));
- }
- p[++j].value = h;
+ if (j == SIZE_MAX)
+ break;
+ if (j == sz) {
+ sz = sz * 3 / 2;
+ p = xreallocarray(p, sz + 3, sizeof(*p));
}
+ p[++j].value = h;
+ }
len[i] = j;
file[i] = p;
@@ -571,7 +575,7 @@ prepare(int i, FILE *fd, size_t filesize, int flags)
static void
prune(void)
{
- int i, j;
+ size_t i, j;
for (pref = 0; pref < len[0] && pref < len[1] &&
file[0][pref + 1].value == file[1][pref + 1].value;
@@ -709,7 +713,7 @@ static void
unravel(int p)
{
struct cand *q;
- int i;
+ size_t i;
for (i = 0; i <= len[0]; i++)
J[i] = i <= pref ? i :
@@ -736,7 +740,7 @@ check(FILE *f1, FILE *f2, int flags)
ixold[0] = ixnew[0] = 0;
/* jackpot = 0; */
ctold = ctnew = 0;
- for (i = 1; i <= len[0]; i++) {
+ for (i = 1; i <= (int)len[0]; i++) {
if (J[i] == 0) {
ixold[i] = ctold += skipline(f1);
continue;
@@ -836,7 +840,7 @@ check(FILE *f1, FILE *f2, int flags)
ixnew[j] = ctnew;
j++;
}
- for (; j <= len[1]; j++) {
+ for (; j <= (int)len[1]; j++) {
ixnew[j] = ctnew += skipline(f2);
}
/*
@@ -1503,9 +1507,9 @@ dump_context_vec(FILE *f1, FILE *f2, int flags)
b = d = 0; /* gcc */
lowa = MAX(1, cvp->a - diff_context);
- upb = MIN(len[0], context_vec_ptr->b + diff_context);
+ upb = MIN((int)len[0], context_vec_ptr->b + diff_context);
lowc = MAX(1, cvp->c - diff_context);
- upd = MIN(len[1], context_vec_ptr->d + diff_context);
+ upd = MIN((int)len[1], context_vec_ptr->d + diff_context);
printf("***************");
if (flags & (D_PROTOTYPE | D_MATCHLAST)) {
@@ -1606,9 +1610,9 @@ dump_unified_vec(FILE *f1, FILE *f2, int flags)
b = d = 0; /* gcc */
lowa = MAX(1, cvp->a - diff_context);
- upb = MIN(len[0], context_vec_ptr->b + diff_context);
+ upb = MIN((int)len[0], context_vec_ptr->b + diff_context);
lowc = MAX(1, cvp->c - diff_context);
- upd = MIN(len[1], context_vec_ptr->d + diff_context);
+ upd = MIN((int)len[1], context_vec_ptr->d + diff_context);
printf("@@ -");
uni_range(lowa, upb);
diff --git a/usr.bin/diff/diffreg_new.c b/usr.bin/diff/diffreg_new.c
index 3b50dfd5940b..f54cd554ccad 100644
--- a/usr.bin/diff/diffreg_new.c
+++ b/usr.bin/diff/diffreg_new.c
@@ -218,10 +218,14 @@ diffreg_new(char *file1, char *file2, int flags, int capsicum)
rc = D_ERROR;
goto done;
}
+ if (left.atomizer_flags & DIFF_ATOMIZER_FILE_TRUNCATED)
+ warnx("%s truncated", file1);
if (diff_atomize_file(&right, cfg, f2, (uint8_t *)str2, st2.st_size, diff_flags)) {
rc = D_ERROR;
goto done;
}
+ if (right.atomizer_flags & DIFF_ATOMIZER_FILE_TRUNCATED)
+ warnx("%s truncated", file2);
result = diff_main(cfg, &left, &right);
if (result->rc != DIFF_RC_OK) {
@@ -249,6 +253,8 @@ diffreg_new(char *file1, char *file2, int flags, int capsicum)
goto done;
}
+ if (color)
+ diff_output_set_colors(color, del_code, add_code);
if (diff_format == D_NORMAL) {
rc = diff_output_plain(NULL, stdout, &info, result, false);
} else if (diff_format == D_EDIT) {
@@ -308,8 +314,8 @@ openfile(const char *path, char **p, struct stat *st)
bool
can_libdiff(int flags)
{
- /* We can't use fifos with libdiff yet */
- if (S_ISFIFO(stb1.st_mode) || S_ISFIFO(stb2.st_mode))
+ /* libdiff's atomizer can only deal with files */
+ if (!S_ISREG(stb1.st_mode) || !S_ISREG(stb2.st_mode))
return false;
/* Is this one of the supported input/output modes for diffreg_new? */
diff --git a/usr.bin/diff/tests/Makefile b/usr.bin/diff/tests/Makefile
index 9f3d29918414..242e76260249 100644
--- a/usr.bin/diff/tests/Makefile
+++ b/usr.bin/diff/tests/Makefile
@@ -1,4 +1,3 @@
-
PACKAGE= tests
ATF_TESTS_SH= diff_test
diff --git a/usr.bin/diff/tests/diff_test.sh b/usr.bin/diff/tests/diff_test.sh
index 146c23fa303d..691b649813a1 100755
--- a/usr.bin/diff/tests/diff_test.sh
+++ b/usr.bin/diff/tests/diff_test.sh
@@ -11,6 +11,7 @@ atf_test_case brief_format
atf_test_case b230049
atf_test_case stripcr_o
atf_test_case b252515
+atf_test_case b278988
atf_test_case Bflag
atf_test_case Nflag
atf_test_case tabsize
@@ -22,6 +23,7 @@ atf_test_case binary
atf_test_case functionname
atf_test_case noderef
atf_test_case ignorecase
+atf_test_case dirloop
simple_body()
{
@@ -89,6 +91,14 @@ b252515_body()
diff -qw b252515_a.in b252515_b.in
}
+b278988_body()
+{
+ printf 'a\nb\nn' > b278988.a.in
+ printf 'a\n\nb\nn' > b278988.b.in
+ atf_check -o empty -s eq:0 \
+ diff -Bw b278988.a.in b278988.b.in
+}
+
header_body()
{
export TZ=UTC
@@ -355,6 +365,21 @@ ignorecase_body()
atf_check -o empty -s exit:0 diff -u -r --ignore-file-name-case A B
}
+dirloop_head()
+{
+ atf_set "timeout" "10"
+}
+dirloop_body()
+{
+ atf_check mkdir -p a/foo/bar
+ atf_check ln -s .. a/foo/bar/up
+ atf_check cp -a a b
+ atf_check \
+ -e match:"a/foo/bar/up: Directory loop detected" \
+ -e match:"b/foo/bar/up: Directory loop detected" \
+ diff -r a b
+}
+
atf_init_test_cases()
{
atf_add_test_case simple
@@ -369,6 +394,7 @@ atf_init_test_cases()
atf_add_test_case b230049
atf_add_test_case stripcr_o
atf_add_test_case b252515
+ atf_add_test_case b278988
atf_add_test_case Bflag
atf_add_test_case Nflag
atf_add_test_case tabsize
@@ -380,4 +406,5 @@ atf_init_test_cases()
atf_add_test_case functionname
atf_add_test_case noderef
atf_add_test_case ignorecase
+ atf_add_test_case dirloop
}