Codebase list golang-github-vbatts-go-mtree / fresh-snapshots/upstream
Import upstream version 0.5.2+ds Debian Janitor 1 year, 4 months ago
56 changed file(s) with 576 addition(s) and 532 deletion(s). Raw diff Collapse all Expand all
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
-6
.gitignore less more
0 *~
1 .cli.test
2 .lint
3 .test
4 .vet
5 gomtree
+0
-21
.travis.yml less more
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
55 CLEAN_FILES := *~
66 TAGS :=
77 ARCHES := linux,386 linux,amd64 linux,arm linux,arm64 openbsd,amd64 windows,amd64 darwin,amd64
8 GO_VER := go1.14
89
910 default: build validation
1011
1213 validation: .test .lint .vet .cli.test
1314
1415 .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 $@
1633
1734 .PHONY: test
1835 test: .test
1936
2037 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)
2139
2240 .test: $(SOURCE_FILES)
23 go test -v $$(glide novendor) && touch $@
41 go test -v $(NO_VENDOR_DIR) && touch $@
2442
2543 .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 $@
2745
2846 .PHONY: lint
2947 lint: .lint
3149 CLEAN_FILES += .lint
3250
3351 .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 $@ \
3654 else \
37 touch $@ ; \
55 touch $@ ; \
3856 fi
3957
4058 .PHONY: vet
4361 CLEAN_FILES += .vet .vet.tags
4462
4563 .vet: $(SOURCE_FILES)
46 go vet $$(glide novendor) && touch $@
64 go vet $(NO_VENDOR_DIR) && touch $@
4765
4866 .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 $@
5068
5169 .PHONY: cli.test
5270 cli.test: .cli.test
5472 CLEAN_FILES += .cli.test .cli.test.tags
5573
5674 .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 $@
5876
5977 .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 $@
6179
6280 .PHONY: build
6381 build: $(BUILD)
6482
6583 $(BUILD): $(SOURCE_FILES)
66 go build -o $(BUILD) $(BUILDPATH)
84 go build -mod=vendor -o $(BUILD) $(BUILDPATH)
6785
6886 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
7193
7294 ./bin:
7395 mkdir -p $@
80102 p=$$(echo $$pair | cut -d , -f 1);\
81103 a=$$(echo $$pair | cut -d , -f 2);\
82104 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 -
85111
86112 clean:
87113 rm -rf $(BUILD) $(CLEAN_FILES)
00 # go-mtree
11
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)
34
45 `mtree` is a filesystem hierarchy validation tooling and format.
56 This is a library and simple cli tool for [mtree(8)][mtree(8)] support.
1011
1112 There is also an [mtree port for Linux][archiecobbs/mtree-port] though it is
1213 not widely packaged for Linux distributions.
13
1414
1515 ## Format
1616
2525 `xattr` and `tar_time`. If you include these keywords, the FreeBSD `mtree`
2626 will fail, as they are unknown keywords to that implementation.
2727
28 To have `go-mtree` produce specifications that will be
28 To have `go-mtree` produce specifications that will be
2929 strictly compatible with the BSD `mtree`, use the `-bsd-keywords` flag when
3030 creating a manifest. This will make sure that only the keywords supported by
3131 BSD `mtree` are used in the program.
32
3332
3433 ### Typical form
3534
5049 path, is provided the keywords and the unique values for each path. Any common
5150 keyword and values are established in the `/set` command.
5251
53
5452 ### Extended attributes form
5553
5654 ```mtree
9694 validate this manifest against a filesystem hierarchy that's on disk, and vice versa.
9795
9896 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
10199 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
103101 123456789.123456789, the "tar time" equivalent would be 123456789.000000000.
104102 This way, if you validate a manifest created using a tar file against an
105103 actual root directory, there will be no complaints from `go-mtree` so long as the
106104 1-second precision time of a file in the root directory is the same.
107105
108
109106 ## Usage
110107
111108 To use the Go programming language library, see [the docs][godoc].
112109
113110 To use the command line tool, first [build it](#Building), then the following.
114111
115
116112 ### Create a manifest
117113
118114 This will also include the sha512 digest of the files.
121117 gomtree -c -K sha512digest -p . > /tmp/root.mtree
122118 ```
123119
124 With a tar file:
120 With a tar file:
125121
126122 ```bash
127123 gomtree -c -K sha512digest -T sometarfile.tar > /tmp/tar.mtree
172168 sha512digest
173169 ```
174170
175
176171 ## Building
177172
178173 Either:
192187 ## Testing
193188
194189 On Linux:
190
195191 ```bash
196192 cd $GOPATH/src/github.com/vbatts/go-mtree
197193 make
198194 ```
199195
200196 On FreeBSD:
197
201198 ```bash
202199 cd $GOPATH/src/github.com/vbatts/go-mtree
203200 gmake
204201 ```
205
206202
207203 [mtree(8)]: https://www.freebsd.org/cgi/man.cgi?mtree(8)
208204 [libarchive-formats(5)]: https://www.freebsd.org/cgi/man.cgi?query=libarchive-formats&sektion=5&n=1
1717
1818 return Compare(dh, newDh, keywords)
1919 }
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 }
88 "time"
99 )
1010
11 // simple walk of current directory, and imediately check it.
11 // simple walk of current directory, and immediately check it.
1212 // may not be parallelizable.
1313 func TestCheck(t *testing.T) {
1414 dh, err := Walk(".", nil, append(DefaultKeywords, []Keyword{"sha1", "xattr"}...), nil)
272272 ..
273273 `
274274 dh, err = ParseSpec(bytes.NewBufferString(spec))
275 if err != nil {
276 t.Error(err)
277 }
275278
276279 res, err = Check(dir, dh, nil, nil)
277280 if err != nil {
22 import (
33 "bytes"
44 "encoding/json"
5 "flag"
65 "fmt"
76 "io"
87 "io/ioutil"
109 "strings"
1110
1211 "github.com/sirupsen/logrus"
12 cli "github.com/urfave/cli/v2"
1313 "github.com/vbatts/go-mtree"
1414 )
1515
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
3616 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 {
3994 logrus.Fatal(err)
4095 }
4196 }
4297
43 func app() error {
44 flag.Parse()
45
46 if *flDebug {
98 func mainApp(c *cli.Context) error {
99 if c.Bool("debug") {
47100 os.Setenv("DEBUG", "1")
48101 logrus.SetLevel(logrus.DebugLevel)
49102 }
50103
51 if *flVersion {
52 fmt.Printf("%s :: %s\n", mtree.AppName, mtree.Version)
53 return nil
54 }
55
56104 // -list-keywords
57 if *flListKeywords {
105 if c.Bool("list-keywords") {
58106 fmt.Println("Available keywords:")
59107 for k := range mtree.KeywordFuncs {
60108 fmt.Print(" ")
71119 }
72120
73121 // --result-format
74 formatFunc, ok := formats[*flResultFormat]
122 formatFunc, ok := formats[c.String("result-format")]
75123 if !ok {
76 return fmt.Errorf("invalid output format: %s", *flResultFormat)
124 return fmt.Errorf("invalid output format: %s", c.String("result-format"))
77125 }
78126
79127 var (
83131 )
84132
85133 // -k <keywords>
86 if *flUseKeywords != "" {
87 tmpKeywords = splitKeywordsArg(*flUseKeywords)
134 if c.String("use-keywords") != "" {
135 tmpKeywords = splitKeywordsArg(c.String("use-keywords"))
88136 if !mtree.InKeywordSlice("type", tmpKeywords) {
89137 tmpKeywords = append([]mtree.Keyword{"type"}, tmpKeywords...)
90138 }
91139 } else {
92 if *flTar != "" {
140 if c.String("tar") != "" {
93141 tmpKeywords = mtree.DefaultTarKeywords[:]
94142 } else {
95143 tmpKeywords = mtree.DefaultKeywords[:]
97145 }
98146
99147 // -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")) {
102150 if !mtree.InKeywordSlice(kw, tmpKeywords) {
103151 tmpKeywords = append(tmpKeywords, kw)
104152 }
106154 }
107155
108156 // -bsd-keywords
109 if *flBsdKeywords {
157 if c.Bool("bsd-keywords") {
110158 for _, k := range tmpKeywords {
111159 if mtree.Keyword(k).Bsd() {
112160 currentKeywords = append(currentKeywords, k)
133181 )
134182
135183 // -f <file>
136 if *flFile != "" && !*flCreate {
184 if len(c.StringSlice("file")) > 0 && !c.Bool("create") {
137185 // 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])
139187 if err != nil {
140188 return err
141189 }
151199 }
152200
153201 // -list-used
154 if *flListUsedKeywords {
202 if c.Bool("list-used") {
155203 if specDh == nil {
156204 return fmt.Errorf("no specification provided. please provide a validation manifest")
157205 }
158206
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 }
167217 } 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")
173226 }
174 fmt.Printf("\n")
175227 }
176228 }
177229 return nil
179231
180232 if specKeywords != nil {
181233 // 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") == "" {
183235 currentKeywords = specKeywords
184236 }
185237
193245 }
194246
195247 // -p and -T are mutually exclusive
196 if *flPath != "" && *flTar != "" {
248 if c.String("path") != "" && c.String("tar") != "" {
197249 return fmt.Errorf("options -T and -p are mutually exclusive")
198250 }
199251
200252 // -p <path>
201253 var rootPath = "."
202 if *flPath != "" {
203 rootPath = *flPath
254 if c.String("path") != "" {
255 rootPath = c.String("path")
204256 }
205257
206258 excludes := []mtree.ExcludeFunc{}
207259 // -d
208 if *flDirectoryOnly {
260 if c.Bool("directory-only") {
209261 excludes = append(excludes, mtree.ExcludeNonDirectories)
210262 }
211263
212264 // -u
213265 // Failing early here. Processing is done below.
214 if *flUpdateAttributes && *flTar != "" {
266 if c.Bool("update-attributes") && c.String("tar") != "" {
215267 return fmt.Errorf("ERROR: -u can not be used with -T")
216268 }
217269
218270 // -T <tar file>
219 if *flTar != "" {
271 if c.String("tar") != "" {
220272 var input io.Reader
221 if *flTar == "-" {
273 if c.String("tar") == "-" {
222274 input = os.Stdin
223275 } else {
224 fh, err := os.Open(*flTar)
276 fh, err := os.Open(c.String("tar"))
225277 if err != nil {
226278 return err
227279 }
238290 }
239291 var err error
240292 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()
241304 if err != nil {
242305 return err
243306 }
250313 }
251314
252315 // -u
253 if *flUpdateAttributes && stateDh != nil {
316 if c.Bool("update-attributes") && stateDh != nil {
254317 // -u
255318 // this comes before the next case, intentionally.
256319 result, err := mtree.Update(rootPath, specDh, mtree.DefaultUpdateKeywords, nil)
257320 if err != nil {
258321 return err
259322 }
260 if result != nil && len(result) > 0 {
323 if len(result) > 0 {
261324 fmt.Printf("%#v\n", result)
262325 }
263326
278341 // unsafe IMO.
279342 for _, diff := range res {
280343 if diff.Type() == mtree.Modified {
281 return fmt.Errorf("mainfest validation failed")
344 return fmt.Errorf("manifest validation failed")
282345 }
283346 }
284347 }
287350 }
288351
289352 // -c
290 if *flCreate {
353 if c.Bool("create") {
291354 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])
294357 if err != nil {
295358 return err
296359 }
322385 return err
323386 }
324387 if res != nil {
325 if isTarSpec(specDh) || *flTar != "" {
388 if isTarSpec(specDh) || c.String("tar") != "" {
326389 res = filterMissingKeywords(res)
327390 }
328391
336399 // unsafe IMO.
337400 for _, diff := range res {
338401 if diff.Type() == mtree.Modified {
339 return fmt.Errorf("mainfest validation failed")
402 return fmt.Errorf("manifest validation failed")
340403 }
341404 }
342405 }
2828 // have different values (or have not been set in one of the
2929 // manifests).
3030 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"
3135
3236 // ErrorDifference represents an attempted update to the values of
3337 // a keyword that failed
156160
157161 // New returns the value of the KeyDeltaVal entry in the "new" DirectoryHierarchy
158162 // (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.
160164 func (k KeyDelta) New() *string {
161165 if k.diff == Modified || k.diff == Extra {
162 return sPtr(k.old)
166 return sPtr(k.new)
163167 }
164168 return nil
165169 }
304308 name: name,
305309 old: diff.Old.Value(),
306310 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,
307436 })
308437 }
309438 }
331460 // NB: The order of the parameters matters (old, new) because Extra and
332461 // Missing are considered as different discrepancy types.
333462 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 }
1111 "time"
1212 )
1313
14 // simple walk of current directory, and imediately check it.
14 // simple walk of current directory, and immediately check it.
1515 // may not be parallelizable.
1616 func TestCompare(t *testing.T) {
1717 old, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
3434 }
3535 }
3636
37 //gocyclo:ignore
3738 func TestCompareModified(t *testing.T) {
3839 dir, err := ioutil.TempDir("", "test-compare-modified")
3940 if err != nil {
8990 }
9091
9192 // These cannot fail.
92 tmpfile, _ = filepath.Rel(dir, tmpfile)
93 tmpdir, _ = filepath.Rel(dir, tmpdir)
9493 tmpsubfile, _ = filepath.Rel(dir, tmpsubfile)
9594
9695 for _, diff := range diffs {
115114 }
116115 }
117116
117 //gocyclo:ignore
118118 func TestCompareMissing(t *testing.T) {
119119 dir, err := ioutil.TempDir("", "test-compare-missing")
120120 if err != nil {
206206 }
207207 }
208208
209 //gocyclo:ignore
209210 func TestCompareExtra(t *testing.T) {
210211 dir, err := ioutil.TempDir("", "test-compare-extra")
211212 if err != nil {
339340 }
340341 }
341342
343 //gocyclo:ignore
342344 func TestTarCompare(t *testing.T) {
343345 dir, err := ioutil.TempDir("", "test-compare-tar")
344346 if err != nil {
7070 return fn
7171 }
7272
73 //gocyclo:ignore
7374 func TestCheckFsEval(t *testing.T) {
7475 dir, err := ioutil.TempDir("", "test-check-fs-eval")
7576 if err != nil {
+0
-21
glide.lock less more
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
-16
glide.yaml less more
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=
1111 "os"
1212
1313 "github.com/vbatts/go-mtree/pkg/govis"
14
15 //lint:ignore SA1019 yes ripemd160 is deprecated, but this is for mtree compatibility
1416 "golang.org/x/crypto/ripemd160"
1517 )
1618
0 //go:build darwin || freebsd || netbsd || openbsd
01 // +build darwin freebsd netbsd openbsd
12
23 package mtree
0 //go:build linux
01 // +build linux
12
23 package mtree
6869 }
6970 xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
7071 if hdr, ok := info.Sys().(*tar.Header); ok {
71 if len(hdr.Xattrs) == 0 {
72 if len(hdr.PAXRecords) == 0 {
7273 return nil, nil
7374 }
7475 klist := []KeyVal{}
75 for k, v := range hdr.Xattrs {
76 for k, v := range hdr.PAXRecords {
7677 encKey, err := govis.Vis(k, DefaultVisFlags)
7778 if err != nil {
7879 return nil, nil
0 //go:build !linux && !darwin && !freebsd && !netbsd && !openbsd
01 // +build !linux,!darwin,!freebsd,!netbsd,!openbsd
12
23 package mtree
1717
1818 // Prefix is the portion of the keyword before a first "." (if present).
1919 //
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`.
2121 func (k Keyword) Prefix() Keyword {
2222 if strings.Contains(string(k), ".") {
2323 return Keyword(strings.SplitN(string(k), ".", 2)[0])
2828 // Suffix is the portion of the keyword after a first ".".
2929 // This is an option feature.
3030 //
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`.
3232 func (k Keyword) Suffix() string {
3333 if strings.Contains(string(k), ".") {
3434 return strings.SplitN(string(k), ".", 2)[1]
173173 }
174174 func keyValCopy(set []KeyVal) []KeyVal {
175175 ret := make([]KeyVal, len(set))
176 for i := range set {
177 ret[i] = set[i]
178 }
176 copy(ret, set)
179177 return ret
180178 }
181179
0 //go:build linux
01 // +build linux
12
23 package mtree
34
45 import (
5 "fmt"
66 "io/ioutil"
77 "os"
88 "path/filepath"
1111 "github.com/vbatts/go-mtree/xattr"
1212 )
1313
14 //gocyclo:ignore
1415 func TestXattr(t *testing.T) {
1516 testDir, present := os.LookupEnv("MTREE_TESTDIR")
1617 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,
1819 // but often /tmp is mounted tmpfs and doesn't support
1920 // xattrs
2021 testDir = "."
3940 }
4041
4142 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)
4344 }
4445 if err := xattr.Set(filepath.Join(dir, "file"), "user.test", []byte("regular file")); err != nil {
4546 t.Fatal(err)
3838 t.Errorf("expected %q; got %q", expected, got)
3939 }
4040
41 expected = "xattr.security.selinux=farts"
4241 kv1 := KeyVal(got)
4342 kv2 := kv.NewValue("farts")
4443 if !kv2.Equal(kv1) {
0 //go:build darwin || dragonfly || freebsd || openbsd || linux || netbsd || solaris
01 // +build darwin dragonfly freebsd openbsd linux netbsd solaris
12
23 package mtree
0 //go:build windows
01 // +build windows
12
23 package mtree
0 package mtree
1
2 import (
3 "os/user"
4 )
5
6 var lookupGroupID = user.LookupGroupId
+0
-9
lookup_new.go less more
0 // +build go1.7
1
2 package mtree
3
4 import (
5 "os/user"
6 )
7
8 var lookupGroupID = user.LookupGroupId
+0
-102
lookup_old.go less more
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 }
0 //go:build !windows
01 // +build !windows
12
23 package mtree
0 //go:build windows
01 // +build windows
12
23 package mtree
372372 }
373373 creator.DH.Entries = append(creator.DH.Entries, dotEntry)
374374 }
375 return
376375 }
377376
378377 // resolveHardlinks goes through an Entry tree, and finds the Entry's associated
408407 }
409408 }
410409
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
436410 func (ts *tarStream) setErr(err error) {
437411 ts.err = err
438412 }
108108 t.Fatal(err)
109109 }
110110
111 res, err := TarCheck(tdh, dh, append(DefaultKeywords, "sha1"))
111 res, err := Compare(tdh, dh, append(DefaultKeywords, "sha1"))
112112 if err != nil {
113113 t.Fatal(err)
114114 }
128128 // `tar -cvf some.tar dir1 dir2 dir3 dir4/dir5 dir6` ... etc.
129129 // The testdata of collection.tar resemble such an archive. the `collection` folder
130130 // is the contents of `collection.tar` extracted
131 //gocyclo:ignore
131132 func TestArchiveCreation(t *testing.T) {
132133 fh, err := os.Open("./testdata/collection.tar")
133134 if err != nil {
163164 }
164165
165166 // Test the tar manifest against itself
166 res, err = TarCheck(tdh, tdh, []Keyword{"sha1"})
167 res, err = Compare(tdh, tdh, []Keyword{"sha1"})
167168 if err != nil {
168169 t.Fatal(err)
169170 }
179180 if err != nil {
180181 t.Fatal(err)
181182 }
182 res, err = TarCheck(tdh, dh, []Keyword{"sha1"})
183 res, err = Compare(tdh, dh, []Keyword{"sha1"})
183184 if err != nil {
184185 t.Fatal(err)
185186 }
196197 // evaluated. Also, The fact that this archive contains a single entry, yet the
197198 // entry is associated with a file that has parent directories, means that the
198199 // "." directory should be the lowest sub-directory under which `file` is contained.
200 //gocyclo:ignore
199201 func TestTreeTraversal(t *testing.T) {
200202 fh, err := os.Open("./testdata/traversal.tar")
201203 if err != nil {
217219 t.Fatal(err)
218220 }
219221
220 res, err := TarCheck(tdh, tdh, []Keyword{"sha1"})
222 res, err := Compare(tdh, tdh, []Keyword{"sha1"})
221223 if err != nil {
222224 t.Fatal(err)
223225 }
331333 Type byte
332334 Sec, Nsec int64
333335 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},
340336 }
341337
342338 func makeTarStream(ff []fakeFile) ([]byte, error) {
22
33 name=$(basename $0)
44 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)
66 t=$(mktemp -d -t go-mtree.XXXXXX)
77
88 echo "[${name}] Running in ${t}"
22
33 name=$(basename $0)
44 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)
66 t=$(mktemp -d -t go-mtree.XXXXXX)
77
88 echo "[${name}] Running in ${t}"
22
33 name=$(basename $0)
44 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)
66 t=$(mktemp -d -t go-mtree.XXXXXX)
77
88 setfattr -n user.has.xattrs -v "true" "${t}" || exit 0
33
44 name=$(basename $0)
55 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)
77 t=$(mktemp -d -t go-mtree.XXXXXX)
88
99 echo "[${name}] Running in ${t}"
22
33 name=$(basename $0)
44 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)
66 t=$(mktemp -d -t go-mtree.XXXXXX)
77
88 echo "[${name}] Running in ${t}"
22
33 name=$(basename $0)
44 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)
66 t=$(mktemp -d -t go-mtree.XXXXXX)
77
88 echo "[${name}] Running in ${t}"
22
33 name=$(basename $0)
44 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)
66 left=$(mktemp -d -t go-mtree.XXXXXX)
77 right=$(mktemp -d -t go-mtree.XXXXXX)
88
22
33 name=$(basename $0)
44 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)
66 t=$(mktemp -d /tmp/go-mtree.XXXXXX)
77
88 echo "[${name}] Running in ${t}"
22
33 name=$(basename $0)
44 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)
66 t=$(mktemp -d -t go-mtree.XXXXXX)
77
88 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
-37
test/cli.go less more
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
-3
test/doc.go less more
0 package test
1
2 // place holder for test helpers
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
-22
test/realpath.go less more
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 }
11
22 import (
33 "encoding/json"
4 "fmt"
54 "io/ioutil"
65 "os"
76 "path/filepath"
1413 //logrus.SetLevel(logrus.DebugLevel)
1514 }
1615
16 //gocyclo:ignore
1717 func TestXattrUpdate(t *testing.T) {
1818 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
2020 // mounted tmpfs and doesn't support xattrs
2121 dir, err := ioutil.TempDir(".", "test.xattr.restore.")
2222 if err != nil {
3030 }
3131
3232 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)
3434 }
3535 if err := xattr.Set(tmpfn, "user.test", []byte("regular file")); err != nil {
3636 t.Fatal(err)
8989 }
9090
9191 // 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.
9393 }
0 //go:build go1.7
01 // +build go1.7
12
23 package mtree
1920 logrus.SetLevel(logrus.DebugLevel)
2021 }
2122
23 //gocyclo:ignore
2224 func TestUpdate(t *testing.T) {
2325 content := []byte("I know half of you half as well as I ought to")
2426 dir, err := ioutil.TempDir("", "test-check-keywords")
0 //go:build linux
01 // +build linux
12
23 package mtree
0 //go:build !linux
01 // +build !linux
12
23 package mtree
1212 // VersionMinor is for functionality in a backwards-compatible manner
1313 VersionMinor = 5
1414 // VersionPatch is for backwards-compatible bug fixes
15 VersionPatch = 0
15 VersionPatch = 1
1616
1717 // VersionDev indicates development branch. Releases will be empty string.
1818 VersionDev = "-dev"
3131 func Walk(root string, excludes []ExcludeFunc, keywords []Keyword, fsEval FsEval) (*DirectoryHierarchy, error) {
3232 if fsEval == nil {
3333 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 }
3439 }
3540 creator := dhCreator{DH: &DirectoryHierarchy{}, fs: fsEval}
3641 // insert signature and metadata comments first (user, machine, tree, date)
102107 }
103108 keyFunc, ok := KeywordFuncs[keyword.Prefix()]
104109 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)
106111 }
107112 kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
108113 if err != nil {
137142 }
138143 keyFunc, ok := KeywordFuncs[keyword.Prefix()]
139144 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)
141146 }
142147 kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
143148 if err != nil {
197202 }
198203 keyFunc, ok := KeywordFuncs[keyword.Prefix()]
199204 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)
201206 }
202207 kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
203208 if err != nil {
0 //go:build linux
01 // +build linux
12
23 package xattr
0 //go:build !linux
01 // +build !linux
12
23 package xattr
0 //go:build !linux
01 // +build !linux
12
23 package xattr