Import upstream version 0.5.2+ds
Debian Janitor
1 year, 4 months ago
0 | kind: pipeline | |
1 | name: default | |
2 | steps: | |
3 | steps: | |
4 | - name: build | |
5 | image: golang | |
6 | commands: | |
7 | - git config --global url."https://".insteadOf git:// | |
8 | - make install.tools | |
9 | - mkdir -p $GOPATH/src/github.com/vbatts && ln -sf $(pwd) $GOPATH/src/github.com/vbatts/go-mtree | |
10 | - make validation | |
11 | - make validation.tags | |
12 | - make build.arches |
0 | language: go | |
1 | go: | |
2 | - "1.x" | |
3 | - "1.11.x" | |
4 | - "1.10.x" | |
5 | - "1.9.x" | |
6 | - "1.8.x" | |
7 | ||
8 | sudo: false | |
9 | ||
10 | before_install: | |
11 | - git config --global url."https://".insteadOf git:// | |
12 | - make install.tools | |
13 | - mkdir -p $GOPATH/src/github.com/vbatts && ln -sf $(pwd) $GOPATH/src/github.com/vbatts/go-mtree | |
14 | ||
15 | install: true | |
16 | ||
17 | script: | |
18 | - make validation | |
19 | - make validation.tags | |
20 | - make build.arches |
5 | 5 | CLEAN_FILES := *~ |
6 | 6 | TAGS := |
7 | 7 | ARCHES := linux,386 linux,amd64 linux,arm linux,arm64 openbsd,amd64 windows,amd64 darwin,amd64 |
8 | GO_VER := go1.14 | |
8 | 9 | |
9 | 10 | default: build validation |
10 | 11 | |
12 | 13 | validation: .test .lint .vet .cli.test |
13 | 14 | |
14 | 15 | .PHONY: validation.tags |
15 | validation.tags: .test.tags .vet.tags .cli.test | |
16 | validation.tags: .test.tags .vet.tags .cli.test .staticcheck | |
17 | ||
18 | .PHONY: gocyclo | |
19 | gocyclo: .gocyclo | |
20 | ||
21 | CLEAN_FILES += .gocyclo | |
22 | ||
23 | .gocyclo: | |
24 | gocyclo -avg -over 15 -ignore 'vendor/*' . && touch $@ | |
25 | ||
26 | .PHONY: staticcheck | |
27 | staticcheck: .staticcheck | |
28 | ||
29 | CLEAN_FILES += .staticcheck | |
30 | ||
31 | .staticcheck: | |
32 | staticcheck . && touch $@ | |
16 | 33 | |
17 | 34 | .PHONY: test |
18 | 35 | test: .test |
19 | 36 | |
20 | 37 | CLEAN_FILES += .test .test.tags |
38 | NO_VENDOR_DIR := $(shell find . -type f -name '*.go' ! -path './vendor*' ! -path './.git*' ! -path './.vscode*' -exec dirname "{}" \; | sort -u) | |
21 | 39 | |
22 | 40 | .test: $(SOURCE_FILES) |
23 | go test -v $$(glide novendor) && touch $@ | |
41 | go test -v $(NO_VENDOR_DIR) && touch $@ | |
24 | 42 | |
25 | 43 | .test.tags: $(SOURCE_FILES) |
26 | set -e ; for tag in $(TAGS) ; do go test -tags $$tag -v $$(glide novendor) ; done && touch $@ | |
44 | set -e ; for tag in $(TAGS) ; do go test -tags $$tag -v $(NO_VENDOR_DIR) ; done && touch $@ | |
27 | 45 | |
28 | 46 | .PHONY: lint |
29 | 47 | lint: .lint |
31 | 49 | CLEAN_FILES += .lint |
32 | 50 | |
33 | 51 | .lint: $(SOURCE_FILES) |
34 | if [[ "$(go version |awk '{ print $3 }')" =~ ^go1\.11\. ]] ; then \ | |
35 | set -e ; for dir in $$(glide novendor) ; do golint -set_exit_status $$dir ; done && touch $@ \ | |
52 | @if [ "$(findstring $(GO_VER),$(shell go version))" != "" ] ; then \ | |
53 | set -e ; for dir in $(NO_VENDOR_DIR) ; do golint -set_exit_status $$dir ; done && touch $@ \ | |
36 | 54 | else \ |
37 | touch $@ ; \ | |
55 | touch $@ ; \ | |
38 | 56 | fi |
39 | 57 | |
40 | 58 | .PHONY: vet |
43 | 61 | CLEAN_FILES += .vet .vet.tags |
44 | 62 | |
45 | 63 | .vet: $(SOURCE_FILES) |
46 | go vet $$(glide novendor) && touch $@ | |
64 | go vet $(NO_VENDOR_DIR) && touch $@ | |
47 | 65 | |
48 | 66 | .vet.tags: $(SOURCE_FILES) |
49 | set -e ; for tag in $(TAGS) ; do go vet -tags $$tag -v $$(glide novendor) ; done && touch $@ | |
67 | set -e ; for tag in $(TAGS) ; do go vet -tags $$tag -v $(NO_VENDOR_DIR) ; done && touch $@ | |
50 | 68 | |
51 | 69 | .PHONY: cli.test |
52 | 70 | cli.test: .cli.test |
54 | 72 | CLEAN_FILES += .cli.test .cli.test.tags |
55 | 73 | |
56 | 74 | .cli.test: $(BUILD) $(wildcard ./test/cli/*.sh) |
57 | @go run ./test/cli.go ./test/cli/*.sh && touch $@ | |
75 | @go run ./test/cli-test/main.go ./test/cli/*.sh && touch $@ | |
58 | 76 | |
59 | 77 | .cli.test.tags: $(BUILD) $(wildcard ./test/cli/*.sh) |
60 | @set -e ; for tag in $(TAGS) ; do go run -tags $$tag ./test/cli.go ./test/cli/*.sh ; done && touch $@ | |
78 | @set -e ; for tag in $(TAGS) ; do go run -tags $$tag ./test/cli-test/main.go ./test/cli/*.sh ; done && touch $@ | |
61 | 79 | |
62 | 80 | .PHONY: build |
63 | 81 | build: $(BUILD) |
64 | 82 | |
65 | 83 | $(BUILD): $(SOURCE_FILES) |
66 | go build -o $(BUILD) $(BUILDPATH) | |
84 | go build -mod=vendor -o $(BUILD) $(BUILDPATH) | |
67 | 85 | |
68 | 86 | install.tools: |
69 | go get -u -v github.com/Masterminds/glide | |
70 | if [[ "$(go version |awk '{ print $3 }')" =~ ^go1\.11\. ]] ; then go get -u golang.org/x/lint/golint ; fi | |
87 | @go install -u github.com/fatih/color@latest ; \ | |
88 | go install -u github.com/fzipp/gocyclo/cmd/gocyclo@latest ; \ | |
89 | go install -u honnef.co/go/tools/cmd/staticcheck@latest ; \ | |
90 | if [ "$(findstring $(GO_VER),$(shell go version))" != "" ] ; then \ | |
91 | go get -u golang.org/x/lint/golint ;\ | |
92 | fi | |
71 | 93 | |
72 | 94 | ./bin: |
73 | 95 | mkdir -p $@ |
80 | 102 | p=$$(echo $$pair | cut -d , -f 1);\ |
81 | 103 | a=$$(echo $$pair | cut -d , -f 2);\ |
82 | 104 | echo "Building $$p/$$a ...";\ |
83 | GOOS=$$p GOARCH=$$a go build -o ./bin/gomtree.$$p.$$a $(BUILDPATH) ;\ | |
84 | done | |
105 | GOOS=$$p GOARCH=$$a go build -mod=vendor -o ./bin/gomtree.$$p.$$a $(BUILDPATH) ;\ | |
106 | done ;\ | |
107 | cd bin ;\ | |
108 | sha1sum gomtree.* > SUMS ;\ | |
109 | sha512sum gomtree.* >> SUMS ;\ | |
110 | cd - | |
85 | 111 | |
86 | 112 | clean: |
87 | 113 | rm -rf $(BUILD) $(CLEAN_FILES) |
0 | 0 | # go-mtree |
1 | 1 | |
2 | [![Build Status](https://travis-ci.org/vbatts/go-mtree.svg?branch=master)](https://travis-ci.org/vbatts/go-mtree) [![Go Report Card](https://goreportcard.com/badge/github.com/vbatts/go-mtree)](https://goreportcard.com/report/github.com/vbatts/go-mtree) | |
2 | [![Go](https://github.com/vbatts/go-mtree/actions/workflows/go.yml/badge.svg)](https://github.com/vbatts/go-mtree/actions/workflows/go.yml) | |
3 | [![Go Report Card](https://goreportcard.com/badge/github.com/vbatts/go-mtree)](https://goreportcard.com/report/github.com/vbatts/go-mtree) | |
3 | 4 | |
4 | 5 | `mtree` is a filesystem hierarchy validation tooling and format. |
5 | 6 | This is a library and simple cli tool for [mtree(8)][mtree(8)] support. |
10 | 11 | |
11 | 12 | There is also an [mtree port for Linux][archiecobbs/mtree-port] though it is |
12 | 13 | not widely packaged for Linux distributions. |
13 | ||
14 | 14 | |
15 | 15 | ## Format |
16 | 16 | |
25 | 25 | `xattr` and `tar_time`. If you include these keywords, the FreeBSD `mtree` |
26 | 26 | will fail, as they are unknown keywords to that implementation. |
27 | 27 | |
28 | To have `go-mtree` produce specifications that will be | |
28 | To have `go-mtree` produce specifications that will be | |
29 | 29 | strictly compatible with the BSD `mtree`, use the `-bsd-keywords` flag when |
30 | 30 | creating a manifest. This will make sure that only the keywords supported by |
31 | 31 | BSD `mtree` are used in the program. |
32 | ||
33 | 32 | |
34 | 33 | ### Typical form |
35 | 34 | |
50 | 49 | path, is provided the keywords and the unique values for each path. Any common |
51 | 50 | keyword and values are established in the `/set` command. |
52 | 51 | |
53 | ||
54 | 52 | ### Extended attributes form |
55 | 53 | |
56 | 54 | ```mtree |
96 | 94 | validate this manifest against a filesystem hierarchy that's on disk, and vice versa. |
97 | 95 | |
98 | 96 | Notice that for the output of creating a validation manifest from a tar file, the default behavior |
99 | for evaluating a notion of time is to use the `tar_time` keyword. In the | |
100 | "filesystem hierarchy" format of mtree, `time` is being evaluated with | |
97 | for evaluating a notion of time is to use the `tar_time` keyword. In the | |
98 | "filesystem hierarchy" format of mtree, `time` is being evaluated with | |
101 | 99 | nanosecond precision. However, GNU tar truncates a file's modification time |
102 | to 1-second precision. That is, if a file's full modification time is | |
100 | to 1-second precision. That is, if a file's full modification time is | |
103 | 101 | 123456789.123456789, the "tar time" equivalent would be 123456789.000000000. |
104 | 102 | This way, if you validate a manifest created using a tar file against an |
105 | 103 | actual root directory, there will be no complaints from `go-mtree` so long as the |
106 | 104 | 1-second precision time of a file in the root directory is the same. |
107 | 105 | |
108 | ||
109 | 106 | ## Usage |
110 | 107 | |
111 | 108 | To use the Go programming language library, see [the docs][godoc]. |
112 | 109 | |
113 | 110 | To use the command line tool, first [build it](#Building), then the following. |
114 | 111 | |
115 | ||
116 | 112 | ### Create a manifest |
117 | 113 | |
118 | 114 | This will also include the sha512 digest of the files. |
121 | 117 | gomtree -c -K sha512digest -p . > /tmp/root.mtree |
122 | 118 | ``` |
123 | 119 | |
124 | With a tar file: | |
120 | With a tar file: | |
125 | 121 | |
126 | 122 | ```bash |
127 | 123 | gomtree -c -K sha512digest -T sometarfile.tar > /tmp/tar.mtree |
172 | 168 | sha512digest |
173 | 169 | ``` |
174 | 170 | |
175 | ||
176 | 171 | ## Building |
177 | 172 | |
178 | 173 | Either: |
192 | 187 | ## Testing |
193 | 188 | |
194 | 189 | On Linux: |
190 | ||
195 | 191 | ```bash |
196 | 192 | cd $GOPATH/src/github.com/vbatts/go-mtree |
197 | 193 | make |
198 | 194 | ``` |
199 | 195 | |
200 | 196 | On FreeBSD: |
197 | ||
201 | 198 | ```bash |
202 | 199 | cd $GOPATH/src/github.com/vbatts/go-mtree |
203 | 200 | gmake |
204 | 201 | ``` |
205 | ||
206 | 202 | |
207 | 203 | [mtree(8)]: https://www.freebsd.org/cgi/man.cgi?mtree(8) |
208 | 204 | [libarchive-formats(5)]: https://www.freebsd.org/cgi/man.cgi?query=libarchive-formats&sektion=5&n=1 |
17 | 17 | |
18 | 18 | return Compare(dh, newDh, keywords) |
19 | 19 | } |
20 | ||
21 | // TarCheck is the tar equivalent of checking a file hierarchy spec against a | |
22 | // tar stream to determine if files have been changed. This is precisely | |
23 | // equivalent to Compare(dh, tarDH, keywords). | |
24 | func TarCheck(tarDH, dh *DirectoryHierarchy, keywords []Keyword) ([]InodeDelta, error) { | |
25 | if keywords == nil { | |
26 | return Compare(dh, tarDH, dh.UsedKeywords()) | |
27 | } | |
28 | return Compare(dh, tarDH, keywords) | |
29 | } |
8 | 8 | "time" |
9 | 9 | ) |
10 | 10 | |
11 | // simple walk of current directory, and imediately check it. | |
11 | // simple walk of current directory, and immediately check it. | |
12 | 12 | // may not be parallelizable. |
13 | 13 | func TestCheck(t *testing.T) { |
14 | 14 | dh, err := Walk(".", nil, append(DefaultKeywords, []Keyword{"sha1", "xattr"}...), nil) |
272 | 272 | .. |
273 | 273 | ` |
274 | 274 | dh, err = ParseSpec(bytes.NewBufferString(spec)) |
275 | if err != nil { | |
276 | t.Error(err) | |
277 | } | |
275 | 278 | |
276 | 279 | res, err = Check(dir, dh, nil, nil) |
277 | 280 | if err != nil { |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | 4 | "encoding/json" |
5 | "flag" | |
6 | 5 | "fmt" |
7 | 6 | "io" |
8 | 7 | "io/ioutil" |
10 | 9 | "strings" |
11 | 10 | |
12 | 11 | "github.com/sirupsen/logrus" |
12 | cli "github.com/urfave/cli/v2" | |
13 | 13 | "github.com/vbatts/go-mtree" |
14 | 14 | ) |
15 | 15 | |
16 | var ( | |
17 | // Flags common with mtree(8) | |
18 | flCreate = flag.Bool("c", false, "create a directory hierarchy spec") | |
19 | flFile = flag.String("f", "", "directory hierarchy spec to validate") | |
20 | flPath = flag.String("p", "", "root path that the hierarchy spec is relative to") | |
21 | flAddKeywords = flag.String("K", "", "Add the specified (delimited by comma or space) keywords to the current set of keywords") | |
22 | flUseKeywords = flag.String("k", "", "Use the specified (delimited by comma or space) keywords as the current set of keywords") | |
23 | flDirectoryOnly = flag.Bool("d", false, "Ignore everything except directory type files") | |
24 | flUpdateAttributes = flag.Bool("u", false, "Modify the owner, group, permissions and xattrs of files, symbolic links and devices, to match the provided specification. This is not compatible with '-T'.") | |
25 | ||
26 | // Flags unique to gomtree | |
27 | flListKeywords = flag.Bool("list-keywords", false, "List the keywords available") | |
28 | flResultFormat = flag.String("result-format", "bsd", "output the validation results using the given format (bsd, json, path)") | |
29 | flTar = flag.String("T", "", "use tar archive to create or validate a directory hierarchy spec (\"-\" indicates stdin)") | |
30 | flBsdKeywords = flag.Bool("bsd-keywords", false, "only operate on keywords that are supported by upstream mtree(8)") | |
31 | flListUsedKeywords = flag.Bool("list-used", false, "list all the keywords found in a validation manifest") | |
32 | flDebug = flag.Bool("debug", false, "output debug info to STDERR") | |
33 | flVersion = flag.Bool("version", false, "display the version of this tool") | |
34 | ) | |
35 | ||
36 | 16 | func main() { |
37 | // so that defers cleanly exec | |
38 | if err := app(); err != nil { | |
17 | app := cli.NewApp() | |
18 | app.Name = mtree.AppName | |
19 | app.Usage = "map a directory hierarchy" | |
20 | app.Version = mtree.Version | |
21 | // cli docs --> https://github.com/urfave/cli/blob/master/docs/v2/manual.md | |
22 | app.Flags = []cli.Flag{ | |
23 | // Flags common with mtree(8) | |
24 | &cli.BoolFlag{ | |
25 | Name: "create", | |
26 | Aliases: []string{"c"}, | |
27 | Usage: "Create a directory hierarchy spec", | |
28 | }, | |
29 | &cli.StringSliceFlag{ | |
30 | Name: "file", | |
31 | Aliases: []string{"f"}, | |
32 | Usage: "Directory hierarchy spec to validate", | |
33 | }, | |
34 | &cli.StringFlag{ | |
35 | Name: "path", | |
36 | Aliases: []string{"p"}, | |
37 | Usage: "Root path that the hierarchy spec is relative to", | |
38 | }, | |
39 | &cli.StringFlag{ | |
40 | Name: "add-keywords", | |
41 | Aliases: []string{"K"}, | |
42 | Usage: "Add the specified (delimited by comma or space) keywords to the current set of keywords", | |
43 | }, | |
44 | &cli.StringFlag{ | |
45 | Name: "use-keywords", | |
46 | Aliases: []string{"k"}, | |
47 | Usage: "Use only the specified (delimited by comma or space) keywords as the current set of keywords", | |
48 | }, | |
49 | &cli.BoolFlag{ | |
50 | Name: "directory-only", | |
51 | Aliases: []string{"d"}, | |
52 | Usage: "Ignore everything except directory type files", | |
53 | }, | |
54 | &cli.BoolFlag{ | |
55 | Name: "update-attributes", | |
56 | Aliases: []string{"u"}, | |
57 | Usage: "Modify the owner, group, permissions and xattrs of files, symbolic links and devices, to match the provided specification. This is not compatible with '-T'.", | |
58 | }, | |
59 | ||
60 | // Flags unique to gomtree | |
61 | &cli.BoolFlag{ | |
62 | Name: "debug", | |
63 | Usage: "Output debug info to STDERR", | |
64 | Value: false, | |
65 | }, | |
66 | &cli.BoolFlag{ | |
67 | Name: "list-keywords", | |
68 | Usage: "List the keywords available", | |
69 | }, | |
70 | &cli.BoolFlag{ | |
71 | Name: "list-used", | |
72 | Usage: "List all the keywords found in a validation manifest", | |
73 | }, | |
74 | &cli.BoolFlag{ | |
75 | Name: "bsd-keywords", | |
76 | Usage: "Only operate on keywords that are supported by upstream mtree(8)", | |
77 | }, | |
78 | &cli.StringFlag{ | |
79 | Name: "tar", | |
80 | Aliases: []string{"T"}, | |
81 | Value: "", | |
82 | Usage: `Use tar archive to create or validate a directory hierarchy spec ("-" indicates stdin)`, | |
83 | }, | |
84 | &cli.StringFlag{ | |
85 | Name: "result-format", | |
86 | Value: "bsd", | |
87 | Usage: "output the validation results using the given format (bsd, json, path)", | |
88 | }, | |
89 | } | |
90 | app.Action = func(c *cli.Context) error { | |
91 | return mainApp(c) | |
92 | } | |
93 | if err := app.Run(os.Args); err != nil { | |
39 | 94 | logrus.Fatal(err) |
40 | 95 | } |
41 | 96 | } |
42 | 97 | |
43 | func app() error { | |
44 | flag.Parse() | |
45 | ||
46 | if *flDebug { | |
98 | func mainApp(c *cli.Context) error { | |
99 | if c.Bool("debug") { | |
47 | 100 | os.Setenv("DEBUG", "1") |
48 | 101 | logrus.SetLevel(logrus.DebugLevel) |
49 | 102 | } |
50 | 103 | |
51 | if *flVersion { | |
52 | fmt.Printf("%s :: %s\n", mtree.AppName, mtree.Version) | |
53 | return nil | |
54 | } | |
55 | ||
56 | 104 | // -list-keywords |
57 | if *flListKeywords { | |
105 | if c.Bool("list-keywords") { | |
58 | 106 | fmt.Println("Available keywords:") |
59 | 107 | for k := range mtree.KeywordFuncs { |
60 | 108 | fmt.Print(" ") |
71 | 119 | } |
72 | 120 | |
73 | 121 | // --result-format |
74 | formatFunc, ok := formats[*flResultFormat] | |
122 | formatFunc, ok := formats[c.String("result-format")] | |
75 | 123 | if !ok { |
76 | return fmt.Errorf("invalid output format: %s", *flResultFormat) | |
124 | return fmt.Errorf("invalid output format: %s", c.String("result-format")) | |
77 | 125 | } |
78 | 126 | |
79 | 127 | var ( |
83 | 131 | ) |
84 | 132 | |
85 | 133 | // -k <keywords> |
86 | if *flUseKeywords != "" { | |
87 | tmpKeywords = splitKeywordsArg(*flUseKeywords) | |
134 | if c.String("use-keywords") != "" { | |
135 | tmpKeywords = splitKeywordsArg(c.String("use-keywords")) | |
88 | 136 | if !mtree.InKeywordSlice("type", tmpKeywords) { |
89 | 137 | tmpKeywords = append([]mtree.Keyword{"type"}, tmpKeywords...) |
90 | 138 | } |
91 | 139 | } else { |
92 | if *flTar != "" { | |
140 | if c.String("tar") != "" { | |
93 | 141 | tmpKeywords = mtree.DefaultTarKeywords[:] |
94 | 142 | } else { |
95 | 143 | tmpKeywords = mtree.DefaultKeywords[:] |
97 | 145 | } |
98 | 146 | |
99 | 147 | // -K <keywords> |
100 | if *flAddKeywords != "" { | |
101 | for _, kw := range splitKeywordsArg(*flAddKeywords) { | |
148 | if c.String("add-keywords") != "" { | |
149 | for _, kw := range splitKeywordsArg(c.String("add-keywords")) { | |
102 | 150 | if !mtree.InKeywordSlice(kw, tmpKeywords) { |
103 | 151 | tmpKeywords = append(tmpKeywords, kw) |
104 | 152 | } |
106 | 154 | } |
107 | 155 | |
108 | 156 | // -bsd-keywords |
109 | if *flBsdKeywords { | |
157 | if c.Bool("bsd-keywords") { | |
110 | 158 | for _, k := range tmpKeywords { |
111 | 159 | if mtree.Keyword(k).Bsd() { |
112 | 160 | currentKeywords = append(currentKeywords, k) |
133 | 181 | ) |
134 | 182 | |
135 | 183 | // -f <file> |
136 | if *flFile != "" && !*flCreate { | |
184 | if len(c.StringSlice("file")) > 0 && !c.Bool("create") { | |
137 | 185 | // load the hierarchy, if we're not creating a new spec |
138 | fh, err := os.Open(*flFile) | |
186 | fh, err := os.Open(c.StringSlice("file")[0]) | |
139 | 187 | if err != nil { |
140 | 188 | return err |
141 | 189 | } |
151 | 199 | } |
152 | 200 | |
153 | 201 | // -list-used |
154 | if *flListUsedKeywords { | |
202 | if c.Bool("list-used") { | |
155 | 203 | if specDh == nil { |
156 | 204 | return fmt.Errorf("no specification provided. please provide a validation manifest") |
157 | 205 | } |
158 | 206 | |
159 | if *flResultFormat == "json" { | |
160 | // if they're asking for json, give it to them | |
161 | data := map[string][]mtree.Keyword{*flFile: specKeywords} | |
162 | buf, err := json.MarshalIndent(data, "", " ") | |
163 | if err != nil { | |
164 | return err | |
165 | } | |
166 | fmt.Println(string(buf)) | |
207 | if c.String("result-format") == "json" { | |
208 | for _, file := range c.StringSlice("file") { | |
209 | // if they're asking for json, give it to them | |
210 | data := map[string][]mtree.Keyword{file: specKeywords} | |
211 | buf, err := json.MarshalIndent(data, "", " ") | |
212 | if err != nil { | |
213 | return err | |
214 | } | |
215 | fmt.Println(string(buf)) | |
216 | } | |
167 | 217 | } else { |
168 | fmt.Printf("Keywords used in [%s]:\n", *flFile) | |
169 | for _, kw := range specKeywords { | |
170 | fmt.Printf(" %s", kw) | |
171 | if _, ok := mtree.KeywordFuncs[kw]; !ok { | |
172 | fmt.Print(" (unsupported)") | |
218 | for _, file := range c.StringSlice("file") { | |
219 | fmt.Printf("Keywords used in [%s]:\n", file) | |
220 | for _, kw := range specKeywords { | |
221 | fmt.Printf(" %s", kw) | |
222 | if _, ok := mtree.KeywordFuncs[kw]; !ok { | |
223 | fmt.Print(" (unsupported)") | |
224 | } | |
225 | fmt.Printf("\n") | |
173 | 226 | } |
174 | fmt.Printf("\n") | |
175 | 227 | } |
176 | 228 | } |
177 | 229 | return nil |
179 | 231 | |
180 | 232 | if specKeywords != nil { |
181 | 233 | // If we didn't actually change the set of keywords, we can just use specKeywords. |
182 | if *flUseKeywords == "" && *flAddKeywords == "" { | |
234 | if c.String("use-keywords") == "" && c.String("add-keywords") == "" { | |
183 | 235 | currentKeywords = specKeywords |
184 | 236 | } |
185 | 237 | |
193 | 245 | } |
194 | 246 | |
195 | 247 | // -p and -T are mutually exclusive |
196 | if *flPath != "" && *flTar != "" { | |
248 | if c.String("path") != "" && c.String("tar") != "" { | |
197 | 249 | return fmt.Errorf("options -T and -p are mutually exclusive") |
198 | 250 | } |
199 | 251 | |
200 | 252 | // -p <path> |
201 | 253 | var rootPath = "." |
202 | if *flPath != "" { | |
203 | rootPath = *flPath | |
254 | if c.String("path") != "" { | |
255 | rootPath = c.String("path") | |
204 | 256 | } |
205 | 257 | |
206 | 258 | excludes := []mtree.ExcludeFunc{} |
207 | 259 | // -d |
208 | if *flDirectoryOnly { | |
260 | if c.Bool("directory-only") { | |
209 | 261 | excludes = append(excludes, mtree.ExcludeNonDirectories) |
210 | 262 | } |
211 | 263 | |
212 | 264 | // -u |
213 | 265 | // Failing early here. Processing is done below. |
214 | if *flUpdateAttributes && *flTar != "" { | |
266 | if c.Bool("update-attributes") && c.String("tar") != "" { | |
215 | 267 | return fmt.Errorf("ERROR: -u can not be used with -T") |
216 | 268 | } |
217 | 269 | |
218 | 270 | // -T <tar file> |
219 | if *flTar != "" { | |
271 | if c.String("tar") != "" { | |
220 | 272 | var input io.Reader |
221 | if *flTar == "-" { | |
273 | if c.String("tar") == "-" { | |
222 | 274 | input = os.Stdin |
223 | 275 | } else { |
224 | fh, err := os.Open(*flTar) | |
276 | fh, err := os.Open(c.String("tar")) | |
225 | 277 | if err != nil { |
226 | 278 | return err |
227 | 279 | } |
238 | 290 | } |
239 | 291 | var err error |
240 | 292 | stateDh, err = ts.Hierarchy() |
293 | if err != nil { | |
294 | return err | |
295 | } | |
296 | } else if len(c.StringSlice("file")) > 1 { | |
297 | // load this second hierarchy file provided | |
298 | fh, err := os.Open(c.StringSlice("file")[1]) | |
299 | if err != nil { | |
300 | return err | |
301 | } | |
302 | stateDh, err = mtree.ParseSpec(fh) | |
303 | fh.Close() | |
241 | 304 | if err != nil { |
242 | 305 | return err |
243 | 306 | } |
250 | 313 | } |
251 | 314 | |
252 | 315 | // -u |
253 | if *flUpdateAttributes && stateDh != nil { | |
316 | if c.Bool("update-attributes") && stateDh != nil { | |
254 | 317 | // -u |
255 | 318 | // this comes before the next case, intentionally. |
256 | 319 | result, err := mtree.Update(rootPath, specDh, mtree.DefaultUpdateKeywords, nil) |
257 | 320 | if err != nil { |
258 | 321 | return err |
259 | 322 | } |
260 | if result != nil && len(result) > 0 { | |
323 | if len(result) > 0 { | |
261 | 324 | fmt.Printf("%#v\n", result) |
262 | 325 | } |
263 | 326 | |
278 | 341 | // unsafe IMO. |
279 | 342 | for _, diff := range res { |
280 | 343 | if diff.Type() == mtree.Modified { |
281 | return fmt.Errorf("mainfest validation failed") | |
344 | return fmt.Errorf("manifest validation failed") | |
282 | 345 | } |
283 | 346 | } |
284 | 347 | } |
287 | 350 | } |
288 | 351 | |
289 | 352 | // -c |
290 | if *flCreate { | |
353 | if c.Bool("create") { | |
291 | 354 | fh := os.Stdout |
292 | if *flFile != "" { | |
293 | fh, err = os.Create(*flFile) | |
355 | if len(c.StringSlice("file")) > 0 { | |
356 | fh, err = os.Create(c.StringSlice("file")[0]) | |
294 | 357 | if err != nil { |
295 | 358 | return err |
296 | 359 | } |
322 | 385 | return err |
323 | 386 | } |
324 | 387 | if res != nil { |
325 | if isTarSpec(specDh) || *flTar != "" { | |
388 | if isTarSpec(specDh) || c.String("tar") != "" { | |
326 | 389 | res = filterMissingKeywords(res) |
327 | 390 | } |
328 | 391 | |
336 | 399 | // unsafe IMO. |
337 | 400 | for _, diff := range res { |
338 | 401 | if diff.Type() == mtree.Modified { |
339 | return fmt.Errorf("mainfest validation failed") | |
402 | return fmt.Errorf("manifest validation failed") | |
340 | 403 | } |
341 | 404 | } |
342 | 405 | } |
28 | 28 | // have different values (or have not been set in one of the |
29 | 29 | // manifests). |
30 | 30 | Modified DifferenceType = "modified" |
31 | ||
32 | // Same represents the case where two files are the same. These are | |
33 | // only generated from CompareSame(). | |
34 | Same DifferenceType = "same" | |
31 | 35 | |
32 | 36 | // ErrorDifference represents an attempted update to the values of |
33 | 37 | // a keyword that failed |
156 | 160 | |
157 | 161 | // New returns the value of the KeyDeltaVal entry in the "new" DirectoryHierarchy |
158 | 162 | // (as determined by the ordering of parameters to Compare). Returns nil if |
159 | // there was no entry in the "old" DirectoryHierarchy. | |
163 | // there was no entry in the "new" DirectoryHierarchy. | |
160 | 164 | func (k KeyDelta) New() *string { |
161 | 165 | if k.diff == Modified || k.diff == Extra { |
162 | return sPtr(k.old) | |
166 | return sPtr(k.new) | |
163 | 167 | } |
164 | 168 | return nil |
165 | 169 | } |
304 | 308 | name: name, |
305 | 309 | old: diff.Old.Value(), |
306 | 310 | new: diff.New.Value(), |
311 | }) | |
312 | } | |
313 | } | |
314 | } | |
315 | ||
316 | return results, nil | |
317 | } | |
318 | ||
319 | // compare is the actual workhorse for Compare() and CompareSame() | |
320 | func compare(oldDh, newDh *DirectoryHierarchy, keys []Keyword, same bool) ([]InodeDelta, error) { | |
321 | // Represents the new and old states for an entry. | |
322 | type stateT struct { | |
323 | Old *Entry | |
324 | New *Entry | |
325 | } | |
326 | ||
327 | // To deal with different orderings of the entries, use a path-keyed | |
328 | // map to make sure we don't start comparing unrelated entries. | |
329 | diffs := map[string]*stateT{} | |
330 | ||
331 | // First, iterate over the old hierarchy. If nil, pretend it's empty. | |
332 | if oldDh != nil { | |
333 | for _, e := range oldDh.Entries { | |
334 | if e.Type == RelativeType || e.Type == FullType { | |
335 | path, err := e.Path() | |
336 | if err != nil { | |
337 | return nil, err | |
338 | } | |
339 | ||
340 | // Cannot take &kv because it's the iterator. | |
341 | cEntry := new(Entry) | |
342 | *cEntry = e | |
343 | ||
344 | _, ok := diffs[path] | |
345 | if !ok { | |
346 | diffs[path] = &stateT{} | |
347 | } | |
348 | diffs[path].Old = cEntry | |
349 | } | |
350 | } | |
351 | } | |
352 | ||
353 | // Then, iterate over the new hierarchy. If nil, pretend it's empty. | |
354 | if newDh != nil { | |
355 | for _, e := range newDh.Entries { | |
356 | if e.Type == RelativeType || e.Type == FullType { | |
357 | path, err := e.Path() | |
358 | if err != nil { | |
359 | return nil, err | |
360 | } | |
361 | ||
362 | // Cannot take &kv because it's the iterator. | |
363 | cEntry := new(Entry) | |
364 | *cEntry = e | |
365 | ||
366 | _, ok := diffs[path] | |
367 | if !ok { | |
368 | diffs[path] = &stateT{} | |
369 | } | |
370 | diffs[path].New = cEntry | |
371 | } | |
372 | } | |
373 | } | |
374 | ||
375 | // Now we compute the diff. | |
376 | var results []InodeDelta | |
377 | for path, diff := range diffs { | |
378 | // Invalid | |
379 | if diff.Old == nil && diff.New == nil { | |
380 | return nil, fmt.Errorf("invalid state: both old and new are nil: path=%s", path) | |
381 | } | |
382 | ||
383 | switch { | |
384 | // Missing | |
385 | case diff.New == nil: | |
386 | results = append(results, InodeDelta{ | |
387 | diff: Missing, | |
388 | path: path, | |
389 | old: *diff.Old, | |
390 | }) | |
391 | ||
392 | // Extra | |
393 | case diff.Old == nil: | |
394 | results = append(results, InodeDelta{ | |
395 | diff: Extra, | |
396 | path: path, | |
397 | new: *diff.New, | |
398 | }) | |
399 | ||
400 | // Modified | |
401 | default: | |
402 | changed, err := compareEntry(*diff.Old, *diff.New) | |
403 | if err != nil { | |
404 | return nil, fmt.Errorf("comparison failed %s: %s", path, err) | |
405 | } | |
406 | ||
407 | // Now remove "changed" entries that don't match the keys. | |
408 | if keys != nil { | |
409 | var filterChanged []KeyDelta | |
410 | for _, keyDiff := range changed { | |
411 | if InKeywordSlice(keyDiff.name.Prefix(), keys) { | |
412 | filterChanged = append(filterChanged, keyDiff) | |
413 | } | |
414 | } | |
415 | changed = filterChanged | |
416 | } | |
417 | ||
418 | // Check if there were any actual changes. | |
419 | if len(changed) > 0 { | |
420 | results = append(results, InodeDelta{ | |
421 | diff: Modified, | |
422 | path: path, | |
423 | old: *diff.Old, | |
424 | new: *diff.New, | |
425 | keys: changed, | |
426 | }) | |
427 | } else if same { | |
428 | // this means that nothing changed, i.e. that | |
429 | // the files are the same. | |
430 | results = append(results, InodeDelta{ | |
431 | diff: Same, | |
432 | path: path, | |
433 | old: *diff.Old, | |
434 | new: *diff.New, | |
435 | keys: changed, | |
307 | 436 | }) |
308 | 437 | } |
309 | 438 | } |
331 | 460 | // NB: The order of the parameters matters (old, new) because Extra and |
332 | 461 | // Missing are considered as different discrepancy types. |
333 | 462 | func Compare(oldDh, newDh *DirectoryHierarchy, keys []Keyword) ([]InodeDelta, error) { |
334 | // Represents the new and old states for an entry. | |
335 | type stateT struct { | |
336 | Old *Entry | |
337 | New *Entry | |
338 | } | |
339 | ||
340 | // To deal with different orderings of the entries, use a path-keyed | |
341 | // map to make sure we don't start comparing unrelated entries. | |
342 | diffs := map[string]*stateT{} | |
343 | ||
344 | // First, iterate over the old hierarchy. If nil, pretend it's empty. | |
345 | if oldDh != nil { | |
346 | for _, e := range oldDh.Entries { | |
347 | if e.Type == RelativeType || e.Type == FullType { | |
348 | path, err := e.Path() | |
349 | if err != nil { | |
350 | return nil, err | |
351 | } | |
352 | ||
353 | // Cannot take &kv because it's the iterator. | |
354 | cEntry := new(Entry) | |
355 | *cEntry = e | |
356 | ||
357 | _, ok := diffs[path] | |
358 | if !ok { | |
359 | diffs[path] = &stateT{} | |
360 | } | |
361 | diffs[path].Old = cEntry | |
362 | } | |
363 | } | |
364 | } | |
365 | ||
366 | // Then, iterate over the new hierarchy. If nil, pretend it's empty. | |
367 | if newDh != nil { | |
368 | for _, e := range newDh.Entries { | |
369 | if e.Type == RelativeType || e.Type == FullType { | |
370 | path, err := e.Path() | |
371 | if err != nil { | |
372 | return nil, err | |
373 | } | |
374 | ||
375 | // Cannot take &kv because it's the iterator. | |
376 | cEntry := new(Entry) | |
377 | *cEntry = e | |
378 | ||
379 | _, ok := diffs[path] | |
380 | if !ok { | |
381 | diffs[path] = &stateT{} | |
382 | } | |
383 | diffs[path].New = cEntry | |
384 | } | |
385 | } | |
386 | } | |
387 | ||
388 | // Now we compute the diff. | |
389 | var results []InodeDelta | |
390 | for path, diff := range diffs { | |
391 | // Invalid | |
392 | if diff.Old == nil && diff.New == nil { | |
393 | return nil, fmt.Errorf("invalid state: both old and new are nil: path=%s", path) | |
394 | } | |
395 | ||
396 | switch { | |
397 | // Missing | |
398 | case diff.New == nil: | |
399 | results = append(results, InodeDelta{ | |
400 | diff: Missing, | |
401 | path: path, | |
402 | old: *diff.Old, | |
403 | }) | |
404 | ||
405 | // Extra | |
406 | case diff.Old == nil: | |
407 | results = append(results, InodeDelta{ | |
408 | diff: Extra, | |
409 | path: path, | |
410 | new: *diff.New, | |
411 | }) | |
412 | ||
413 | // Modified | |
414 | default: | |
415 | changed, err := compareEntry(*diff.Old, *diff.New) | |
416 | if err != nil { | |
417 | return nil, fmt.Errorf("comparison failed %s: %s", path, err) | |
418 | } | |
419 | ||
420 | // Now remove "changed" entries that don't match the keys. | |
421 | if keys != nil { | |
422 | var filterChanged []KeyDelta | |
423 | for _, keyDiff := range changed { | |
424 | if InKeywordSlice(keyDiff.name.Prefix(), keys) { | |
425 | filterChanged = append(filterChanged, keyDiff) | |
426 | } | |
427 | } | |
428 | changed = filterChanged | |
429 | } | |
430 | ||
431 | // Check if there were any actual changes. | |
432 | if len(changed) > 0 { | |
433 | results = append(results, InodeDelta{ | |
434 | diff: Modified, | |
435 | path: path, | |
436 | old: *diff.Old, | |
437 | new: *diff.New, | |
438 | keys: changed, | |
439 | }) | |
440 | } | |
441 | } | |
442 | } | |
443 | ||
444 | return results, nil | |
445 | } | |
463 | return compare(oldDh, newDh, keys, false) | |
464 | } | |
465 | ||
466 | // CompareSame is the same as Compare, except it also includes the entries | |
467 | // that are the same with a Same DifferenceType. | |
468 | func CompareSame(oldDh, newDh *DirectoryHierarchy, keys []Keyword) ([]InodeDelta, error) { | |
469 | return compare(oldDh, newDh, keys, true) | |
470 | } |
11 | 11 | "time" |
12 | 12 | ) |
13 | 13 | |
14 | // simple walk of current directory, and imediately check it. | |
14 | // simple walk of current directory, and immediately check it. | |
15 | 15 | // may not be parallelizable. |
16 | 16 | func TestCompare(t *testing.T) { |
17 | 17 | old, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil) |
34 | 34 | } |
35 | 35 | } |
36 | 36 | |
37 | //gocyclo:ignore | |
37 | 38 | func TestCompareModified(t *testing.T) { |
38 | 39 | dir, err := ioutil.TempDir("", "test-compare-modified") |
39 | 40 | if err != nil { |
89 | 90 | } |
90 | 91 | |
91 | 92 | // These cannot fail. |
92 | tmpfile, _ = filepath.Rel(dir, tmpfile) | |
93 | tmpdir, _ = filepath.Rel(dir, tmpdir) | |
94 | 93 | tmpsubfile, _ = filepath.Rel(dir, tmpsubfile) |
95 | 94 | |
96 | 95 | for _, diff := range diffs { |
115 | 114 | } |
116 | 115 | } |
117 | 116 | |
117 | //gocyclo:ignore | |
118 | 118 | func TestCompareMissing(t *testing.T) { |
119 | 119 | dir, err := ioutil.TempDir("", "test-compare-missing") |
120 | 120 | if err != nil { |
206 | 206 | } |
207 | 207 | } |
208 | 208 | |
209 | //gocyclo:ignore | |
209 | 210 | func TestCompareExtra(t *testing.T) { |
210 | 211 | dir, err := ioutil.TempDir("", "test-compare-extra") |
211 | 212 | if err != nil { |
339 | 340 | } |
340 | 341 | } |
341 | 342 | |
343 | //gocyclo:ignore | |
342 | 344 | func TestTarCompare(t *testing.T) { |
343 | 345 | dir, err := ioutil.TempDir("", "test-compare-tar") |
344 | 346 | if err != nil { |
70 | 70 | return fn |
71 | 71 | } |
72 | 72 | |
73 | //gocyclo:ignore | |
73 | 74 | func TestCheckFsEval(t *testing.T) { |
74 | 75 | dir, err := ioutil.TempDir("", "test-check-fs-eval") |
75 | 76 | if err != nil { |
0 | hash: 8b0df7f603e6b580aa2640d99d3fa7430198f7db89321ff2abf76efa969d14c2 | |
1 | updated: 2018-08-20T07:56:40.333174254-04:00 | |
2 | imports: | |
3 | - name: github.com/fatih/color | |
4 | version: 5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4 | |
5 | - name: github.com/sirupsen/logrus | |
6 | version: 3e01752db0189b9157070a0e1668a620f9a85da2 | |
7 | - name: golang.org/x/crypto | |
8 | version: 1351f936d976c60a0a48d728281922cf63eafb8d | |
9 | subpackages: | |
10 | - ripemd160 | |
11 | - ssh/terminal | |
12 | - name: golang.org/x/sys | |
13 | version: 8dbc5d05d6edcc104950cc299a1ce6641235bc86 | |
14 | subpackages: | |
15 | - unix | |
16 | testImports: | |
17 | - name: github.com/davecgh/go-spew | |
18 | version: 8991bc29aa16c548c550c7ff78260e27b9ab7c73 | |
19 | subpackages: | |
20 | - spew |
0 | package: github.com/vbatts/go-mtree | |
1 | description: File systems verification utility and library, in likeness of mtree(8) | |
2 | homepage: https://github.com/vbatts/go-mtree | |
3 | license: BSD-3-Clause | |
4 | import: | |
5 | - package: golang.org/x/crypto | |
6 | subpackages: | |
7 | - ripemd160 | |
8 | - package: github.com/sirupsen/logrus | |
9 | version: ^1.0.0 | |
10 | - package: golang.org/x/sys | |
11 | version: 8dbc5d05d6edcc104950cc299a1ce6641235bc86 | |
12 | subpackages: | |
13 | - unix | |
14 | - package: github.com/fatih/color | |
15 | version: ^1.6.0 |
0 | module github.com/vbatts/go-mtree | |
1 | ||
2 | go 1.17 | |
3 | ||
4 | require ( | |
5 | github.com/davecgh/go-spew v1.1.1 | |
6 | github.com/fatih/color v1.13.0 | |
7 | github.com/sirupsen/logrus v1.8.1 | |
8 | github.com/urfave/cli/v2 v2.10.3 | |
9 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d | |
10 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e | |
11 | ) | |
12 | ||
13 | require ( | |
14 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect | |
15 | github.com/mattn/go-colorable v0.1.12 // indirect | |
16 | github.com/mattn/go-isatty v0.0.14 // indirect | |
17 | github.com/russross/blackfriday/v2 v2.1.0 // indirect | |
18 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect | |
19 | ) |
0 | github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= | |
1 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= | |
2 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | |
3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
5 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= | |
6 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= | |
7 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | |
8 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= | |
9 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= | |
10 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | |
11 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= | |
12 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= | |
13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
15 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | |
16 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | |
17 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= | |
18 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | |
19 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | |
20 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |
21 | github.com/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo= | |
22 | github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= | |
23 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= | |
24 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= | |
25 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= | |
26 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | |
27 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | |
28 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
29 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
30 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
31 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
32 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
33 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
34 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
35 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
36 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo= | |
37 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
38 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |
39 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |
40 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | |
41 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
43 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
11 | 11 | "os" |
12 | 12 | |
13 | 13 | "github.com/vbatts/go-mtree/pkg/govis" |
14 | ||
15 | //lint:ignore SA1019 yes ripemd160 is deprecated, but this is for mtree compatibility | |
14 | 16 | "golang.org/x/crypto/ripemd160" |
15 | 17 | ) |
16 | 18 |
0 | //go:build darwin || freebsd || netbsd || openbsd | |
0 | 1 | // +build darwin freebsd netbsd openbsd |
1 | 2 | |
2 | 3 | package mtree |
0 | //go:build linux | |
0 | 1 | // +build linux |
1 | 2 | |
2 | 3 | package mtree |
68 | 69 | } |
69 | 70 | xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) { |
70 | 71 | if hdr, ok := info.Sys().(*tar.Header); ok { |
71 | if len(hdr.Xattrs) == 0 { | |
72 | if len(hdr.PAXRecords) == 0 { | |
72 | 73 | return nil, nil |
73 | 74 | } |
74 | 75 | klist := []KeyVal{} |
75 | for k, v := range hdr.Xattrs { | |
76 | for k, v := range hdr.PAXRecords { | |
76 | 77 | encKey, err := govis.Vis(k, DefaultVisFlags) |
77 | 78 | if err != nil { |
78 | 79 | return nil, nil |
0 | //go:build !linux && !darwin && !freebsd && !netbsd && !openbsd | |
0 | 1 | // +build !linux,!darwin,!freebsd,!netbsd,!openbsd |
1 | 2 | |
2 | 3 | package mtree |
17 | 17 | |
18 | 18 | // Prefix is the portion of the keyword before a first "." (if present). |
19 | 19 | // |
20 | // Primarly for the xattr use-case, where the keyword `xattr.security.selinux` would have a Suffix of `security.selinux`. | |
20 | // Primarily for the xattr use-case, where the keyword `xattr.security.selinux` would have a Suffix of `security.selinux`. | |
21 | 21 | func (k Keyword) Prefix() Keyword { |
22 | 22 | if strings.Contains(string(k), ".") { |
23 | 23 | return Keyword(strings.SplitN(string(k), ".", 2)[0]) |
28 | 28 | // Suffix is the portion of the keyword after a first ".". |
29 | 29 | // This is an option feature. |
30 | 30 | // |
31 | // Primarly for the xattr use-case, where the keyword `xattr.security.selinux` would have a Suffix of `security.selinux`. | |
31 | // Primarily for the xattr use-case, where the keyword `xattr.security.selinux` would have a Suffix of `security.selinux`. | |
32 | 32 | func (k Keyword) Suffix() string { |
33 | 33 | if strings.Contains(string(k), ".") { |
34 | 34 | return strings.SplitN(string(k), ".", 2)[1] |
173 | 173 | } |
174 | 174 | func keyValCopy(set []KeyVal) []KeyVal { |
175 | 175 | ret := make([]KeyVal, len(set)) |
176 | for i := range set { | |
177 | ret[i] = set[i] | |
178 | } | |
176 | copy(ret, set) | |
179 | 177 | return ret |
180 | 178 | } |
181 | 179 |
0 | //go:build linux | |
0 | 1 | // +build linux |
1 | 2 | |
2 | 3 | package mtree |
3 | 4 | |
4 | 5 | import ( |
5 | "fmt" | |
6 | 6 | "io/ioutil" |
7 | 7 | "os" |
8 | 8 | "path/filepath" |
11 | 11 | "github.com/vbatts/go-mtree/xattr" |
12 | 12 | ) |
13 | 13 | |
14 | //gocyclo:ignore | |
14 | 15 | func TestXattr(t *testing.T) { |
15 | 16 | testDir, present := os.LookupEnv("MTREE_TESTDIR") |
16 | 17 | if present == false { |
17 | // a bit dirty to create/destory a directory in cwd, | |
18 | // a bit dirty to create/destroy a directory in cwd, | |
18 | 19 | // but often /tmp is mounted tmpfs and doesn't support |
19 | 20 | // xattrs |
20 | 21 | testDir = "." |
39 | 40 | } |
40 | 41 | |
41 | 42 | if err := xattr.Set(dir, "user.test", []byte("directory")); err != nil { |
42 | t.Skip(fmt.Sprintf("skipping: %q does not support xattrs", dir)) | |
43 | t.Skipf("skipping: %q does not support xattrs", dir) | |
43 | 44 | } |
44 | 45 | if err := xattr.Set(filepath.Join(dir, "file"), "user.test", []byte("regular file")); err != nil { |
45 | 46 | t.Fatal(err) |
38 | 38 | t.Errorf("expected %q; got %q", expected, got) |
39 | 39 | } |
40 | 40 | |
41 | expected = "xattr.security.selinux=farts" | |
42 | 41 | kv1 := KeyVal(got) |
43 | 42 | kv2 := kv.NewValue("farts") |
44 | 43 | if !kv2.Equal(kv1) { |
0 | //go:build darwin || dragonfly || freebsd || openbsd || linux || netbsd || solaris | |
0 | 1 | // +build darwin dragonfly freebsd openbsd linux netbsd solaris |
1 | 2 | |
2 | 3 | package mtree |
0 | // +build go1.7 | |
1 | ||
2 | package mtree | |
3 | ||
4 | import ( | |
5 | "os/user" | |
6 | ) | |
7 | ||
8 | var lookupGroupID = user.LookupGroupId |
0 | // +build !go1.7 | |
1 | ||
2 | package mtree | |
3 | ||
4 | import ( | |
5 | "bufio" | |
6 | "bytes" | |
7 | "io" | |
8 | "os" | |
9 | "strconv" | |
10 | "strings" | |
11 | ) | |
12 | ||
13 | const groupFile = "/etc/group" | |
14 | ||
15 | var colon = []byte{':'} | |
16 | ||
17 | // Group represents a grouping of users. | |
18 | // | |
19 | // On POSIX systems Gid contains a decimal number representing the group ID. | |
20 | type Group struct { | |
21 | Gid string // group ID | |
22 | Name string // group name | |
23 | } | |
24 | ||
25 | func lookupGroupID(id string) (*Group, error) { | |
26 | f, err := os.Open(groupFile) | |
27 | if err != nil { | |
28 | return nil, err | |
29 | } | |
30 | defer f.Close() | |
31 | return findGroupID(id, f) | |
32 | } | |
33 | ||
34 | func findGroupID(id string, r io.Reader) (*Group, error) { | |
35 | if v, err := readColonFile(r, matchGroupIndexValue(id, 2)); err != nil { | |
36 | return nil, err | |
37 | } else if v != nil { | |
38 | return v.(*Group), nil | |
39 | } | |
40 | return nil, UnknownGroupIDError(id) | |
41 | } | |
42 | ||
43 | // lineFunc returns a value, an error, or (nil, nil) to skip the row. | |
44 | type lineFunc func(line []byte) (v interface{}, err error) | |
45 | ||
46 | // readColonFile parses r as an /etc/group or /etc/passwd style file, running | |
47 | // fn for each row. readColonFile returns a value, an error, or (nil, nil) if | |
48 | // the end of the file is reached without a match. | |
49 | func readColonFile(r io.Reader, fn lineFunc) (v interface{}, err error) { | |
50 | bs := bufio.NewScanner(r) | |
51 | for bs.Scan() { | |
52 | line := bs.Bytes() | |
53 | // There's no spec for /etc/passwd or /etc/group, but we try to follow | |
54 | // the same rules as the glibc parser, which allows comments and blank | |
55 | // space at the beginning of a line. | |
56 | line = bytes.TrimSpace(line) | |
57 | if len(line) == 0 || line[0] == '#' { | |
58 | continue | |
59 | } | |
60 | v, err = fn(line) | |
61 | if v != nil || err != nil { | |
62 | return | |
63 | } | |
64 | } | |
65 | return nil, bs.Err() | |
66 | } | |
67 | ||
68 | func matchGroupIndexValue(value string, idx int) lineFunc { | |
69 | var leadColon string | |
70 | if idx > 0 { | |
71 | leadColon = ":" | |
72 | } | |
73 | substr := []byte(leadColon + value + ":") | |
74 | return func(line []byte) (v interface{}, err error) { | |
75 | if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 { | |
76 | return | |
77 | } | |
78 | // wheel:*:0:root | |
79 | parts := strings.SplitN(string(line), ":", 4) | |
80 | if len(parts) < 4 || parts[0] == "" || parts[idx] != value || | |
81 | // If the file contains +foo and you search for "foo", glibc | |
82 | // returns an "invalid argument" error. Similarly, if you search | |
83 | // for a gid for a row where the group name starts with "+" or "-", | |
84 | // glibc fails to find the record. | |
85 | parts[0][0] == '+' || parts[0][0] == '-' { | |
86 | return | |
87 | } | |
88 | if _, err := strconv.Atoi(parts[2]); err != nil { | |
89 | return nil, nil | |
90 | } | |
91 | return &Group{Name: parts[0], Gid: parts[2]}, nil | |
92 | } | |
93 | } | |
94 | ||
95 | // UnknownGroupIDError is returned by LookupGroupId when | |
96 | // a group cannot be found. | |
97 | type UnknownGroupIDError string | |
98 | ||
99 | func (e UnknownGroupIDError) Error() string { | |
100 | return "group: unknown groupid " + string(e) | |
101 | } |
372 | 372 | } |
373 | 373 | creator.DH.Entries = append(creator.DH.Entries, dotEntry) |
374 | 374 | } |
375 | return | |
376 | 375 | } |
377 | 376 | |
378 | 377 | // resolveHardlinks goes through an Entry tree, and finds the Entry's associated |
408 | 407 | } |
409 | 408 | } |
410 | 409 | |
411 | // filter takes in a pointer to an Entry, and returns a slice of Entry's that | |
412 | // satisfy the predicate p | |
413 | func filter(root *Entry, p func(*Entry) bool) []Entry { | |
414 | if root != nil { | |
415 | var validEntrys []Entry | |
416 | if len(root.Children) > 0 || root.Prev != nil { | |
417 | for _, c := range root.Children { | |
418 | // filter the sub-directory | |
419 | if c.Prev != nil { | |
420 | validEntrys = append(validEntrys, filter(c, p)...) | |
421 | } | |
422 | if p(c) { | |
423 | if c.Prev == nil { | |
424 | validEntrys = append([]Entry{*c}, validEntrys...) | |
425 | } else { | |
426 | validEntrys = append(validEntrys, *c) | |
427 | } | |
428 | } | |
429 | } | |
430 | return validEntrys | |
431 | } | |
432 | } | |
433 | return nil | |
434 | } | |
435 | ||
436 | 410 | func (ts *tarStream) setErr(err error) { |
437 | 411 | ts.err = err |
438 | 412 | } |
108 | 108 | t.Fatal(err) |
109 | 109 | } |
110 | 110 | |
111 | res, err := TarCheck(tdh, dh, append(DefaultKeywords, "sha1")) | |
111 | res, err := Compare(tdh, dh, append(DefaultKeywords, "sha1")) | |
112 | 112 | if err != nil { |
113 | 113 | t.Fatal(err) |
114 | 114 | } |
128 | 128 | // `tar -cvf some.tar dir1 dir2 dir3 dir4/dir5 dir6` ... etc. |
129 | 129 | // The testdata of collection.tar resemble such an archive. the `collection` folder |
130 | 130 | // is the contents of `collection.tar` extracted |
131 | //gocyclo:ignore | |
131 | 132 | func TestArchiveCreation(t *testing.T) { |
132 | 133 | fh, err := os.Open("./testdata/collection.tar") |
133 | 134 | if err != nil { |
163 | 164 | } |
164 | 165 | |
165 | 166 | // Test the tar manifest against itself |
166 | res, err = TarCheck(tdh, tdh, []Keyword{"sha1"}) | |
167 | res, err = Compare(tdh, tdh, []Keyword{"sha1"}) | |
167 | 168 | if err != nil { |
168 | 169 | t.Fatal(err) |
169 | 170 | } |
179 | 180 | if err != nil { |
180 | 181 | t.Fatal(err) |
181 | 182 | } |
182 | res, err = TarCheck(tdh, dh, []Keyword{"sha1"}) | |
183 | res, err = Compare(tdh, dh, []Keyword{"sha1"}) | |
183 | 184 | if err != nil { |
184 | 185 | t.Fatal(err) |
185 | 186 | } |
196 | 197 | // evaluated. Also, The fact that this archive contains a single entry, yet the |
197 | 198 | // entry is associated with a file that has parent directories, means that the |
198 | 199 | // "." directory should be the lowest sub-directory under which `file` is contained. |
200 | //gocyclo:ignore | |
199 | 201 | func TestTreeTraversal(t *testing.T) { |
200 | 202 | fh, err := os.Open("./testdata/traversal.tar") |
201 | 203 | if err != nil { |
217 | 219 | t.Fatal(err) |
218 | 220 | } |
219 | 221 | |
220 | res, err := TarCheck(tdh, tdh, []Keyword{"sha1"}) | |
222 | res, err := Compare(tdh, tdh, []Keyword{"sha1"}) | |
221 | 223 | if err != nil { |
222 | 224 | t.Fatal(err) |
223 | 225 | } |
331 | 333 | Type byte |
332 | 334 | Sec, Nsec int64 |
333 | 335 | Xattrs map[string]string |
334 | } | |
335 | ||
336 | // minimal tar archive that mimics what is in ./testdata/test.tar | |
337 | var minimalFiles = []fakeFile{ | |
338 | {"x/", "", 0755, '5', 0, 0, nil}, | |
339 | {"x/files", "howdy\n", 0644, '0', 0, 0, nil}, | |
340 | 336 | } |
341 | 337 | |
342 | 338 | func makeTarStream(ff []fakeFile) ([]byte, error) { |
2 | 2 | |
3 | 3 | name=$(basename $0) |
4 | 4 | root="$(dirname $(dirname $(dirname $0)))" |
5 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | 6 | t=$(mktemp -d -t go-mtree.XXXXXX) |
7 | 7 | |
8 | 8 | echo "[${name}] Running in ${t}" |
2 | 2 | |
3 | 3 | name=$(basename $0) |
4 | 4 | root="$(dirname $(dirname $(dirname $0)))" |
5 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | 6 | t=$(mktemp -d -t go-mtree.XXXXXX) |
7 | 7 | |
8 | 8 | echo "[${name}] Running in ${t}" |
2 | 2 | |
3 | 3 | name=$(basename $0) |
4 | 4 | root="$(dirname $(dirname $(dirname $0)))" |
5 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | 6 | t=$(mktemp -d -t go-mtree.XXXXXX) |
7 | 7 | |
8 | 8 | setfattr -n user.has.xattrs -v "true" "${t}" || exit 0 |
3 | 3 | |
4 | 4 | name=$(basename $0) |
5 | 5 | root="$(dirname $(dirname $(dirname $0)))" |
6 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
6 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
7 | 7 | t=$(mktemp -d -t go-mtree.XXXXXX) |
8 | 8 | |
9 | 9 | echo "[${name}] Running in ${t}" |
2 | 2 | |
3 | 3 | name=$(basename $0) |
4 | 4 | root="$(dirname $(dirname $(dirname $0)))" |
5 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | 6 | t=$(mktemp -d -t go-mtree.XXXXXX) |
7 | 7 | |
8 | 8 | echo "[${name}] Running in ${t}" |
2 | 2 | |
3 | 3 | name=$(basename $0) |
4 | 4 | root="$(dirname $(dirname $(dirname $0)))" |
5 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | 6 | t=$(mktemp -d -t go-mtree.XXXXXX) |
7 | 7 | |
8 | 8 | echo "[${name}] Running in ${t}" |
2 | 2 | |
3 | 3 | name=$(basename $0) |
4 | 4 | root="$(dirname $(dirname $(dirname $0)))" |
5 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | 6 | left=$(mktemp -d -t go-mtree.XXXXXX) |
7 | 7 | right=$(mktemp -d -t go-mtree.XXXXXX) |
8 | 8 |
2 | 2 | |
3 | 3 | name=$(basename $0) |
4 | 4 | root="$(dirname $(dirname $(dirname $0)))" |
5 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | 6 | t=$(mktemp -d /tmp/go-mtree.XXXXXX) |
7 | 7 | |
8 | 8 | echo "[${name}] Running in ${t}" |
2 | 2 | |
3 | 3 | name=$(basename $0) |
4 | 4 | root="$(dirname $(dirname $(dirname $0)))" |
5 | gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree) | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | 6 | t=$(mktemp -d -t go-mtree.XXXXXX) |
7 | 7 | |
8 | 8 | echo "[${name}] Running in ${t}" |
0 | #!/bin/bash | |
1 | set -e | |
2 | ||
3 | name=$(basename $0) | |
4 | root="$(dirname $(dirname $(dirname $0)))" | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | t=$(mktemp -d /tmp/go-mtree.XXXXXX) | |
7 | ||
8 | echo "[${name}] Running in ${t}" | |
9 | ||
10 | ## testing comparing two files | |
11 | ||
12 | pushd ${root} | |
13 | mkdir -p ${t}/extract | |
14 | git archive --format=tar HEAD^{tree} . | tar -C ${t}/extract/ -x | |
15 | ||
16 | ${gomtree} -K sha256digest -c -p ${t}/extract/ > ${t}/${name}-1.mtree | |
17 | rm -rf ${t}/extract/*.go | |
18 | ${gomtree} -K sha256digest -c -p ${t}/extract/ > ${t}/${name}-2.mtree | |
19 | ||
20 | # this _ought_ to fail because the files are missing now | |
21 | ! ${gomtree} -f ${t}/${name}-1.mtree -f ${t}/${name}-2.mtree | |
22 | ||
23 | popd | |
24 | rm -rf ${t} |
0 | #!/bin/bash | |
1 | set -e | |
2 | ||
3 | name=$(basename $0) | |
4 | root="$(dirname $(dirname $(dirname $0)))" | |
5 | gomtree=$(go run ${root}/test/realpath/main.go ${root}/gomtree) | |
6 | t=$(mktemp -d /tmp/go-mtree.XXXXXX) | |
7 | ||
8 | echo "[${name}] Running in ${t}" | |
9 | ||
10 | ## testing comparing two files | |
11 | ||
12 | pushd ${root} | |
13 | mkdir -p ${t}/ | |
14 | touch ${t}/foo | |
15 | ||
16 | ## can not walk a file. We're expecting a directory. | |
17 | ## https://github.com/vbatts/go-mtree/issues/166 | |
18 | ! ${gomtree} -c -K uname,uid,gname,gid,type,nlink,link,mode,flags,xattr,xattrs,size,time,sha256 -p ${t}/foo | |
19 | ||
20 | popd | |
21 | rm -rf ${t} |
0 | package main | |
1 | ||
2 | import ( | |
3 | "flag" | |
4 | "fmt" | |
5 | "os" | |
6 | "os/exec" | |
7 | ||
8 | "github.com/fatih/color" | |
9 | ) | |
10 | ||
11 | func main() { | |
12 | flag.Parse() | |
13 | green := color.New(color.FgGreen).SprintFunc() | |
14 | red := color.New(color.FgRed).SprintFunc() | |
15 | ||
16 | failed := 0 | |
17 | for _, arg := range flag.Args() { | |
18 | cmd := exec.Command("bash", arg) | |
19 | if os.Getenv("TMPDIR") != "" { | |
20 | cmd.Env = append(cmd.Env, "TMPDIR="+os.Getenv("TMPDIR")) | |
21 | } | |
22 | cmd.Stderr = os.Stderr | |
23 | cmd.Stdout = os.Stdout | |
24 | if err := cmd.Run(); err != nil { | |
25 | failed++ | |
26 | fmt.Fprintf(os.Stderr, red("FAILED: %s\n"), arg) | |
27 | } | |
28 | } | |
29 | if failed > 0 { | |
30 | fmt.Fprintf(os.Stderr, red("%d FAILED tests\n"), failed) | |
31 | os.Exit(1) | |
32 | } | |
33 | fmt.Fprintf(os.Stdout, green("SUCCESS: no cli tests failed\n")) | |
34 | } |
0 | // +build ignore | |
1 | ||
2 | package main | |
3 | ||
4 | import ( | |
5 | "flag" | |
6 | "fmt" | |
7 | "os" | |
8 | "os/exec" | |
9 | ||
10 | "github.com/fatih/color" | |
11 | ) | |
12 | ||
13 | func main() { | |
14 | flag.Parse() | |
15 | green := color.New(color.FgGreen).SprintFunc() | |
16 | red := color.New(color.FgRed).SprintFunc() | |
17 | ||
18 | failed := 0 | |
19 | for _, arg := range flag.Args() { | |
20 | cmd := exec.Command("bash", arg) | |
21 | if os.Getenv("TMPDIR") != "" { | |
22 | cmd.Env = append(cmd.Env, "TMPDIR="+os.Getenv("TMPDIR")) | |
23 | } | |
24 | cmd.Stderr = os.Stderr | |
25 | cmd.Stdout = os.Stdout | |
26 | if err := cmd.Run(); err != nil { | |
27 | failed++ | |
28 | fmt.Fprintf(os.Stderr, red("FAILED: %s\n"), arg) | |
29 | } | |
30 | } | |
31 | if failed > 0 { | |
32 | fmt.Fprintf(os.Stderr, red("%d FAILED tests\n"), failed) | |
33 | os.Exit(1) | |
34 | } | |
35 | fmt.Fprintf(os.Stdout, green("SUCCESS: no cli tests failed\n")) | |
36 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "flag" | |
4 | "fmt" | |
5 | "os" | |
6 | "path/filepath" | |
7 | ) | |
8 | ||
9 | func main() { | |
10 | flag.Parse() | |
11 | for _, arg := range flag.Args() { | |
12 | path, err := filepath.Abs(arg) | |
13 | if err != nil { | |
14 | fmt.Fprint(os.Stderr, err) | |
15 | os.Exit(1) | |
16 | } | |
17 | fmt.Printf("%s", path) | |
18 | } | |
19 | } |
0 | // +build ignore | |
1 | ||
2 | package main | |
3 | ||
4 | import ( | |
5 | "flag" | |
6 | "fmt" | |
7 | "os" | |
8 | "path/filepath" | |
9 | ) | |
10 | ||
11 | func main() { | |
12 | flag.Parse() | |
13 | for _, arg := range flag.Args() { | |
14 | path, err := filepath.Abs(arg) | |
15 | if err != nil { | |
16 | fmt.Fprint(os.Stderr, err) | |
17 | os.Exit(1) | |
18 | } | |
19 | fmt.Printf("%s", path) | |
20 | } | |
21 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "encoding/json" |
4 | "fmt" | |
5 | 4 | "io/ioutil" |
6 | 5 | "os" |
7 | 6 | "path/filepath" |
14 | 13 | //logrus.SetLevel(logrus.DebugLevel) |
15 | 14 | } |
16 | 15 | |
16 | //gocyclo:ignore | |
17 | 17 | func TestXattrUpdate(t *testing.T) { |
18 | 18 | content := []byte("I know half of you half as well as I ought to") |
19 | // a bit dirty to create/destory a directory in cwd, but often /tmp is | |
19 | // a bit dirty to create/destroy a directory in cwd, but often /tmp is | |
20 | 20 | // mounted tmpfs and doesn't support xattrs |
21 | 21 | dir, err := ioutil.TempDir(".", "test.xattr.restore.") |
22 | 22 | if err != nil { |
30 | 30 | } |
31 | 31 | |
32 | 32 | if err := xattr.Set(dir, "user.test", []byte("directory")); err != nil { |
33 | t.Skip(fmt.Sprintf("skipping: %q does not support xattrs", dir)) | |
33 | t.Skipf("skipping: %q does not support xattrs", dir) | |
34 | 34 | } |
35 | 35 | if err := xattr.Set(tmpfn, "user.test", []byte("regular file")); err != nil { |
36 | 36 | t.Fatal(err) |
89 | 89 | } |
90 | 90 | |
91 | 91 | // TODO make a test for xattr here. Likely in the user space for privileges. Even still this may be prone to error for some tmpfs don't act right with xattrs. :-\ |
92 | // I'd hate to have to t.Skip() a test rather than fail alltogether. | |
92 | // I'd hate to have to t.Skip() a test rather than fail altogether. | |
93 | 93 | } |
0 | //go:build go1.7 | |
0 | 1 | // +build go1.7 |
1 | 2 | |
2 | 3 | package mtree |
19 | 20 | logrus.SetLevel(logrus.DebugLevel) |
20 | 21 | } |
21 | 22 | |
23 | //gocyclo:ignore | |
22 | 24 | func TestUpdate(t *testing.T) { |
23 | 25 | content := []byte("I know half of you half as well as I ought to") |
24 | 26 | dir, err := ioutil.TempDir("", "test-check-keywords") |
12 | 12 | // VersionMinor is for functionality in a backwards-compatible manner |
13 | 13 | VersionMinor = 5 |
14 | 14 | // VersionPatch is for backwards-compatible bug fixes |
15 | VersionPatch = 0 | |
15 | VersionPatch = 1 | |
16 | 16 | |
17 | 17 | // VersionDev indicates development branch. Releases will be empty string. |
18 | 18 | VersionDev = "-dev" |
31 | 31 | func Walk(root string, excludes []ExcludeFunc, keywords []Keyword, fsEval FsEval) (*DirectoryHierarchy, error) { |
32 | 32 | if fsEval == nil { |
33 | 33 | fsEval = DefaultFsEval{} |
34 | } | |
35 | if info, err := os.Stat(root); err == nil { | |
36 | if !info.IsDir() { | |
37 | return nil, fmt.Errorf("%s: Not a directory", filepath.Base(root)) | |
38 | } | |
34 | 39 | } |
35 | 40 | creator := dhCreator{DH: &DirectoryHierarchy{}, fs: fsEval} |
36 | 41 | // insert signature and metadata comments first (user, machine, tree, date) |
102 | 107 | } |
103 | 108 | keyFunc, ok := KeywordFuncs[keyword.Prefix()] |
104 | 109 | if !ok { |
105 | return fmt.Errorf("Unknown keyword %q for file %q", keyword.Prefix(), path) | |
110 | return fmt.Errorf("unknown keyword %q for file %q", keyword.Prefix(), path) | |
106 | 111 | } |
107 | 112 | kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r) |
108 | 113 | if err != nil { |
137 | 142 | } |
138 | 143 | keyFunc, ok := KeywordFuncs[keyword.Prefix()] |
139 | 144 | if !ok { |
140 | return fmt.Errorf("Unknown keyword %q for file %q", keyword.Prefix(), path) | |
145 | return fmt.Errorf("unknown keyword %q for file %q", keyword.Prefix(), path) | |
141 | 146 | } |
142 | 147 | kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r) |
143 | 148 | if err != nil { |
197 | 202 | } |
198 | 203 | keyFunc, ok := KeywordFuncs[keyword.Prefix()] |
199 | 204 | if !ok { |
200 | return fmt.Errorf("Unknown keyword %q for file %q", keyword.Prefix(), path) | |
205 | return fmt.Errorf("unknown keyword %q for file %q", keyword.Prefix(), path) | |
201 | 206 | } |
202 | 207 | kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r) |
203 | 208 | if err != nil { |