mirror of
https://github.com/vbatts/go-mtree.git
synced 2025-10-03 20:21:01 +00:00
cmd: validate: add --strict mode
Due to FreeBSD compatibility, we have so far been forced into some unfortunate behaviour with regards to which comparison errors we actually return an error code for. FreeBSD generally does not return errors if a file or keyword only exists in one of the manifests being compared. Some users do not care about FreeBSD compatibility and just want to get errors if there is any difference between a manifest and directory tree. Commit9cdd9152b3
("cmd: gomtree: re-enable errors when there is a Modified entry") added a TODO to implement this, but I've only just got around to implementing it. This patch adds a new --strict flag to "gomtree validate", which causes it to error out if any changes were detected and also disables the FreeBSD compatibility keyword delta filters. This is more akin to what modern users would expect from a manifest validation tool. Fixes:21723a3974
("*: fix comparison of missing keywords") Fixes:9cdd9152b3
("cmd: gomtree: re-enable errors when there is a Modified entry") Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
parent
1ce6aa8db5
commit
b06e4de597
2 changed files with 136 additions and 21 deletions
|
@ -3,6 +3,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -81,10 +82,16 @@ func NewValidateCommand() *cli.Command {
|
||||||
Value: "bsd",
|
Value: "bsd",
|
||||||
Usage: "output the validation results using the given format (bsd, json, path)",
|
Usage: "output the validation results using the given format (bsd, json, path)",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "strict",
|
||||||
|
Usage: "enable strict validation of manifests (any discrepancy will result in an error)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errValidate = errors.New("manifest validation failed")
|
||||||
|
|
||||||
func validateAction(c *cli.Context) error {
|
func validateAction(c *cli.Context) error {
|
||||||
// -list-keywords
|
// -list-keywords
|
||||||
if c.Bool("list-keywords") {
|
if c.Bool("list-keywords") {
|
||||||
|
@ -307,18 +314,14 @@ func validateAction(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if res != nil {
|
if len(res) > 0 {
|
||||||
out := formatFunc(res)
|
out := formatFunc(res, c.Bool("strict"))
|
||||||
if _, err := os.Stdout.Write([]byte(out)); err != nil {
|
if _, err := os.Stdout.Write([]byte(out)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should be a flag. Allowing files to be added and
|
|
||||||
// removed and still returning "it's all good" is simply
|
|
||||||
// unsafe IMO.
|
|
||||||
for _, diff := range res {
|
for _, diff := range res {
|
||||||
if diff.Type() == mtree.Modified {
|
if c.Bool("strict") || diff.Type() == mtree.Modified {
|
||||||
return fmt.Errorf("manifest validation failed")
|
return errValidate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -368,21 +371,19 @@ func validateAction(c *cli.Context) error {
|
||||||
if isTarSpec(specDh) || c.String("tar") != "" {
|
if isTarSpec(specDh) || c.String("tar") != "" {
|
||||||
filters = append(filters, tarKeywordFilter)
|
filters = append(filters, tarKeywordFilter)
|
||||||
}
|
}
|
||||||
filters = append(filters, freebsdCompatKeywordFilter)
|
if !c.Bool("strict") {
|
||||||
|
filters = append(filters, freebsdCompatKeywordFilter)
|
||||||
|
}
|
||||||
res = filterDeltas(res, filters...)
|
res = filterDeltas(res, filters...)
|
||||||
|
|
||||||
if len(res) > 0 {
|
if len(res) > 0 {
|
||||||
out := formatFunc(res)
|
out := formatFunc(res, c.Bool("strict"))
|
||||||
if _, err := os.Stdout.Write([]byte(out)); err != nil {
|
if _, err := os.Stdout.Write([]byte(out)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should be a flag. Allowing files to be added and
|
|
||||||
// removed and still returning "it's all good" is simply
|
|
||||||
// unsafe IMO.
|
|
||||||
for _, diff := range res {
|
for _, diff := range res {
|
||||||
if diff.Type() == mtree.Modified {
|
if c.Bool("strict") || diff.Type() == mtree.Modified {
|
||||||
return fmt.Errorf("manifest validation failed")
|
return errValidate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,9 +393,9 @@ func validateAction(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var formats = map[string]func([]mtree.InodeDelta) string{
|
var formats = map[string]func([]mtree.InodeDelta, bool) string{
|
||||||
// Outputs the errors in the BSD format.
|
// Outputs the errors in the BSD format.
|
||||||
"bsd": func(d []mtree.InodeDelta) string {
|
"bsd": func(d []mtree.InodeDelta, strict bool) string {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
for _, delta := range d {
|
for _, delta := range d {
|
||||||
fmt.Fprintln(&buffer, delta)
|
fmt.Fprintln(&buffer, delta)
|
||||||
|
@ -403,7 +404,7 @@ var formats = map[string]func([]mtree.InodeDelta) string{
|
||||||
},
|
},
|
||||||
|
|
||||||
// Outputs the full result struct in JSON.
|
// Outputs the full result struct in JSON.
|
||||||
"json": func(d []mtree.InodeDelta) string {
|
"json": func(d []mtree.InodeDelta, strict bool) string {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
if err := json.NewEncoder(&buffer).Encode(d); err != nil {
|
if err := json.NewEncoder(&buffer).Encode(d); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -412,10 +413,10 @@ var formats = map[string]func([]mtree.InodeDelta) string{
|
||||||
},
|
},
|
||||||
|
|
||||||
// Outputs only the paths which failed to validate.
|
// Outputs only the paths which failed to validate.
|
||||||
"path": func(d []mtree.InodeDelta) string {
|
"path": func(d []mtree.InodeDelta, strict bool) string {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
for _, delta := range d {
|
for _, delta := range d {
|
||||||
if delta.Type() == mtree.Modified {
|
if strict || delta.Type() == mtree.Modified {
|
||||||
fmt.Fprintln(&buffer, delta.Path())
|
fmt.Fprintln(&buffer, delta.Path())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
114
test/cli/0013.sh
Normal file
114
test/cli/0013.sh
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
name=$(basename $0)
|
||||||
|
root="$(dirname $(dirname $(dirname $0)))"
|
||||||
|
gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree)
|
||||||
|
t=$(mktemp -d /tmp/go-mtree.XXXXXX)
|
||||||
|
|
||||||
|
setfattr -n user.has.xattrs -v "true" "${t}" || exit 0
|
||||||
|
|
||||||
|
echo "[${name}] Running in ${t}"
|
||||||
|
|
||||||
|
## Test that --strict mode will error out in cases that stock gomtree will not.
|
||||||
|
|
||||||
|
pushd ${root}
|
||||||
|
mkdir -p ${t}/root
|
||||||
|
|
||||||
|
mkdir -p ${t}/root/foo/bar
|
||||||
|
echo "valid" >${t}/root/foo/bar/file
|
||||||
|
echo "#!/bin/false" >${t}/root/binary
|
||||||
|
chmod 755 ${t}/root/binary
|
||||||
|
date="2025-09-05T13:05:10"
|
||||||
|
touch -d "$date.12345" ${t}/root/time
|
||||||
|
touch ${t}/root/xattr
|
||||||
|
setfattr -n user.mtree.testing -v "apples and=bananas" ${t}/root/xattr
|
||||||
|
|
||||||
|
keywords_notime=type,uid,gid,nlink,link,mode,flags,xattr,size,sha256
|
||||||
|
keywords=$keywords_notime,time
|
||||||
|
${gomtree} -c -k "$keywords" -p ${t}/root -f ${t}/root.mtree
|
||||||
|
|
||||||
|
# Make sure cp -ar still validates.
|
||||||
|
dir=${t}/copy; cp -ar ${t}/root ${dir}
|
||||||
|
${gomtree} -k "$keywords" -p ${dir} -f ${t}/root.mtree
|
||||||
|
${gomtree} --strict -k "$keywords" -p ${dir} -f ${t}/root.mtree
|
||||||
|
|
||||||
|
# Missing files should not validate.
|
||||||
|
dir=${t}/missing; cp -ar ${t}/root ${dir}
|
||||||
|
rm ${dir}/binary
|
||||||
|
(! ${gomtree} --strict -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
# "size" will tip off gomtree even in stock mode, so re-do with just "type".
|
||||||
|
${gomtree} -k type -p ${dir} -f ${t}/root.mtree
|
||||||
|
(! ${gomtree} --strict -k type -p ${dir} -f ${t}/root.mtree)
|
||||||
|
|
||||||
|
# Extra files should not validate.
|
||||||
|
dir=${t}/extra; cp -ar ${t}/root ${dir}
|
||||||
|
touch ${dir}/newfile
|
||||||
|
(! ${gomtree} --strict -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
# "size" will tip off gomtree even in stock mode, so re-do with just "type".
|
||||||
|
${gomtree} -k type -p ${dir} -f ${t}/root.mtree
|
||||||
|
(! ${gomtree} --strict -k type -p ${dir} -f ${t}/root.mtree)
|
||||||
|
|
||||||
|
# Extra keywords that are missing from the original manifest should not
|
||||||
|
# validate.
|
||||||
|
dir=${t}/root
|
||||||
|
${gomtree} -k "$keywords,md5" -p ${dir} -f ${t}/root.mtree
|
||||||
|
(! ${gomtree} --strict -k "$keywords,md5" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
|
||||||
|
# Mismatched keywords should not validate when comparing two manifests.
|
||||||
|
dir=${t}/root
|
||||||
|
keywords2=type,uid,gid,nlink,mode,flags,xattr,size,sha384,time
|
||||||
|
${gomtree} -c -k "$keywords2" -p ${dir} -f ${t}/root.mtree2
|
||||||
|
${gomtree} -k "$keywords" -f ${t}/root.mtree -f ${t}/root.mtree2
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -f ${t}/root.mtree -f ${t}/root.mtree2)
|
||||||
|
${gomtree} -k "$keywords" -f ${t}/root.mtree2 -f ${t}/root.mtree
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -f ${t}/root.mtree2 -f ${t}/root.mtree)
|
||||||
|
${gomtree} -k "$keywords2" -f ${t}/root.mtree -f ${t}/root.mtree2
|
||||||
|
(! ${gomtree} --strict -k "$keywords2" -f ${t}/root.mtree -f ${t}/root.mtree2)
|
||||||
|
${gomtree} -k "$keywords2" -f ${t}/root.mtree2 -f ${t}/root.mtree
|
||||||
|
(! ${gomtree} --strict -k "$keywords2" -f ${t}/root.mtree2 -f ${t}/root.mtree)
|
||||||
|
|
||||||
|
# Changed xattrs should not validate (even without --strict).
|
||||||
|
dir=${t}/xattr-change; cp -ar ${t}/root ${dir}
|
||||||
|
setfattr -n user.mtree.testing -v "different value" ${dir}/xattr
|
||||||
|
(! ${gomtree} -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
|
||||||
|
# Adding xattrs to existing xattr-set files should not validate (even without
|
||||||
|
# --strict).
|
||||||
|
dir=${t}/xattr-add1; cp -ar ${t}/root ${dir}
|
||||||
|
setfattr -n user.mtree.new -v "newxattr" ${dir}/xattr
|
||||||
|
(! ${gomtree} -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
|
||||||
|
# Adding xattrs to unrelated files should not validate (even without --strict).
|
||||||
|
dir=${t}/xattr-add2; cp -ar ${t}/root ${dir}
|
||||||
|
setfattr -n user.mtree.new -v "newxattr" ${dir}/binary
|
||||||
|
(! ${gomtree} -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
|
||||||
|
# Removing xattrs should not validate (even without --strict).
|
||||||
|
dir=${t}/xattr-rm; cp -ar ${t}/root ${dir}
|
||||||
|
setfattr -x user.mtree.testing ${dir}/xattr
|
||||||
|
(! ${gomtree} -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
|
||||||
|
# time -> tar_time validation should still work even with --strict mode.
|
||||||
|
dir=${t}/tartime; cp -ar ${t}/root ${dir}
|
||||||
|
touch -d "$date.00000" ${dir}/time
|
||||||
|
(! ${gomtree} -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -p ${dir} -f ${t}/root.mtree)
|
||||||
|
(! ${gomtree} --strict -k "$keywords" -p ${dir} -f ${t}/root.mtree)
|
||||||
|
${gomtree} -k "$keywords_notime,tar_time" -p ${dir} -f ${t}/root.mtree
|
||||||
|
${gomtree} --strict -k "$keywords_notime,tar_time" -p ${dir} -f ${t}/root.mtree
|
Loading…
Add table
Add a link
Reference in a new issue