acst

Tracks changes and corruption in files using xattr-based checksums.
git clone https://noxz.tech/git/acst.git
Log | Files | Tags | LICENSE

commit: 31289e6e45202d86aafe4732467fb21b4a09bced
parent: f5b601c6d56ad15a53ca5084fe2a03ba4bcad089
author: Chris Noxz <chris@noxz.tech>
date:   Tue, 12 Jul 2022 12:02:11 +0200
Add the ability to read the name of files through standard input.

* Add test for reading files through standard input (using file name '-')
* Allow for file names to start with or be equal to '-'
* Move program name holder to global variables
* Update manual
Macst.120++++++++++-
Macst.c29+++++++++++-----
Macst.h7++--
Mtest36+++++++++++++++++++-
4 files changed, 79 insertions(+), 13 deletions(-)
diff --git a/acst.1 b/acst.1
@@ -40,6 +40,12 @@ you specify two different partitions mounted on two different directories, they
 would both be traversed.
 .P
 .B acst
+reads names of files to open from standard input if the only
+.IR FILE
+argument is '-'. To open files starting with '-', use '--' to cancel parsing of
+arguments.
+.P
+.B acst
 does not aim to be format-compatible with
 .B shatag
 and uses different names for the extended attributes but similar formats for
@@ -52,7 +58,7 @@ section).
 .BR -d
 Check for duplicates among files based on stored checksums from acst's extended
 attributes. Return values when checking for duplicates are normally 0 for
-success or 1 for fatal errors (in other words
+success or 1 for fatal errors (in other words, the
 .B "RETURN VALUES"
 section does not apply). Certainty of the resultĀ is, of course, dependent on
 checksums being created or corrected fairly recently as no checksums are being
@@ -153,6 +159,18 @@ Duplicate of checksum among files checked.
 .
 .SH EXAMPLES
 .TP
+.BR "find /strg/shr/media -xdev -type f | acst - > /root/acst.log"
+will use
+.B find
+to recursively traverse through files in
+/mnt/memorystick within the same file system and log the result to
+/root/acst.log. Using
+.B acst
+this way is many times faster than using the recursive argument and can also
+replace all examples below using the recursive argument. It should be treated
+as the preferred method of using
+.BR acst .
+.TP
 .BR "acst -r /home/user01 /mnt/memorystick > /root/acst.log"
 will recursively process files in both /home/user01 and /mnt/memorystick even
 if they belong to different file systems, as they are both specified using the
diff --git a/acst.c b/acst.c
@@ -491,14 +491,16 @@ xa_write(int fd, const xa_t *xa)
 int
 main(int argc, char *argv[])
 {
+	char            line[PATH_MAX + 1];     // line holder for reading stdin
 	int             opt,                    // argument option holder
-	                i;                      // index counter for arguments
+	                i,                      // index counter for arguments
+	                c;                      // holder for getc(stdin)
 
 	/* declare global variables */
-	gl.dup_head = NULL;
+	gl.dup_head     = NULL;
+	gl.prg          = argv[0];
 
-	arg.prg = argv[0];
-	while ((opt = getopt(argc, argv, "dhmnqrvx")) != -1) {
+	while ((opt = getopt(argc, argv, "dhmnqrvx")) != -1)
 		switch (opt) {
 		case 'd':                           arg.duplicates = true; break;
 		case 'h':                           USAGE(EXIT_SUCCESS); break;
@@ -510,13 +512,24 @@ main(int argc, char *argv[])
 		case 'x':                           arg.remove = true; break;
 		default:                            USAGE(EXIT_FAILURE);
 		}
-	}
 
-	if (optind >= argc)
+	if ((i = optind) >= argc)
 		USAGE(EXIT_FAILURE);
 
-	for (i = optind; i < argc; i++)
-		process_argument(argv[i]);
+	/* read stdin if the only FILE is -, while the last argument wasn't -- */
+	if (argc - i == 1 && argv[i][0] == 0x2d && argv[i - 1][1] != argv[i][0])
+		for (i = 0; (c = getc(stdin)) != EOF; i++) {
+			if (i > PATH_MAX)
+				error(ER_FATAL, STR_ERR_PMAX);
+			if ((line[i] = (c != 0x0a) ? c : 0) != 0)
+				continue;
+			process_argument(line);
+			i = -1;
+		}
+	/* ...else, process each argument as a FILE */
+	else
+		for (; i < argc; i++)
+			process_argument(argv[i]);
 
 	if (arg.duplicates && gl.dup_head)
 		return dup_print(&gl.dup_head);
diff --git a/acst.h b/acst.h
@@ -38,6 +38,7 @@
 #define STR_DPH_FRMT        "<dup> %s\n"STR_DPN_FRMT
 #define STR_DPS_FRMT        "Found %d files with duplicates.\n"
 #define STR_ERR_OOM         "Error: out of memory\n"
+#define STR_ERR_PMAX        "Error: PATH_MAX reached before end\n"
 #define STR_ERR_ABNO        "Error: abnormal changes detected \"%s\"\n"
 #define STR_ERR_OPNF        "Error: could not open file \"%s\"\n"
 #define STR_ERR_OPND        "Error: could not open directory \"%s\"\n"
@@ -61,7 +62,7 @@
 #define MIN(X, Y)           (((X) < (Y)) ? (X) : (Y))
 #define USAGE(code)                                                         \
         {                                                                   \
-          printf("Usage: %s [OPTION]... <FILE>...\n", arg.prg);             \
+          printf("Usage: %s [OPTION]... <FILE>...\n", gl.prg);              \
           if ((code) == EXIT_SUCCESS)                                       \
             printf(                                                         \
     "\n"                                                                    \
@@ -85,7 +86,7 @@
         }
 #define VER()                                                               \
         {                                                                   \
-          printf("%s " VERSION "\n", arg.prg);                              \
+          printf("%s " VERSION "\n", gl.prg);                               \
           exit(EXIT_SUCCESS);                                               \
         }
 #define SUMMERIZE()                                                         \
@@ -172,7 +173,6 @@ typedef struct ExtendedAttribute {
 
 /* arguments collected from command line */
 struct Arguments {
-	const char             *prg;                /* program name */
 	bool                    dryrun;             /* make a dry run */
 	int                     quiet;              /* level of quietness */
 	bool                    recursive;          /* indicate to open and walk directories */
@@ -202,6 +202,7 @@ struct Counters {
 
 /* global variables */
 struct Global {
+	const char             *prg;                /* program name */
 	dn_t                   *dup_head;           /* head of Duplicate nodes */
 } gl;
 
diff --git a/test b/test
@@ -281,7 +281,7 @@ TMPFILE="$(mktemp -d XXXXXXXXXX)" && {
 	rm -rf object
 
 	#########################################################################
-	header 'multiple files'
+	header 'multiple files, using command line'
 	sort > expected <<- EOF
 	<new> object/level-1/file-1
 	 stored: 0000000000000000000000000000000000000000000000000000000000000000 0000000000.000000000
@@ -302,6 +302,40 @@ TMPFILE="$(mktemp -d XXXXXXXXXX)" && {
 	diff -u expected output
 	rm -rf object
 
+	#########################################################################
+	header 'multiple files, using STDIN'
+	sort > expected <<- EOF
+	<new> object/level-1/file-1
+	 stored: 0000000000000000000000000000000000000000000000000000000000000000 0000000000.000000000
+	 actual: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1655848860.000000000
+	<new> object/level-1/level-2/file-2
+	 stored: 0000000000000000000000000000000000000000000000000000000000000000 0000000000.000000000
+	 actual: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1655848920.000000000
+	<new> object/level-1/level-2/level-3/file-3
+	 stored: 0000000000000000000000000000000000000000000000000000000000000000 0000000000.000000000
+	 actual: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1655848980.000000000
+	EOF
+	rm -f object
+	mkdir -p object/level-1/level-2/level-3
+	TZ=CET touch -t 202206220001 object/level-1/file-1
+	TZ=CET touch -t 202206220002 object/level-1/level-2/file-2
+	TZ=CET touch -t 202206220003 object/level-1/level-2/level-3/file-3
+	find object -xdev -type f | ../"${PRG}" - | sort > output
+	diff -u expected output
+	rm -rf object
+
+	#########################################################################
+	header 'handle (-) as a file, when argument parsing is interrupted'
+	set +e
+	echo "Error: could not open file \"-\"" > expected
+	../"${PRG}" -- - > output 2>&1
+	if [ $? -ne 2 ]; then
+		echo "should have returned an error code 2, but returned 0"
+		exit 1
+	fi
+	set -e
+	diff -u expected output
+
 	#########################################################################
 	header 'dry run'
 	cat > expected <<- EOF