Skip to content

Commit

Permalink
Adding a -recursive flag (#15)
Browse files Browse the repository at this point in the history
added -recursive flag, can pass directories

cshatag can now be passed directories as command-line arguments.

os.Exit is only ever called from main so that the program always attempts to process all files before exiting.

Also, recursive mode now just skips non-regular files without producing
an error.
  • Loading branch information
es80 authored and rfjakob committed Dec 3, 2019
1 parent 7847306 commit b169f0a
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 58 deletions.
37 changes: 20 additions & 17 deletions README.md
Expand Up @@ -9,7 +9,7 @@ NAME
cshatag - compiled shatag
SYNOPSIS
cshatag [OPTIONS] FILE [FILE2...]
cshatag [OPTIONS] FILE [FILE...]
DESCRIPTION
cshatag is a minimal and fast re-implementation of shatag
Expand All @@ -31,41 +31,44 @@ DESCRIPTION
<timechange> only mtime has changed, checksum stayed the same
<corrupt> mtime stayed the same but checksum changed
cshatag aims to be format-compatible with shatag and uses the same
extended attributes (see the COMPATIBILITY section).
cshatag aims to be format-compatible with shatag and uses the same ex‐
tended attributes (see the COMPATIBILITY section).
cshatag was written in C in 2012 and has been rewritten in Go in 2019.
OPTIONS
-remove remove cshatag's xattrs from FILE
-q quiet mode - don't report <ok> files
-qq quiet2 mode - only report <corrupt> files and errors
-recursive recursively process the contents of directories
-remove remove cshatag's xattrs from FILE
-q quiet mode - don't report <ok> files
-qq quiet2 mode - only report <corrupt> files and errors
EXAMPLES
Typically, cshatag will be called from find:
# find . -xdev -type f -print0 | xargs -0 cshatag > cshatag.log
Errors like corrupt files will then be printed to stderr or grep for
Check all regular files in the file tree below the current working di‐
rectory:
# cshatag -recursive . > cshatag.log
Errors like corrupt files will then be printed to stderr or grep for
"corrupt" in cshatag.log.
To remove the extended attributes from all files:
# find . -xdev -type f -print0 | xargs -0 cshatag -remove
# cshatag -recursive -remove .
RETURN VALUE
0 Success
1 Wrong number of arguments
2 File could not be opened
3 File is not a regular file
4 Extended attributs could not be written to file
5 File is corrupt
2 One or more files could not be opened
3 One or more files is not a regular file
4 Extended attributes could not be written to one or more files
5 At least one file was found to be corrupt
6 More than one type of error occurred
COMPATIBILITY
cshatag writes the user.shatag.ts field with full integer nanosecond
cshatag writes the user.shatag.ts field with full integer nanosecond
precision, while python uses a double for the whole mtime and loses the
last few digits.
AUTHOR
Jakob Unterwurzacher <jakobunt@gmail.com>,
https://github.com/rfjakob/cshatag
Jakob Unterwurzacher <jakobunt@gmail.com>, https://github.com/rf‐
jakob/cshatag
COPYRIGHT
Copyright 2012 Jakob Unterwurzacher. MIT License.
Expand Down
33 changes: 19 additions & 14 deletions check.go
Expand Up @@ -62,14 +62,10 @@ func getStoredAttr(f *os.File) (attr fileAttr, err error) {
}

// getMtime reads the actual modification time of file "f" from disk.
func getMtime(f *os.File) (ts fileTimestamp) {
func getMtime(f *os.File) (ts fileTimestamp, err error) {
fi, err := f.Stat()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
if !fi.Mode().IsRegular() {
fmt.Printf("Error: %q is not a regular file\n", f.Name())
os.Exit(3)
return
}
ts.s = uint64(fi.ModTime().Unix())
ts.ns = uint32(fi.ModTime().Nanosecond())
Expand All @@ -79,15 +75,19 @@ func getMtime(f *os.File) (ts fileTimestamp) {
// getActualAttr reads the actual modification time and hashes the file content.
func getActualAttr(f *os.File) (attr fileAttr, err error) {
attr.sha256 = []byte(zeroSha256)
attr.ts = getMtime(f)
attr.ts, err = getMtime(f)
if err != nil {
return attr, err
}
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
fmt.Println(err)
os.Exit(2)
return attr, err
}
// Check if the file was modified while we were computing the hash
ts2 := getMtime(f)
if attr.ts != ts2 {
ts2, err := getMtime(f)
if err != nil {
return attr, err
} else if attr.ts != ts2 {
return attr, syscall.EINPROGRESS
}
attr.sha256 = []byte(fmt.Sprintf("%x", h.Sum(nil)))
Expand Down Expand Up @@ -147,15 +147,15 @@ func checkFile(fn string) {
f, err := os.Open(fn)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
stats.errors++
stats.errorsOpening++
return
}
defer f.Close()

if args.remove {
if err = removeAttr(f); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
stats.errors++
stats.errorsOther++
return
}
if !args.q {
Expand All @@ -173,7 +173,12 @@ func checkFile(fn string) {
}
stats.inprogress++
return
} else if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
stats.errorsOther++
return
}

if stored.ts == actual.ts {
if bytes.Equal(stored.sha256, actual.sha256) {
if !args.q {
Expand Down Expand Up @@ -203,7 +208,7 @@ func checkFile(fn string) {
err = storeAttr(f, actual)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
stats.errors++
stats.errorsWritingXattr++
return
}
}
26 changes: 14 additions & 12 deletions cshatag.1
Expand Up @@ -45,38 +45,42 @@ rewritten in Go in 2019.

.SH OPTIONS

-remove remove cshatag's xattrs from FILE
-recursive recursively process the contents of directories
.br
-q quiet mode - don't report <ok> files
-remove remove cshatag's xattrs from FILE
.br
-qq quiet2 mode - only report <corrupt> files and errors
-q quiet mode - don't report <ok> files
.br
-qq quiet2 mode - only report <corrupt> files and errors

.SH EXAMPLES

Typically, cshatag will be called from find:
Check all regular files in the file tree below the current working directory:
.br
# find . -xdev -type f -print0 | xargs -0 cshatag > cshatag.log
# cshatag -recursive . > cshatag.log
.br
Errors like corrupt files will then be printed to stderr
or grep for "corrupt" in cshatag.log.

To remove the extended attributes from all files:
.br
# find . -xdev -type f -print0 | xargs -0 cshatag -remove
# cshatag -recursive -remove .

.SH "RETURN VALUE"

0 Success
.br
1 Wrong number of arguments
.br
2 File could not be opened
2 One or more files could not be opened
.br
3 One or more files is not a regular file
.br
3 File is not a regular file
4 Extended attributes could not be written to one or more files
.br
4 Extended attributs could not be written to file
5 At least one file was found to be corrupt
.br
5 File is corrupt
6 More than one type of error occurred

.SH COMPATIBILITY

Expand All @@ -92,5 +96,3 @@ Copyright 2012 Jakob Unterwurzacher. MIT License.

.SH "SEE ALSO"
shatag(1), sha256sum(1), getfattr(1), setfattr(1)


90 changes: 75 additions & 15 deletions main.go
Expand Up @@ -4,24 +4,68 @@ import (
"flag"
"fmt"
"os"
"path/filepath"
)

var GitVersion = ""

var stats struct {
total int
errors int
inprogress int
corrupt int
timechange int
outdated int
ok int
total int
errorsNotRegular int
errorsOpening int
errorsWritingXattr int
errorsOther int
inprogress int
corrupt int
timechange int
outdated int
ok int
}

var args struct {
remove bool
q bool
qq bool
remove bool
recursive bool
q bool
qq bool
}

// walkFn is used when `cshatag` is called with the `--recursive` option. It is the function called
// for each file or directory visited whilst traversing the file tree.
func walkFn(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Fprintf(os.Stderr, "Error accessing %q: %v\n", path, err)
stats.errorsOpening++
} else if info.Mode().IsRegular() {
checkFile(path)
} else if !info.IsDir() {
if !args.qq {
fmt.Printf("<nonregular> %s\n", path)
}
}
return nil
}

// processArg is called for each command-line argument given. For regular files it will call
// `checkFile`. Directories will be processed recursively provided the `--recursive` flag is set.
// Symbolic links are not followed.
func processArg(fn string) {
fi, err := os.Lstat(fn) // Using Lstat to be consistent with filepath.Walk for symbolic links.
if err != nil {
fmt.Fprintln(os.Stderr, err)
stats.errorsOpening++
} else if fi.Mode().IsRegular() {
checkFile(fn)
} else if fi.IsDir() {
if args.recursive {
filepath.Walk(fn, walkFn)
} else {
fmt.Fprintf(os.Stderr, "Error: %q is a directory, did you mean to use the '-recursive' option?\n", fn)
stats.errorsNotRegular++
}
} else {
fmt.Fprintf(os.Stderr, "Error: %q is not a regular file.\n", fn)
stats.errorsNotRegular++
}
}

func main() {
Expand All @@ -34,6 +78,8 @@ func main() {
flag.BoolVar(&args.remove, "remove", false, "Remove any previously stored extended attributes.")
flag.BoolVar(&args.q, "q", false, "quiet: don't print <ok> files")
flag.BoolVar(&args.qq, "qq", false, "quiet²: Only print <corrupt> files and errors")
flag.BoolVar(&args.recursive, "recursive", false, "Recursively descend into subdirectories. "+
"Symbolic links are not followed.")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "%s %s\n", myname, GitVersion)
fmt.Fprintf(os.Stderr, "Usage: %s [OPTION] FILE [FILE ...]\n", myname)
Expand All @@ -51,13 +97,27 @@ func main() {
}

for _, fn := range flag.Args() {
checkFile(fn)
}
if (stats.ok + stats.outdated + stats.timechange) == stats.total {
os.Exit(0)
processArg(fn)
}

if stats.corrupt > 0 {
os.Exit(5)
}
os.Exit(2)

totalErrors := stats.errorsOpening + stats.errorsNotRegular + stats.errorsWritingXattr +
stats.errorsOther
if totalErrors > 0 {
if stats.errorsOpening == totalErrors {
os.Exit(2)
} else if stats.errorsNotRegular == totalErrors {
os.Exit(3)
} else if stats.errorsWritingXattr == totalErrors {
os.Exit(4)
}
os.Exit(6)
}
if (stats.ok + stats.outdated + stats.timechange) == stats.total {
os.Exit(0)
}
os.Exit(6)
}
1 change: 1 addition & 0 deletions tests/.gitignore
@@ -1,3 +1,4 @@
foo/foo.txt
foo.txt
*.out
*.err
Expand Down
1 change: 1 addition & 0 deletions tests/6.expected
@@ -0,0 +1 @@
Error: "foo" is a directory, did you mean to use the '-recursive' option?
3 changes: 3 additions & 0 deletions tests/7.expected
@@ -0,0 +1,3 @@
<outdated> foo/foo.txt
stored: 0000000000000000000000000000000000000000000000000000000000000000 0000000000.000000000
actual: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1546297200.000000000
18 changes: 18 additions & 0 deletions tests/run_tests.sh
Expand Up @@ -114,6 +114,7 @@ if [[ $RES -ne 3 ]]; then
echo "should have returned an error code 3, but returned $RES"
exit 1
fi
rm -f symlink1

echo "*** Testing timechange ***"
echo same > foo.txt
Expand All @@ -123,4 +124,21 @@ TZ=CET touch -t 201901010001 foo.txt
../cshatag foo.txt > 5.out
diff -u 5.expected 5.out

echo "*** Testing recursive flag ***"
rm -rf foo
mkdir foo
TZ=CET touch -t 201901010000 foo/foo.txt
set +e
../cshatag foo 2> 6.err
RES=$?
set -e
if [[ $RES -ne 3 ]]; then
echo "should have returned error code 3"
exit 1
fi
diff -u 6.expected 6.err
../cshatag --recursive foo > 7.out
diff -u 7.expected 7.out
rm -rf foo

echo "*** ALL TESTS PASSED ***"

0 comments on commit b169f0a

Please sign in to comment.