Support diff --no-dereference

This commit is contained in:
Daniel Rosenberg 2024-07-30 18:05:26 -07:00 committed by Rob Landley
parent 6462689585
commit 8bed55bb67
4 changed files with 81 additions and 21 deletions

View File

@ -156,6 +156,7 @@ char *xreadfile(char *name, char *buf, off_t len);
int xioctl(int fd, int request, void *data);
char *xgetcwd(void);
void xstat(char *path, struct stat *st);
void xlstat(char *path, struct stat *st);
char *xabspath(char *path, int exact);
void xchdir(char *path);
void xchroot(char *path);

View File

@ -576,6 +576,11 @@ void xstat(char *path, struct stat *st)
if(stat(path, st)) perror_exit("Can't stat %s", path);
}
void xlstat(char *path, struct stat *st)
{
if(lstat(path, st)) perror_exit("Can't lstat %s", path);
}
// Canonicalize path, even to file with one or more missing components at end.
// Returns allocated string for pathname or NULL if doesn't exist. Flags are:
// ABS_PATH:path to last component must exist ABS_FILE: whole path must exist

View File

@ -43,6 +43,13 @@ echo -e "1\n3" > bb
testcmd "line format" "--unchanged-line-format=U%l --old-line-format=D%l --new-line-format=A%l aa bb" "U1D2A3" "" ""
testcmd "line format empty" "--unchanged-line-format= --old-line-format=D%l --new-line-format=A%l aa bb" "D2A3" "" ""
ln -s aa cc
testcmd "follow symlink" "-q -L aa -L cc aa cc" "" "" ""
testcmd "no follow symlink" "-q --no-dereference -L aa -L cc aa cc" "File aa is a regular file while file cc is a symbolic link\n" "" ""
ln -s ./aa dd
testcmd "symlink differs" "-q -L cc -L dd cc dd" "" "" ""
testcmd "symlink differs no follow" "-q --no-dereference -L cc -L dd cc dd" "Symbolic links cc and dd differ\n" "" ""
mkfifo fifo1
mkfifo fifo2
echo -e "1\n2" > fifo1&

View File

@ -8,7 +8,7 @@
*
* Deviations from posix: always does -u
USE_DIFF(NEWTOY(diff, "<2>2(unchanged-line-format):;(old-line-format):;(new-line-format):;(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)S(starting-file):F(show-function-line):;L(label)*N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
USE_DIFF(NEWTOY(diff, "<2>2(unchanged-line-format):;(old-line-format):;(no-dereference);(new-line-format):;(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)S(starting-file):F(show-function-line):;L(label)*N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
config DIFF
bool "diff"
@ -35,6 +35,7 @@ config DIFF
-w Ignore all whitespace
--color Color output --strip-trailing-cr Strip '\r' from input lines
--no-dereference Don't follow symbolic links
--TYPE-line-format=FORMAT Display TYPE (unchanged/old/new) lines using FORMAT
FORMAT uses printf integer escapes (ala %-2.4x) followed by LETTER: FELMNn
Supported format specifiers are:
@ -51,7 +52,7 @@ GLOBALS(
struct arg_list *L;
char *F, *S, *new_line_format, *old_line_format, *unchanged_line_format;
int dir_num, size, is_binary, differ, change, len[2], *offset[2];
int dir_num, size, is_binary, is_symlink, differ, change, len[2], *offset[2];
struct stat st[2];
struct {
char **list;
@ -61,6 +62,10 @@ GLOBALS(
FILE *fp;
int len;
} file[2];
struct {
char *name;
int len;
} link[2];
)
#define IS_STDIN(s) (*(s)=='-' && !(s)[1])
@ -512,19 +517,19 @@ static int list_dir(struct dirtree *node)
if (S_ISDIR(node->st.st_mode) && !node->parent) { //add root dirs.
add_to_list(node);
return (DIRTREE_RECURSE|DIRTREE_SYMFOLLOW);
return (DIRTREE_RECURSE|((FLAG(no_dereference)) ? 0 : DIRTREE_SYMFOLLOW));
}
if (S_ISDIR(node->st.st_mode) && FLAG(r)) {
if (!FLAG(N)) ret = skip(node);
if (!ret) return DIRTREE_RECURSE|DIRTREE_SYMFOLLOW;
if (!ret) return DIRTREE_RECURSE|((FLAG(no_dereference)) ? 0 : DIRTREE_SYMFOLLOW);
else {
add_to_list(node); //only at one side.
return 0;
}
} else {
add_to_list(node);
return S_ISDIR(node->st.st_mode) ? 0 : (DIRTREE_RECURSE|DIRTREE_SYMFOLLOW);
return S_ISDIR(node->st.st_mode) ? 0 : (DIRTREE_RECURSE|((FLAG(no_dereference)) ? 0 : DIRTREE_SYMFOLLOW));
}
}
@ -578,6 +583,33 @@ static void show_label(char *prefix, char *filename, struct stat *sb)
free(quoted_file);
}
static void do_symlink_diff(char **files)
{
size_t i;
int s = sizeof(toybuf)/2;
TT.is_symlink = 1;
TT.differ = 0;
TT.link[0].name = TT.link[1].name = NULL;
for (i = 0; i < 2; i++) {
TT.link[i].name = xreadlink(files[i]);
if (TT.link[i].name == 0) {
perror_msg("readlink failed");
TT.differ = 2;
free(TT.link[0].name);
return;
}
TT.link[i].len = strlen(TT.link[i].name);
}
if (TT.link[0].len != TT.link[1].len) TT.differ = 1;
else if (smemcmp(TT.link[0].name, TT.link[1].name, TT.link[0].len))
TT.differ = 1;
free(TT.link[0].name);
free(TT.link[1].name);
return;
}
static void do_diff(char **files)
{
long i = 1, size = 1, x = 0, change = 0, ignore_white,
@ -717,9 +749,9 @@ calc_ct:
static void show_status(char **files)
{
if (TT.differ==2) return; // TODO: needed?
if (TT.differ ? FLAG(q) || TT.is_binary : FLAG(s))
printf("Files %s and %s %s\n", files[0], files[1],
TT.differ ? "differ" : "are identical");
if (TT.differ ? FLAG(q) || TT.is_binary || TT.is_symlink : FLAG(s))
printf("%s %s and %s %s\n", TT.is_symlink ? "Symbolic links" : "Files",
files[0], files[1], TT.differ ? "differ" : "are identical");
}
static void create_empty_entry(int l , int r, int j)
@ -736,25 +768,30 @@ static void create_empty_entry(int l , int r, int j)
f[!i] = "/dev/null";
}
path[i] = f[i] = TT.dir[i].list[i ? r : l];
stat(f[i], st+i);
(FLAG(no_dereference) ? lstat : stat)(f[i], st+i);
if (j) st[!i] = st[i];
}
for (i = 0; i<2; i++) {
if (!S_ISREG(st[i].st_mode) && !S_ISDIR(st[i].st_mode)) {
printf("File %s is not a regular file or directory and was skipped\n",
if (!S_ISREG(st[i].st_mode) && !S_ISDIR(st[i].st_mode) && !S_ISLNK(st[i].st_mode)) {
printf("File %s is not a regular file, symbolic link, or directory and was skipped\n",
path[i]);
break;
}
}
if (i != 2);
else if (S_ISDIR(st[0].st_mode) && S_ISDIR(st[1].st_mode))
printf("Common subdirectories: %s and %s\n", path[0], path[1]);
else if ((i = S_ISDIR(st[0].st_mode)) != S_ISDIR(st[1].st_mode)) {
char *fidir[] = {"directory", "regular file"};
else if ((st[0].st_mode & S_IFMT) != (st[1].st_mode & S_IFMT)) {
i = S_ISREG(st[0].st_mode) + 2 * S_ISLNK(st[0].st_mode);
int k = S_ISREG(st[1].st_mode) + 2 * S_ISLNK(st[1].st_mode);
char *fidir[] = {"directory", "regular file", "symbolic link"};
printf("File %s is a %s while file %s is a %s\n",
path[0], fidir[!i], path[1], fidir[i]);
path[0], fidir[i], path[1], fidir[k]);
} else if (S_ISDIR(st[0].st_mode))
printf("Common subdirectories: %s and %s\n", path[0], path[1]);
else if (S_ISLNK(st[0].st_mode)) {
do_symlink_diff(f);
show_status(path);
} else {
do_diff(f);
show_status(path);
@ -814,7 +851,7 @@ static void diff_dir(int *start)
void diff_main(void)
{
int j = 0, k = 1, start[2] = {1, 1};
int i, j = 0, k = 1, start[2] = {1, 1};
char **files = toys.optargs;
toys.exitval = 2;
@ -822,6 +859,7 @@ void diff_main(void)
for (j = 0; j < 2; j++) {
if (IS_STDIN(files[j])) fstat(0, &TT.st[j]);
else if (FLAG(no_dereference)) xlstat(files[j], &TT.st[j]);
else xstat(files[j], &TT.st[j]);
}
@ -844,7 +882,7 @@ void diff_main(void)
if (S_ISDIR(TT.st[0].st_mode) && S_ISDIR(TT.st[1].st_mode)) {
for (j = 0; j < 2; j++) {
memset(TT.dir+j, 0, sizeof(*TT.dir));
dirtree_flagread(files[j], DIRTREE_SYMFOLLOW, list_dir);
dirtree_flagread(files[j], (FLAG(no_dereference)) ? 0 : DIRTREE_SYMFOLLOW, list_dir);
TT.dir[j].nr_elm = TT.size; //size updated in list_dir
qsort(&TT.dir[j].list[1], TT.size-1, sizeof(char *), (void *)cmp);
@ -868,10 +906,19 @@ void diff_main(void)
char *slash = strrchr(files[d], '/');
files[!d] = concat_file_path(files[!d], slash ? slash+1 : files[d]);
if (stat(files[!d], &TT.st[!d])) perror_exit("%s", files[!d]);
if ((FLAG(no_dereference) ? lstat : stat)(files[!d], &TT.st[!d]))
perror_exit("%s", files[!d]);
}
if ((i = S_ISREG(TT.st[0].st_mode)) != S_ISREG(TT.st[1].st_mode)) {
char *fidir[] = {"regular file", "symbolic link"};
printf("File %s is a %s while file %s is a %s\n",
files[0], fidir[!i], files[1], fidir[i]);
TT.differ = 1;
} else {
if (S_ISLNK(TT.st[0].st_mode)) do_symlink_diff(files);
else do_diff(files);
show_status(files);
}
do_diff(files);
show_status(files);
if (TT.file[0].fp) fclose(TT.file[0].fp);
if (TT.file[1].fp) fclose(TT.file[1].fp);
}