Switch to go mod

This commit is contained in:
Ettore Di Giacinto
2019-11-10 18:04:06 +01:00
parent f634493dc0
commit 420186b7db
1200 changed files with 139110 additions and 7763 deletions

6
vendor/github.com/vbatts/go-mtree/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,6 @@
*~
.cli.test
.lint
.test
.vet
gomtree

21
vendor/github.com/vbatts/go-mtree/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,21 @@
language: go
go:
- "1.x"
- "1.11.x"
- "1.10.x"
- "1.9.x"
- "1.8.x"
sudo: false
before_install:
- git config --global url."https://".insteadOf git://
- make install.tools
- mkdir -p $GOPATH/src/github.com/vbatts && ln -sf $(pwd) $GOPATH/src/github.com/vbatts/go-mtree
install: true
script:
- make validation
- make validation.tags
- make build.arches

28
vendor/github.com/vbatts/go-mtree/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,28 @@
Copyright (c) 2016 Vincent Batts, Raleigh, NC, USA
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

89
vendor/github.com/vbatts/go-mtree/Makefile generated vendored Normal file
View File

@@ -0,0 +1,89 @@
BUILD := gomtree
BUILDPATH := github.com/vbatts/go-mtree/cmd/gomtree
CWD := $(shell pwd)
SOURCE_FILES := $(shell find . -type f -name "*.go")
CLEAN_FILES := *~
TAGS :=
ARCHES := linux,386 linux,amd64 linux,arm linux,arm64 openbsd,amd64 windows,amd64 darwin,amd64
default: build validation
.PHONY: validation
validation: .test .lint .vet .cli.test
.PHONY: validation.tags
validation.tags: .test.tags .vet.tags .cli.test
.PHONY: test
test: .test
CLEAN_FILES += .test .test.tags
.test: $(SOURCE_FILES)
go test -v $$(glide novendor) && touch $@
.test.tags: $(SOURCE_FILES)
set -e ; for tag in $(TAGS) ; do go test -tags $$tag -v $$(glide novendor) ; done && touch $@
.PHONY: lint
lint: .lint
CLEAN_FILES += .lint
.lint: $(SOURCE_FILES)
if [[ "$(go version |awk '{ print $3 }')" =~ ^go1\.11\. ]] ; then \
set -e ; for dir in $$(glide novendor) ; do golint -set_exit_status $$dir ; done && touch $@ \
else \
touch $@ ; \
fi
.PHONY: vet
vet: .vet .vet.tags
CLEAN_FILES += .vet .vet.tags
.vet: $(SOURCE_FILES)
go vet $$(glide novendor) && touch $@
.vet.tags: $(SOURCE_FILES)
set -e ; for tag in $(TAGS) ; do go vet -tags $$tag -v $$(glide novendor) ; done && touch $@
.PHONY: cli.test
cli.test: .cli.test
CLEAN_FILES += .cli.test .cli.test.tags
.cli.test: $(BUILD) $(wildcard ./test/cli/*.sh)
@go run ./test/cli.go ./test/cli/*.sh && touch $@
.cli.test.tags: $(BUILD) $(wildcard ./test/cli/*.sh)
@set -e ; for tag in $(TAGS) ; do go run -tags $$tag ./test/cli.go ./test/cli/*.sh ; done && touch $@
.PHONY: build
build: $(BUILD)
$(BUILD): $(SOURCE_FILES)
go build -o $(BUILD) $(BUILDPATH)
install.tools:
go get -u -v github.com/Masterminds/glide
if [[ "$(go version |awk '{ print $3 }')" =~ ^go1\.11\. ]] ; then go get -u golang.org/x/lint/golint ; fi
./bin:
mkdir -p $@
CLEAN_FILES += bin
build.arches: ./bin
@set -e ;\
for pair in $(ARCHES); do \
p=$$(echo $$pair | cut -d , -f 1);\
a=$$(echo $$pair | cut -d , -f 2);\
echo "Building $$p/$$a ...";\
GOOS=$$p GOARCH=$$a go build -o ./bin/gomtree.$$p.$$a $(BUILDPATH) ;\
done
clean:
rm -rf $(BUILD) $(CLEAN_FILES)

213
vendor/github.com/vbatts/go-mtree/README.md generated vendored Normal file
View File

@@ -0,0 +1,213 @@
# go-mtree
[![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)
`mtree` is a filesystem hierarchy validation tooling and format.
This is a library and simple cli tool for [mtree(8)][mtree(8)] support.
While the traditional `mtree` cli utility is primarily on BSDs (FreeBSD,
openBSD, etc), even broader support for the `mtree` specification format is
provided with libarchive ([libarchive-formats(5)][libarchive-formats(5)]).
There is also an [mtree port for Linux][archiecobbs/mtree-port] though it is
not widely packaged for Linux distributions.
## Format
The format of hierarchy specification is consistent with the `# mtree v2.0`
format. Both the BSD `mtree` and libarchive ought to be interoperable with it
with only one definite caveat. On Linux, extended attributes (`xattr`) on
files are often a critical aspect of the file, holding ACLs, capabilities, etc.
While FreeBSD filesystem do support `extattr`, this feature has not made its
way into their `mtree`.
This implementation of mtree supports a few non-upstream "keyword"s, such as:
`xattr` and `tar_time`. If you include these keywords, the FreeBSD `mtree`
will fail, as they are unknown keywords to that implementation.
To have `go-mtree` produce specifications that will be
strictly compatible with the BSD `mtree`, use the `-bsd-keywords` flag when
creating a manifest. This will make sure that only the keywords supported by
BSD `mtree` are used in the program.
### Typical form
With the standard keywords, plus say `sha256digest`, the hierarchy
specification looks like:
```mtree
# .
/set type=file nlink=1 mode=0664 uid=1000 gid=100
. size=4096 type=dir mode=0755 nlink=6 time=1459370393.273231538
LICENSE size=1502 mode=0644 time=1458851690.0 sha256digest=ef4e53d83096be56dc38dbf9bc8ba9e3068bec1ec37c179033d1e8f99a1c2a95
README.md size=2820 mode=0644 time=1459370256.316148361 sha256digest=d9b955134d99f84b17c0a711ce507515cc93cd7080a9dcd50400e3d993d876ac
[...]
```
See the directory presently in, and the files present. Along with each
path, is provided the keywords and the unique values for each path. Any common
keyword and values are established in the `/set` command.
### Extended attributes form
```mtree
# .
/set type=file nlink=1 mode=0664 uid=1000 gid=1000
. size=4096 type=dir mode=0775 nlink=6 time=1459370191.11179595 xattr.security.selinux=dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==
LICENSE size=1502 time=1458851690.583562292 xattr.security.selinux=dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==
README.md size=2366 mode=0644 time=1459369604.0 xattr.security.selinux=dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==
[...]
```
See the keyword prefixed with `xattr.` followed by the extended attribute's
namespace and keyword. This setup is consistent for use with Linux extended
attributes as well as FreeBSD extended attributes.
Since extended attributes are an unordered hashmap, this approach allows for
checking each `<namespace>.<key>` individually.
The value is the [base64 encoded][base64] of the value of the particular
extended attribute. Since the values themselves could be raw bytes, this
approach avoids issues with encoding.
### Tar form
```mtree
# .
/set type=file mode=0664 uid=1000 gid=1000
. type=dir mode=0775 tar_time=1468430408.000000000
# samedir
samedir type=dir mode=0775 tar_time=1468000972.000000000
file2 size=0 tar_time=1467999782.000000000
file1 size=0 tar_time=1467999781.000000000
[...]
```
While `go-mtree` serves mainly as a library for upstream `mtree` support,
`go-mtree` is also compatible with [tar archives][tar] (which is not an upstream feature).
This means that we can now create and validate a manifest by specifying a tar file.
More interestingly, this also means that we can create a manifest from an archive, and then
validate this manifest against a filesystem hierarchy that's on disk, and vice versa.
Notice that for the output of creating a validation manifest from a tar file, the default behavior
for evaluating a notion of time is to use the `tar_time` keyword. In the
"filesystem hierarchy" format of mtree, `time` is being evaluated with
nanosecond precision. However, GNU tar truncates a file's modification time
to 1-second precision. That is, if a file's full modification time is
123456789.123456789, the "tar time" equivalent would be 123456789.000000000.
This way, if you validate a manifest created using a tar file against an
actual root directory, there will be no complaints from `go-mtree` so long as the
1-second precision time of a file in the root directory is the same.
## Usage
To use the Go programming language library, see [the docs][godoc].
To use the command line tool, first [build it](#Building), then the following.
### Create a manifest
This will also include the sha512 digest of the files.
```bash
gomtree -c -K sha512digest -p . > /tmp/root.mtree
```
With a tar file:
```bash
gomtree -c -K sha512digest -T sometarfile.tar > /tmp/tar.mtree
```
### Validate a manifest
```bash
gomtree -p . -f /tmp/root.mtree
```
With a tar file:
```bash
gomtree -T sometarfile.tar -f /tmp/root.mtree
```
### See the supported keywords
```bash
gomtree -list-keywords
Available keywords:
uname
sha1
sha1digest
sha256digest
xattrs (not upstream)
link (default)
nlink (default)
md5digest
rmd160digest
mode (default)
cksum
md5
rmd160
type (default)
time (default)
uid (default)
gid (default)
sha256
sha384
sha512
xattr (not upstream)
tar_time (not upstream)
size (default)
ripemd160digest
sha384digest
sha512digest
```
## Building
Either:
```bash
go get github.com/vbatts/go-mtree/cmd/gomtree
```
or
```bash
git clone git://github.com/vbatts/go-mtree.git $GOPATH/src/github.com/vbatts/go-mtree
cd $GOPATH/src/github.com/vbatts/go-mtree
go build ./cmd/gomtree
```
## Testing
On Linux:
```bash
cd $GOPATH/src/github.com/vbatts/go-mtree
make
```
On FreeBSD:
```bash
cd $GOPATH/src/github.com/vbatts/go-mtree
gmake
```
[mtree(8)]: https://www.freebsd.org/cgi/man.cgi?mtree(8)
[libarchive-formats(5)]: https://www.freebsd.org/cgi/man.cgi?query=libarchive-formats&sektion=5&n=1
[archiecobbs/mtree-port]: https://github.com/archiecobbs/mtree-port
[godoc]: https://godoc.org/github.com/vbatts/go-mtree
[tar]: http://man7.org/linux/man-pages/man1/tar.1.html
[base64]: https://tools.ietf.org/html/rfc4648

30
vendor/github.com/vbatts/go-mtree/check.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
package mtree
// Check a root directory path against the DirectoryHierarchy, regarding only
// the available keywords from the list and each entry in the hierarchy.
// If keywords is nil, the check all present in the DirectoryHierarchy
//
// This is equivalent to creating a new DirectoryHierarchy with Walk(root, nil,
// keywords, fs) and then doing a Compare(dh, newDh, keywords).
func Check(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval) ([]InodeDelta, error) {
if keywords == nil {
keywords = dh.UsedKeywords()
}
newDh, err := Walk(root, nil, keywords, fs)
if err != nil {
return nil, err
}
return Compare(dh, newDh, keywords)
}
// TarCheck is the tar equivalent of checking a file hierarchy spec against a
// tar stream to determine if files have been changed. This is precisely
// equivalent to Compare(dh, tarDH, keywords).
func TarCheck(tarDH, dh *DirectoryHierarchy, keywords []Keyword) ([]InodeDelta, error) {
if keywords == nil {
return Compare(dh, tarDH, dh.UsedKeywords())
}
return Compare(dh, tarDH, keywords)
}

49
vendor/github.com/vbatts/go-mtree/cksum.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
package mtree
import (
"bufio"
"io"
)
const posixPolynomial uint32 = 0x04C11DB7
// cksum is an implementation of the POSIX CRC algorithm
func cksum(r io.Reader) (uint32, int, error) {
in := bufio.NewReader(r)
count := 0
var sum uint32
f := func(b byte) {
for i := 7; i >= 0; i-- {
msb := sum & (1 << 31)
sum = sum << 1
if msb != 0 {
sum = sum ^ posixPolynomial
}
}
sum ^= uint32(b)
}
for done := false; !done; {
switch b, err := in.ReadByte(); err {
case io.EOF:
done = true
case nil:
f(b)
count++
default:
return ^sum, count, err
}
}
for m := count; ; {
f(byte(m) & 0xff)
m = m >> 8
if m == 0 {
break
}
}
f(0)
f(0)
f(0)
f(0)
return ^sum, count, nil
}

446
vendor/github.com/vbatts/go-mtree/compare.go generated vendored Normal file
View File

@@ -0,0 +1,446 @@
package mtree
import (
"encoding/json"
"fmt"
"strconv"
)
// XXX: Do we need a Difference interface to make it so people can do var x
// Difference = <something>? The main problem is that keys and inodes need to
// have different interfaces, so it's just a pain.
// DifferenceType represents the type of a discrepancy encountered for
// an object. This is also used to represent discrepancies between keys
// for objects.
type DifferenceType string
const (
// Missing represents a discrepancy where the object is present in
// the @old manifest but is not present in the @new manifest.
Missing DifferenceType = "missing"
// Extra represents a discrepancy where the object is not present in
// the @old manifest but is present in the @new manifest.
Extra DifferenceType = "extra"
// Modified represents a discrepancy where the object is present in
// both the @old and @new manifests, but one or more of the keys
// have different values (or have not been set in one of the
// manifests).
Modified DifferenceType = "modified"
// ErrorDifference represents an attempted update to the values of
// a keyword that failed
ErrorDifference DifferenceType = "errored"
)
// These functions return *type from the parameter. It's just shorthand, to
// ensure that we don't accidentally expose pointers to the caller that are
// internal data.
func ePtr(e Entry) *Entry { return &e }
func sPtr(s string) *string { return &s }
// InodeDelta Represents a discrepancy in a filesystem object between two
// DirectoryHierarchy manifests. Discrepancies are caused by entries only
// present in one manifest [Missing, Extra], keys only present in one of the
// manifests [Modified] or a difference between the keys of the same object in
// both manifests [Modified].
type InodeDelta struct {
diff DifferenceType
path string
new Entry
old Entry
keys []KeyDelta
}
// Type returns the type of discrepancy encountered when comparing this inode
// between the two DirectoryHierarchy manifests.
func (i InodeDelta) Type() DifferenceType {
return i.diff
}
// Path returns the path to the inode (relative to the root of the
// DirectoryHierarchy manifests).
func (i InodeDelta) Path() string {
return i.path
}
// Diff returns the set of key discrepancies between the two manifests for the
// specific inode. If the DifferenceType of the inode is not Modified, then
// Diff returns nil.
func (i InodeDelta) Diff() []KeyDelta {
return i.keys
}
// Old returns the value of the inode Entry in the "old" DirectoryHierarchy (as
// determined by the ordering of parameters to Compare).
func (i InodeDelta) Old() *Entry {
if i.diff == Modified || i.diff == Missing {
return ePtr(i.old)
}
return nil
}
// New returns the value of the inode Entry in the "new" DirectoryHierarchy (as
// determined by the ordering of parameters to Compare).
func (i InodeDelta) New() *Entry {
if i.diff == Modified || i.diff == Extra {
return ePtr(i.new)
}
return nil
}
// MarshalJSON creates a JSON-encoded version of InodeDelta.
func (i InodeDelta) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Type DifferenceType `json:"type"`
Path string `json:"path"`
Keys []KeyDelta `json:"keys"`
}{
Type: i.diff,
Path: i.path,
Keys: i.keys,
})
}
// String returns a "pretty" formatting for InodeDelta.
func (i InodeDelta) String() string {
switch i.diff {
case Modified:
// Output the first failure.
f := i.keys[0]
return fmt.Sprintf("%q: keyword %q: expected %s; got %s", i.path, f.name, f.old, f.new)
case Extra:
return fmt.Sprintf("%q: unexpected path", i.path)
case Missing:
return fmt.Sprintf("%q: missing path", i.path)
default:
panic("programming error")
}
}
// KeyDelta Represents a discrepancy in a key for a particular filesystem
// object between two DirectoryHierarchy manifests. Discrepancies are caused by
// keys only present in one manifest [Missing, Extra] or a difference between
// the keys of the same object in both manifests [Modified]. A set of these is
// returned with InodeDelta.Diff().
type KeyDelta struct {
diff DifferenceType
name Keyword
old string
new string
err error // used for update delta results
}
// Type returns the type of discrepancy encountered when comparing this key
// between the two DirectoryHierarchy manifests' relevant inode entry.
func (k KeyDelta) Type() DifferenceType {
return k.diff
}
// Name returns the name (the key) of the KeyDeltaVal entry in the
// DirectoryHierarchy.
func (k KeyDelta) Name() Keyword {
return k.name
}
// Old returns the value of the KeyDeltaVal entry in the "old" DirectoryHierarchy
// (as determined by the ordering of parameters to Compare). Returns nil if
// there was no entry in the "old" DirectoryHierarchy.
func (k KeyDelta) Old() *string {
if k.diff == Modified || k.diff == Missing {
return sPtr(k.old)
}
return nil
}
// New returns the value of the KeyDeltaVal entry in the "new" DirectoryHierarchy
// (as determined by the ordering of parameters to Compare). Returns nil if
// there was no entry in the "old" DirectoryHierarchy.
func (k KeyDelta) New() *string {
if k.diff == Modified || k.diff == Extra {
return sPtr(k.old)
}
return nil
}
// MarshalJSON creates a JSON-encoded version of KeyDelta.
func (k KeyDelta) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Type DifferenceType `json:"type"`
Name Keyword `json:"name"`
Old string `json:"old"`
New string `json:"new"`
}{
Type: k.diff,
Name: k.name,
Old: k.old,
New: k.new,
})
}
// Like Compare, but for single inode entries only. Used to compute the
// cached version of inode.keys.
func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
// Represents the new and old states for an entry's keys.
type stateT struct {
Old *KeyVal
New *KeyVal
}
diffs := map[Keyword]*stateT{}
oldKeys := oldEntry.AllKeys()
newKeys := newEntry.AllKeys()
// Fill the map with the old keys first.
for _, kv := range oldKeys {
key := kv.Keyword()
// only add this diff if the new keys has this keyword
if key != "tar_time" && key != "time" && key.Prefix() != "xattr" && len(HasKeyword(newKeys, key)) == 0 {
continue
}
// Cannot take &kv because it's the iterator.
copy := new(KeyVal)
*copy = kv
_, ok := diffs[key]
if !ok {
diffs[key] = new(stateT)
}
diffs[key].Old = copy
}
// Then fill the new keys.
for _, kv := range newKeys {
key := kv.Keyword()
// only add this diff if the old keys has this keyword
if key != "tar_time" && key != "time" && key.Prefix() != "xattr" && len(HasKeyword(oldKeys, key)) == 0 {
continue
}
// Cannot take &kv because it's the iterator.
copy := new(KeyVal)
*copy = kv
_, ok := diffs[key]
if !ok {
diffs[key] = new(stateT)
}
diffs[key].New = copy
}
// We need a full list of the keys so we can deal with different keyvalue
// orderings.
var kws []Keyword
for kw := range diffs {
kws = append(kws, kw)
}
// If both tar_time and time were specified in the set of keys, we have to
// mess with the diffs. This is an unfortunate side-effect of tar archives.
// TODO(cyphar): This really should be abstracted inside keywords.go
if InKeywordSlice("tar_time", kws) && InKeywordSlice("time", kws) {
// Delete "time".
timeStateT := diffs["time"]
delete(diffs, "time")
// Make a new tar_time.
if diffs["tar_time"].Old == nil {
time, err := strconv.ParseFloat(timeStateT.Old.Value(), 64)
if err != nil {
return nil, fmt.Errorf("failed to parse old time: %s", err)
}
newTime := new(KeyVal)
*newTime = KeyVal(fmt.Sprintf("tar_time=%d.000000000", int64(time)))
diffs["tar_time"].Old = newTime
} else if diffs["tar_time"].New == nil {
time, err := strconv.ParseFloat(timeStateT.New.Value(), 64)
if err != nil {
return nil, fmt.Errorf("failed to parse new time: %s", err)
}
newTime := new(KeyVal)
*newTime = KeyVal(fmt.Sprintf("tar_time=%d.000000000", int64(time)))
diffs["tar_time"].New = newTime
} else {
return nil, fmt.Errorf("time and tar_time set in the same manifest")
}
}
// Are there any differences?
var results []KeyDelta
for name, diff := range diffs {
// Invalid
if diff.Old == nil && diff.New == nil {
return nil, fmt.Errorf("invalid state: both old and new are nil: key=%s", name)
}
switch {
// Missing
case diff.New == nil:
results = append(results, KeyDelta{
diff: Missing,
name: name,
old: diff.Old.Value(),
})
// Extra
case diff.Old == nil:
results = append(results, KeyDelta{
diff: Extra,
name: name,
new: diff.New.Value(),
})
// Modified
default:
if !diff.Old.Equal(*diff.New) {
results = append(results, KeyDelta{
diff: Modified,
name: name,
old: diff.Old.Value(),
new: diff.New.Value(),
})
}
}
}
return results, nil
}
// Compare compares two directory hierarchy manifests, and returns the
// list of discrepancies between the two. All of the entries in the
// manifest are considered, with differences being generated for
// RelativeType and FullType entries. Differences in structure (such as
// the way /set and /unset are written) are not considered to be
// discrepancies. The list of differences are all filesystem objects.
//
// keys controls which keys will be compared, but if keys is nil then all
// possible keys will be compared between the two manifests (allowing for
// missing entries and the like). A missing or extra key is treated as a
// Modified type.
//
// If oldDh or newDh are empty, we assume they are a hierarchy that is
// completely empty. This is purely for helping callers create synthetic
// InodeDeltas.
//
// NB: The order of the parameters matters (old, new) because Extra and
// Missing are considered as different discrepancy types.
func Compare(oldDh, newDh *DirectoryHierarchy, keys []Keyword) ([]InodeDelta, error) {
// Represents the new and old states for an entry.
type stateT struct {
Old *Entry
New *Entry
}
// To deal with different orderings of the entries, use a path-keyed
// map to make sure we don't start comparing unrelated entries.
diffs := map[string]*stateT{}
// First, iterate over the old hierarchy. If nil, pretend it's empty.
if oldDh != nil {
for _, e := range oldDh.Entries {
if e.Type == RelativeType || e.Type == FullType {
path, err := e.Path()
if err != nil {
return nil, err
}
// Cannot take &kv because it's the iterator.
cEntry := new(Entry)
*cEntry = e
_, ok := diffs[path]
if !ok {
diffs[path] = &stateT{}
}
diffs[path].Old = cEntry
}
}
}
// Then, iterate over the new hierarchy. If nil, pretend it's empty.
if newDh != nil {
for _, e := range newDh.Entries {
if e.Type == RelativeType || e.Type == FullType {
path, err := e.Path()
if err != nil {
return nil, err
}
// Cannot take &kv because it's the iterator.
cEntry := new(Entry)
*cEntry = e
_, ok := diffs[path]
if !ok {
diffs[path] = &stateT{}
}
diffs[path].New = cEntry
}
}
}
// Now we compute the diff.
var results []InodeDelta
for path, diff := range diffs {
// Invalid
if diff.Old == nil && diff.New == nil {
return nil, fmt.Errorf("invalid state: both old and new are nil: path=%s", path)
}
switch {
// Missing
case diff.New == nil:
results = append(results, InodeDelta{
diff: Missing,
path: path,
old: *diff.Old,
})
// Extra
case diff.Old == nil:
results = append(results, InodeDelta{
diff: Extra,
path: path,
new: *diff.New,
})
// Modified
default:
changed, err := compareEntry(*diff.Old, *diff.New)
if err != nil {
return nil, fmt.Errorf("comparison failed %s: %s", path, err)
}
// Now remove "changed" entries that don't match the keys.
if keys != nil {
var filterChanged []KeyDelta
for _, keyDiff := range changed {
if InKeywordSlice(keyDiff.name.Prefix(), keys) {
filterChanged = append(filterChanged, keyDiff)
}
}
changed = filterChanged
}
// Check if there were any actual changes.
if len(changed) > 0 {
results = append(results, InodeDelta{
diff: Modified,
path: path,
old: *diff.Old,
new: *diff.New,
keys: changed,
})
}
}
}
return results, nil
}

10
vendor/github.com/vbatts/go-mtree/creator.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
package mtree
// dhCreator is used in when building a DirectoryHierarchy
type dhCreator struct {
DH *DirectoryHierarchy
fs FsEval
curSet *Entry
curDir *Entry
curEnt *Entry
}

187
vendor/github.com/vbatts/go-mtree/entry.go generated vendored Normal file
View File

@@ -0,0 +1,187 @@
package mtree
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/vbatts/go-mtree/pkg/govis"
)
type byPos []Entry
func (bp byPos) Len() int { return len(bp) }
func (bp byPos) Less(i, j int) bool { return bp[i].Pos < bp[j].Pos }
func (bp byPos) Swap(i, j int) { bp[i], bp[j] = bp[j], bp[i] }
// Entry is each component of content in the mtree spec file
type Entry struct {
Parent *Entry // up
Children []*Entry // down
Prev, Next *Entry // left, right
Set *Entry // current `/set` for additional keywords
Pos int // order in the spec
Raw string // file or directory name
Name string // file or directory name
Keywords []KeyVal // TODO(vbatts) maybe a keyword typed set of values?
Type EntryType
}
// Descend searches thru an Entry's children to find the Entry associated with
// `filename`. Directories are stored at the end of an Entry's children so do a
// traverse backwards. If you descend to a "."
func (e Entry) Descend(filename string) *Entry {
if filename == "." || filename == "" {
return &e
}
numChildren := len(e.Children)
for i := range e.Children {
c := e.Children[numChildren-1-i]
if c.Name == filename {
return c
}
}
return nil
}
// Find is a wrapper around Descend that takes in a whole string path and tries
// to find that Entry
func (e Entry) Find(filepath string) *Entry {
resultnode := &e
for _, path := range strings.Split(filepath, "/") {
encoded, err := govis.Vis(path, DefaultVisFlags)
if err != nil {
return nil
}
resultnode = resultnode.Descend(encoded)
if resultnode == nil {
return nil
}
}
return resultnode
}
// Ascend gets the parent of an Entry. Serves mainly to maintain readability
// when traversing up and down an Entry tree
func (e Entry) Ascend() *Entry {
return e.Parent
}
// CleanPath makes a path safe for use with filepath.Join. This is done by not
// only cleaning the path, but also (if the path is relative) adding a leading
// '/' and cleaning it (then removing the leading '/'). This ensures that a
// path resulting from prepending another path will always resolve to lexically
// be a subdirectory of the prefixed path. This is all done lexically, so paths
// that include symlinks won't be safe as a result of using CleanPath.
//
// This code was copied from runc/libcontainer/utils/utils.go. It was
// originally written by myself, so I am dual-licensing it for the purpose of
// this project.
func CleanPath(path string) string {
// Deal with empty strings nicely.
if path == "" {
return ""
}
// Ensure that all paths are cleaned (especially problematic ones like
// "/../../../../../" which can cause lots of issues).
path = filepath.Clean(path)
// If the path isn't absolute, we need to do more processing to fix paths
// such as "../../../../<etc>/some/path". We also shouldn't convert absolute
// paths to relative ones.
if !filepath.IsAbs(path) {
path = filepath.Clean(string(os.PathSeparator) + path)
// This can't fail, as (by definition) all paths are relative to root.
path, _ = filepath.Rel(string(os.PathSeparator), path)
}
// Clean the path again for good measure.
return filepath.Clean(path)
}
// Path provides the full path of the file, despite RelativeType or FullType. It
// will be in Unvis'd form.
func (e Entry) Path() (string, error) {
decodedName, err := govis.Unvis(e.Name, DefaultVisFlags)
if err != nil {
return "", err
}
decodedName = CleanPath(decodedName)
if e.Parent == nil || e.Type == FullType {
return decodedName, nil
}
parentName, err := e.Parent.Path()
if err != nil {
return "", err
}
return CleanPath(filepath.Join(parentName, decodedName)), nil
}
// String joins a file with its associated keywords. The file name will be the
// Vis'd encoded version so that it can be parsed appropriately when Check'd.
func (e Entry) String() string {
if e.Raw != "" {
return e.Raw
}
if e.Type == BlankType {
return ""
}
if e.Type == DotDotType {
return e.Name
}
if e.Type == SpecialType || e.Type == FullType || inKeyValSlice("type=dir", e.Keywords) {
return fmt.Sprintf("%s %s", e.Name, strings.Join(KeyValToString(e.Keywords), " "))
}
return fmt.Sprintf(" %s %s", e.Name, strings.Join(KeyValToString(e.Keywords), " "))
}
// AllKeys returns the full set of KeyVal for the given entry, based on the
// /set keys as well as the entry-local keys. Entry-local keys always take
// precedence.
func (e Entry) AllKeys() []KeyVal {
if e.Set != nil {
return MergeKeyValSet(e.Set.Keywords, e.Keywords)
}
return e.Keywords
}
// IsDir checks the type= value for this entry on whether it is a directory
func (e Entry) IsDir() bool {
for _, kv := range e.AllKeys() {
if kv.Keyword().Prefix() == "type" {
return kv.Value() == "dir"
}
}
return false
}
// EntryType are the formats of lines in an mtree spec file
type EntryType int
// The types of lines to be found in an mtree spec file
const (
SignatureType EntryType = iota // first line of the file, like `#mtree v2.0`
BlankType // blank lines are ignored
CommentType // Lines beginning with `#` are ignored
SpecialType // line that has `/` prefix issue a "special" command (currently only /set and /unset)
RelativeType // if the first white-space delimited word does not have a '/' in it. Options/keywords are applied.
DotDotType // .. - A relative path step. keywords/options are ignored
FullType // if the first word on the line has a `/` after the first character, it interpretted as a file pathname with options
)
// String returns the name of the EntryType
func (et EntryType) String() string {
return typeNames[et]
}
var typeNames = map[EntryType]string{
SignatureType: "SignatureType",
BlankType: "BlankType",
CommentType: "CommentType",
SpecialType: "SpecialType",
RelativeType: "RelativeType",
DotDotType: "DotDotType",
FullType: "FullType",
}

54
vendor/github.com/vbatts/go-mtree/fseval.go generated vendored Normal file
View File

@@ -0,0 +1,54 @@
package mtree
import "os"
// FsEval is a mock-friendly method of specifying to go-mtree how to carry out
// filesystem operations such as opening files and the like. The semantics of
// all of these wrappers MUST be identical to the semantics described here.
type FsEval interface {
// Open must have the same semantics as os.Open.
Open(path string) (*os.File, error)
// Lstat must have the same semantics as os.Lstat.
Lstat(path string) (os.FileInfo, error)
// Readdir must have the same semantics as calling os.Open on the given
// path and then returning the result of (*os.File).Readdir(-1).
Readdir(path string) ([]os.FileInfo, error)
// KeywordFunc must return a wrapper around the provided function (in other
// words, the returned function must refer to the same keyword).
KeywordFunc(fn KeywordFunc) KeywordFunc
}
// DefaultFsEval is the default implementation of FsEval (and is the default
// used if a nil interface is passed to any mtree function). It does not modify
// or wrap any of the methods (they all just call out to os.*).
type DefaultFsEval struct{}
// Open must have the same semantics as os.Open.
func (fs DefaultFsEval) Open(path string) (*os.File, error) {
return os.Open(path)
}
// Lstat must have the same semantics as os.Lstat.
func (fs DefaultFsEval) Lstat(path string) (os.FileInfo, error) {
return os.Lstat(path)
}
// Readdir must have the same semantics as calling os.Open on the given
// path and then returning the result of (*os.File).Readdir(-1).
func (fs DefaultFsEval) Readdir(path string) ([]os.FileInfo, error) {
fh, err := os.Open(path)
if err != nil {
return nil, err
}
defer fh.Close()
return fh.Readdir(-1)
}
// KeywordFunc must return a wrapper around the provided function (in other
// words, the returned function must refer to the same keyword).
func (fs DefaultFsEval) KeywordFunc(fn KeywordFunc) KeywordFunc {
return fn
}

21
vendor/github.com/vbatts/go-mtree/glide.lock generated vendored Normal file
View File

@@ -0,0 +1,21 @@
hash: 8b0df7f603e6b580aa2640d99d3fa7430198f7db89321ff2abf76efa969d14c2
updated: 2018-08-20T07:56:40.333174254-04:00
imports:
- name: github.com/fatih/color
version: 5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4
- name: github.com/sirupsen/logrus
version: 3e01752db0189b9157070a0e1668a620f9a85da2
- name: golang.org/x/crypto
version: 1351f936d976c60a0a48d728281922cf63eafb8d
subpackages:
- ripemd160
- ssh/terminal
- name: golang.org/x/sys
version: 8dbc5d05d6edcc104950cc299a1ce6641235bc86
subpackages:
- unix
testImports:
- name: github.com/davecgh/go-spew
version: 8991bc29aa16c548c550c7ff78260e27b9ab7c73
subpackages:
- spew

16
vendor/github.com/vbatts/go-mtree/glide.yaml generated vendored Normal file
View File

@@ -0,0 +1,16 @@
package: github.com/vbatts/go-mtree
description: File systems verification utility and library, in likeness of mtree(8)
homepage: https://github.com/vbatts/go-mtree
license: BSD-3-Clause
import:
- package: golang.org/x/crypto
subpackages:
- ripemd160
- package: github.com/sirupsen/logrus
version: ^1.0.0
- package: golang.org/x/sys
version: 8dbc5d05d6edcc104950cc299a1ce6641235bc86
subpackages:
- unix
- package: github.com/fatih/color
version: ^1.6.0

48
vendor/github.com/vbatts/go-mtree/hierarchy.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
package mtree
import (
"io"
"sort"
)
// DirectoryHierarchy is the mapped structure for an mtree directory hierarchy
// spec
type DirectoryHierarchy struct {
Entries []Entry
}
// WriteTo simplifies the output of the resulting hierarchy spec
func (dh DirectoryHierarchy) WriteTo(w io.Writer) (n int64, err error) {
sort.Sort(byPos(dh.Entries))
var sum int64
for _, e := range dh.Entries {
str := e.String()
i, err := io.WriteString(w, str+"\n")
if err != nil {
return sum, err
}
sum += int64(i)
}
return sum, nil
}
// UsedKeywords collects and returns all the keywords used in a
// a DirectoryHierarchy
func (dh DirectoryHierarchy) UsedKeywords() []Keyword {
usedkeywords := []Keyword{}
for _, e := range dh.Entries {
switch e.Type {
case FullType, RelativeType, SpecialType:
if e.Type != SpecialType || e.Name == "/set" {
kvs := e.Keywords
for _, kv := range kvs {
kw := KeyVal(kv).Keyword().Prefix()
if !InKeywordSlice(kw, usedkeywords) {
usedkeywords = append(usedkeywords, KeywordSynonym(string(kw)))
}
}
}
}
}
return usedkeywords
}

172
vendor/github.com/vbatts/go-mtree/keywordfunc.go generated vendored Normal file
View File

@@ -0,0 +1,172 @@
package mtree
import (
"archive/tar"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"io"
"os"
"github.com/vbatts/go-mtree/pkg/govis"
"golang.org/x/crypto/ripemd160"
)
// KeywordFunc is the type of a function called on each file to be included in
// a DirectoryHierarchy, that will produce the string output of the keyword to
// be included for the file entry. Otherwise, empty string.
// io.Reader `r` is to the file stream for the file payload. While this
// function takes an io.Reader, the caller needs to reset it to the beginning
// for each new KeywordFunc
type KeywordFunc func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error)
var (
// KeywordFuncs is the map of all keywords (and the functions to produce them)
KeywordFuncs = map[Keyword]KeywordFunc{
"size": sizeKeywordFunc, // The size, in bytes, of the file
"type": typeKeywordFunc, // The type of the file
"time": timeKeywordFunc, // The last modification time of the file
"link": linkKeywordFunc, // The target of the symbolic link when type=link
"uid": uidKeywordFunc, // The file owner as a numeric value
"gid": gidKeywordFunc, // The file group as a numeric value
"nlink": nlinkKeywordFunc, // The number of hard links the file is expected to have
"uname": unameKeywordFunc, // The file owner as a symbolic name
"gname": gnameKeywordFunc, // The file group as a symbolic name
"mode": modeKeywordFunc, // The current file's permissions as a numeric (octal) or symbolic value
"cksum": cksumKeywordFunc, // The checksum of the file using the default algorithm specified by the cksum(1) utility
"md5": hasherKeywordFunc("md5digest", md5.New), // The MD5 message digest of the file
"md5digest": hasherKeywordFunc("md5digest", md5.New), // A synonym for `md5`
"rmd160": hasherKeywordFunc("ripemd160digest", ripemd160.New), // The RIPEMD160 message digest of the file
"rmd160digest": hasherKeywordFunc("ripemd160digest", ripemd160.New), // A synonym for `rmd160`
"ripemd160digest": hasherKeywordFunc("ripemd160digest", ripemd160.New), // A synonym for `rmd160`
"sha1": hasherKeywordFunc("sha1digest", sha1.New), // The SHA1 message digest of the file
"sha1digest": hasherKeywordFunc("sha1digest", sha1.New), // A synonym for `sha1`
"sha256": hasherKeywordFunc("sha256digest", sha256.New), // The SHA256 message digest of the file
"sha256digest": hasherKeywordFunc("sha256digest", sha256.New), // A synonym for `sha256`
"sha384": hasherKeywordFunc("sha384digest", sha512.New384), // The SHA384 message digest of the file
"sha384digest": hasherKeywordFunc("sha384digest", sha512.New384), // A synonym for `sha384`
"sha512": hasherKeywordFunc("sha512digest", sha512.New), // The SHA512 message digest of the file
"sha512digest": hasherKeywordFunc("sha512digest", sha512.New), // A synonym for `sha512`
"sha512256": hasherKeywordFunc("sha512digest", sha512.New512_256), // The SHA512/256 message digest of the file
"sha512256digest": hasherKeywordFunc("sha512digest", sha512.New512_256), // A synonym for `sha512256`
"flags": flagsKeywordFunc, // NOTE: this is a noop, but here to support the presence of the "flags" keyword.
// This is not an upstreamed keyword, but used to vary from "time", as tar
// archives do not store nanosecond precision. So comparing on "time" will
// be only seconds level accurate.
"tar_time": tartimeKeywordFunc, // The last modification time of the file, from a tar archive mtime
// This is not an upstreamed keyword, but a needed attribute for file validation.
// The pattern for this keyword key is prefixed by "xattr." followed by the extended attribute "namespace.key".
// The keyword value is the SHA1 digest of the extended attribute's value.
// In this way, the order of the keys does not matter, and the contents of the value is not revealed.
"xattr": xattrKeywordFunc,
"xattrs": xattrKeywordFunc,
}
)
var (
modeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
permissions := info.Mode().Perm()
if os.ModeSetuid&info.Mode() > 0 {
permissions |= (1 << 11)
}
if os.ModeSetgid&info.Mode() > 0 {
permissions |= (1 << 10)
}
if os.ModeSticky&info.Mode() > 0 {
permissions |= (1 << 9)
}
return []KeyVal{KeyVal(fmt.Sprintf("mode=%#o", permissions))}, nil
}
sizeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if sys, ok := info.Sys().(*tar.Header); ok {
if sys.Typeflag == tar.TypeSymlink {
return []KeyVal{KeyVal(fmt.Sprintf("size=%d", len(sys.Linkname)))}, nil
}
}
return []KeyVal{KeyVal(fmt.Sprintf("size=%d", info.Size()))}, nil
}
cksumKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if !info.Mode().IsRegular() {
return nil, nil
}
sum, _, err := cksum(r)
if err != nil {
return nil, err
}
return []KeyVal{KeyVal(fmt.Sprintf("cksum=%d", sum))}, nil
}
hasherKeywordFunc = func(name string, newHash func() hash.Hash) KeywordFunc {
return func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if !info.Mode().IsRegular() {
return nil, nil
}
h := newHash()
if _, err := io.Copy(h, r); err != nil {
return nil, err
}
return []KeyVal{KeyVal(fmt.Sprintf("%s=%x", KeywordSynonym(name), h.Sum(nil)))}, nil
}
}
tartimeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return []KeyVal{KeyVal(fmt.Sprintf("tar_time=%d.%9.9d", info.ModTime().Unix(), 0))}, nil
}
timeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
tSec := info.ModTime().Unix()
tNano := info.ModTime().Nanosecond()
return []KeyVal{KeyVal(fmt.Sprintf("time=%d.%9.9d", tSec, tNano))}, nil
}
linkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if sys, ok := info.Sys().(*tar.Header); ok {
if sys.Linkname != "" {
linkname, err := govis.Vis(sys.Linkname, DefaultVisFlags)
if err != nil {
return nil, nil
}
return []KeyVal{KeyVal(fmt.Sprintf("link=%s", linkname))}, nil
}
return nil, nil
}
if info.Mode()&os.ModeSymlink != 0 {
str, err := os.Readlink(path)
if err != nil {
return nil, nil
}
linkname, err := govis.Vis(str, DefaultVisFlags)
if err != nil {
return nil, nil
}
return []KeyVal{KeyVal(fmt.Sprintf("link=%s", linkname))}, nil
}
return nil, nil
}
typeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if info.Mode().IsDir() {
return []KeyVal{"type=dir"}, nil
}
if info.Mode().IsRegular() {
return []KeyVal{"type=file"}, nil
}
if info.Mode()&os.ModeSocket != 0 {
return []KeyVal{"type=socket"}, nil
}
if info.Mode()&os.ModeSymlink != 0 {
return []KeyVal{"type=link"}, nil
}
if info.Mode()&os.ModeNamedPipe != 0 {
return []KeyVal{"type=fifo"}, nil
}
if info.Mode()&os.ModeDevice != 0 {
if info.Mode()&os.ModeCharDevice != 0 {
return []KeyVal{"type=char"}, nil
}
return []KeyVal{"type=block"}, nil
}
return nil, nil
}
)

69
vendor/github.com/vbatts/go-mtree/keywordfuncs_bsd.go generated vendored Normal file
View File

@@ -0,0 +1,69 @@
// +build darwin freebsd netbsd openbsd
package mtree
import (
"archive/tar"
"fmt"
"io"
"os"
"os/user"
"syscall"
)
var (
flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
// ideally this will pull in from here https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2
return nil, nil
}
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", hdr.Uname))}, nil
}
stat := info.Sys().(*syscall.Stat_t)
u, err := user.LookupId(fmt.Sprintf("%d", stat.Uid))
if err != nil {
return nil, err
}
return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", u.Username))}, nil
}
gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", hdr.Gname))}, nil
}
stat := info.Sys().(*syscall.Stat_t)
g, err := lookupGroupID(fmt.Sprintf("%d", stat.Gid))
if err != nil {
return nil, err
}
return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", g.Name))}, nil
}
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", hdr.Uid))}, nil
}
stat := info.Sys().(*syscall.Stat_t)
return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", stat.Uid))}, nil
}
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", hdr.Gid))}, nil
}
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", stat.Gid))}, nil
}
return nil, nil
}
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
return []KeyVal{KeyVal(fmt.Sprintf("nlink=%d", stat.Nlink))}, nil
}
return nil, nil
}
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return nil, nil
}
)

107
vendor/github.com/vbatts/go-mtree/keywordfuncs_linux.go generated vendored Normal file
View File

@@ -0,0 +1,107 @@
// +build linux
package mtree
import (
"archive/tar"
"encoding/base64"
"fmt"
"io"
"os"
"os/user"
"syscall"
"github.com/vbatts/go-mtree/pkg/govis"
"github.com/vbatts/go-mtree/xattr"
)
var (
// this is bsd specific https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2
flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return nil, nil
}
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", hdr.Uname))}, nil
}
stat := info.Sys().(*syscall.Stat_t)
u, err := user.LookupId(fmt.Sprintf("%d", stat.Uid))
if err != nil {
return nil, nil
}
return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", u.Username))}, nil
}
gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", hdr.Gname))}, nil
}
stat := info.Sys().(*syscall.Stat_t)
g, err := lookupGroupID(fmt.Sprintf("%d", stat.Gid))
if err != nil {
return nil, nil
}
return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", g.Name))}, nil
}
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", hdr.Uid))}, nil
}
stat := info.Sys().(*syscall.Stat_t)
return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", stat.Uid))}, nil
}
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", hdr.Gid))}, nil
}
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", stat.Gid))}, nil
}
return nil, nil
}
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
return []KeyVal{KeyVal(fmt.Sprintf("nlink=%d", stat.Nlink))}, nil
}
return nil, nil
}
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
if len(hdr.Xattrs) == 0 {
return nil, nil
}
klist := []KeyVal{}
for k, v := range hdr.Xattrs {
encKey, err := govis.Vis(k, DefaultVisFlags)
if err != nil {
return nil, nil
}
klist = append(klist, KeyVal(fmt.Sprintf("xattr.%s=%s", encKey, base64.StdEncoding.EncodeToString([]byte(v)))))
}
return klist, nil
}
if !info.Mode().IsRegular() && !info.Mode().IsDir() {
return nil, nil
}
xlist, err := xattr.List(path)
if err != nil {
return nil, nil
}
klist := make([]KeyVal, len(xlist))
for i := range xlist {
data, err := xattr.Get(path, xlist[i])
if err != nil {
return nil, nil
}
encKey, err := govis.Vis(xlist[i], DefaultVisFlags)
if err != nil {
return nil, nil
}
klist[i] = KeyVal(fmt.Sprintf("xattr.%s=%s", encKey, base64.StdEncoding.EncodeToString(data)))
}
return klist, nil
}
)

View File

@@ -0,0 +1,47 @@
// +build !linux,!darwin,!freebsd,!netbsd,!openbsd
package mtree
import (
"archive/tar"
"fmt"
"io"
"os"
)
var (
// this is bsd specific https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2
flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return nil, nil
}
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", hdr.Uname))}, nil
}
return nil, nil
}
gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", hdr.Gname))}, nil
}
return nil, nil
}
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", hdr.Uid))}, nil
}
return nil, nil
}
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok {
return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", hdr.Gid))}, nil
}
return nil, nil
}
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return nil, nil
}
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return nil, nil
}
)

327
vendor/github.com/vbatts/go-mtree/keywords.go generated vendored Normal file
View File

@@ -0,0 +1,327 @@
package mtree
import (
"fmt"
"strings"
"github.com/vbatts/go-mtree/pkg/govis"
)
// DefaultVisFlags is the set of Vis flags used when encoding filenames and
// other similar entries.
const DefaultVisFlags govis.VisFlag = govis.VisWhite | govis.VisOctal | govis.VisGlob
// Keyword is the string name of a keyword, with some convenience functions for
// determining whether it is a default or bsd standard keyword.
// It first portion before the "="
type Keyword string
// Prefix is the portion of the keyword before a first "." (if present).
//
// Primarly for the xattr use-case, where the keyword `xattr.security.selinux` would have a Suffix of `security.selinux`.
func (k Keyword) Prefix() Keyword {
if strings.Contains(string(k), ".") {
return Keyword(strings.SplitN(string(k), ".", 2)[0])
}
return k
}
// Suffix is the portion of the keyword after a first ".".
// This is an option feature.
//
// Primarly for the xattr use-case, where the keyword `xattr.security.selinux` would have a Suffix of `security.selinux`.
func (k Keyword) Suffix() string {
if strings.Contains(string(k), ".") {
return strings.SplitN(string(k), ".", 2)[1]
}
return string(k)
}
// Default returns whether this keyword is in the default set of keywords
func (k Keyword) Default() bool {
return InKeywordSlice(k, DefaultKeywords)
}
// Bsd returns whether this keyword is in the upstream FreeBSD mtree(8)
func (k Keyword) Bsd() bool {
return InKeywordSlice(k, BsdKeywords)
}
// Synonym returns the canonical name for this keyword. This is provides the
// same functionality as KeywordSynonym()
func (k Keyword) Synonym() Keyword {
return KeywordSynonym(string(k))
}
// InKeywordSlice checks for the presence of `a` in `list`
func InKeywordSlice(a Keyword, list []Keyword) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
func inKeyValSlice(a KeyVal, list []KeyVal) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// ToKeywords makes a list of Keyword from a list of string
func ToKeywords(list []string) []Keyword {
ret := make([]Keyword, len(list))
for i := range list {
ret[i] = Keyword(list[i])
}
return ret
}
// FromKeywords makes a list of string from a list of Keyword
func FromKeywords(list []Keyword) []string {
ret := make([]string, len(list))
for i := range list {
ret[i] = string(list[i])
}
return ret
}
// KeyValToString constructs a list of string from the list of KeyVal
func KeyValToString(list []KeyVal) []string {
ret := make([]string, len(list))
for i := range list {
ret[i] = string(list[i])
}
return ret
}
// StringToKeyVals constructs a list of KeyVal from the list of strings, like "keyword=value"
func StringToKeyVals(list []string) []KeyVal {
ret := make([]KeyVal, len(list))
for i := range list {
ret[i] = KeyVal(list[i])
}
return ret
}
// KeyVal is a "keyword=value"
type KeyVal string
// Keyword is the mapping to the available keywords
func (kv KeyVal) Keyword() Keyword {
if !strings.Contains(string(kv), "=") {
return Keyword("")
}
return Keyword(strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0])
}
// Value is the data/value portion of "keyword=value"
func (kv KeyVal) Value() string {
if !strings.Contains(string(kv), "=") {
return ""
}
return strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[1]
}
// NewValue returns a new KeyVal with the newval
func (kv KeyVal) NewValue(newval string) KeyVal {
return KeyVal(fmt.Sprintf("%s=%s", kv.Keyword(), newval))
}
// Equal returns whether two KeyVal are equivalent. This takes
// care of certain odd cases such as tar_mtime, and should be used over
// using == comparisons directly unless you really know what you're
// doing.
func (kv KeyVal) Equal(b KeyVal) bool {
// TODO: Implement handling of tar_mtime.
return kv.Keyword() == b.Keyword() && kv.Value() == b.Value()
}
func keywordPrefixes(kvset []Keyword) []Keyword {
kvs := []Keyword{}
for _, kv := range kvset {
kvs = append(kvs, kv.Prefix())
}
return kvs
}
// keyvalSelector takes an array of KeyVal ("keyword=value") and filters out
// that only the set of keywords
func keyvalSelector(keyval []KeyVal, keyset []Keyword) []KeyVal {
retList := []KeyVal{}
for _, kv := range keyval {
if InKeywordSlice(kv.Keyword().Prefix(), keywordPrefixes(keyset)) {
retList = append(retList, kv)
}
}
return retList
}
func keyValDifference(this, that []KeyVal) []KeyVal {
if len(this) == 0 {
return that
}
diff := []KeyVal{}
for _, kv := range this {
if !inKeyValSlice(kv, that) {
diff = append(diff, kv)
}
}
return diff
}
func keyValCopy(set []KeyVal) []KeyVal {
ret := make([]KeyVal, len(set))
for i := range set {
ret[i] = set[i]
}
return ret
}
// Has the "keyword" present in the list of KeyVal, and returns the
// corresponding KeyVal, else an empty string.
func Has(keyvals []KeyVal, keyword string) []KeyVal {
return HasKeyword(keyvals, Keyword(keyword))
}
// HasKeyword the "keyword" present in the list of KeyVal, and returns the
// corresponding KeyVal, else an empty string.
// This match is done on the Prefix of the keyword only.
func HasKeyword(keyvals []KeyVal, keyword Keyword) []KeyVal {
kvs := []KeyVal{}
for i := range keyvals {
if keyvals[i].Keyword().Prefix() == keyword.Prefix() {
kvs = append(kvs, keyvals[i])
}
}
return kvs
}
// MergeSet takes the current setKeyVals, and then applies the entryKeyVals
// such that the entry's values win. The union is returned.
func MergeSet(setKeyVals, entryKeyVals []string) []KeyVal {
retList := StringToKeyVals(setKeyVals)
eKVs := StringToKeyVals(entryKeyVals)
return MergeKeyValSet(retList, eKVs)
}
// MergeKeyValSet does a merge of the two sets of KeyVal, and the KeyVal of
// entryKeyVals win when there is a duplicate Keyword.
func MergeKeyValSet(setKeyVals, entryKeyVals []KeyVal) []KeyVal {
retList := keyValCopy(setKeyVals)
seenKeywords := []Keyword{}
for i := range retList {
word := retList[i].Keyword()
for _, kv := range HasKeyword(entryKeyVals, word) {
// match on the keyword prefix and suffix here
if kv.Keyword() == word {
retList[i] = kv
}
}
seenKeywords = append(seenKeywords, word)
}
for i := range entryKeyVals {
if !InKeywordSlice(entryKeyVals[i].Keyword(), seenKeywords) {
retList = append(retList, entryKeyVals[i])
}
}
return retList
}
var (
// DefaultKeywords has the several default keyword producers (uid, gid,
// mode, nlink, type, size, mtime)
DefaultKeywords = []Keyword{
"size",
"type",
"uid",
"gid",
"mode",
"link",
"nlink",
"time",
}
// DefaultTarKeywords has keywords that should be used when creating a manifest from
// an archive. Currently, evaluating the # of hardlinks has not been implemented yet
DefaultTarKeywords = []Keyword{
"size",
"type",
"uid",
"gid",
"mode",
"link",
"tar_time",
}
// BsdKeywords is the set of keywords that is only in the upstream FreeBSD mtree
BsdKeywords = []Keyword{
"cksum",
"flags", // this one is really mostly BSD specific ...
"ignore",
"gid",
"gname",
"link",
"md5",
"md5digest",
"mode",
"nlink",
"nochange",
"optional",
"ripemd160digest",
"rmd160",
"rmd160digest",
"sha1",
"sha1digest",
"sha256",
"sha256digest",
"sha384",
"sha384digest",
"sha512",
"sha512digest",
"size",
"tags",
"time",
"type",
"uid",
"uname",
}
// SetKeywords is the default set of keywords calculated for a `/set` SpecialType
SetKeywords = []Keyword{
"uid",
"gid",
}
)
// KeywordSynonym returns the canonical name for keywords that have synonyms,
// and just returns the name provided if there is no synonym. In this way it
// ought to be safe to wrap any keyword name.
func KeywordSynonym(name string) Keyword {
var retname string
switch name {
case "md5":
retname = "md5digest"
case "rmd160":
retname = "ripemd160digest"
case "rmd160digest":
retname = "ripemd160digest"
case "sha1":
retname = "sha1digest"
case "sha256":
retname = "sha256digest"
case "sha384":
retname = "sha384digest"
case "sha512":
retname = "sha512digest"
case "sha512256":
retname = "sha512256digest"
case "xattrs":
retname = "xattr"
default:
retname = name
}
return Keyword(retname)
}

22
vendor/github.com/vbatts/go-mtree/lchtimes_unix.go generated vendored Normal file
View File

@@ -0,0 +1,22 @@
// +build darwin dragonfly freebsd openbsd linux netbsd solaris
package mtree
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func lchtimes(name string, atime time.Time, mtime time.Time) error {
utimes := []unix.Timespec{
unix.NsecToTimespec(atime.UnixNano()),
unix.NsecToTimespec(mtime.UnixNano()),
}
if e := unix.UtimesNanoAt(unix.AT_FDCWD, name, utimes, unix.AT_SYMLINK_NOFOLLOW); e != nil {
return &os.PathError{Op: "chtimes", Path: name, Err: e}
}
return nil
}

View File

@@ -0,0 +1,11 @@
// +build windows
package mtree
import (
"time"
)
func lchtimes(name string, atime time.Time, mtime time.Time) error {
return nil
}

9
vendor/github.com/vbatts/go-mtree/lookup_new.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
// +build go1.7
package mtree
import (
"os/user"
)
var lookupGroupID = user.LookupGroupId

102
vendor/github.com/vbatts/go-mtree/lookup_old.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
// +build !go1.7
package mtree
import (
"bufio"
"bytes"
"io"
"os"
"strconv"
"strings"
)
const groupFile = "/etc/group"
var colon = []byte{':'}
// Group represents a grouping of users.
//
// On POSIX systems Gid contains a decimal number representing the group ID.
type Group struct {
Gid string // group ID
Name string // group name
}
func lookupGroupID(id string) (*Group, error) {
f, err := os.Open(groupFile)
if err != nil {
return nil, err
}
defer f.Close()
return findGroupID(id, f)
}
func findGroupID(id string, r io.Reader) (*Group, error) {
if v, err := readColonFile(r, matchGroupIndexValue(id, 2)); err != nil {
return nil, err
} else if v != nil {
return v.(*Group), nil
}
return nil, UnknownGroupIDError(id)
}
// lineFunc returns a value, an error, or (nil, nil) to skip the row.
type lineFunc func(line []byte) (v interface{}, err error)
// readColonFile parses r as an /etc/group or /etc/passwd style file, running
// fn for each row. readColonFile returns a value, an error, or (nil, nil) if
// the end of the file is reached without a match.
func readColonFile(r io.Reader, fn lineFunc) (v interface{}, err error) {
bs := bufio.NewScanner(r)
for bs.Scan() {
line := bs.Bytes()
// There's no spec for /etc/passwd or /etc/group, but we try to follow
// the same rules as the glibc parser, which allows comments and blank
// space at the beginning of a line.
line = bytes.TrimSpace(line)
if len(line) == 0 || line[0] == '#' {
continue
}
v, err = fn(line)
if v != nil || err != nil {
return
}
}
return nil, bs.Err()
}
func matchGroupIndexValue(value string, idx int) lineFunc {
var leadColon string
if idx > 0 {
leadColon = ":"
}
substr := []byte(leadColon + value + ":")
return func(line []byte) (v interface{}, err error) {
if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 {
return
}
// wheel:*:0:root
parts := strings.SplitN(string(line), ":", 4)
if len(parts) < 4 || parts[0] == "" || parts[idx] != value ||
// If the file contains +foo and you search for "foo", glibc
// returns an "invalid argument" error. Similarly, if you search
// for a gid for a row where the group name starts with "+" or "-",
// glibc fails to find the record.
parts[0][0] == '+' || parts[0][0] == '-' {
return
}
if _, err := strconv.Atoi(parts[2]); err != nil {
return nil, nil
}
return &Group{Name: parts[0], Gid: parts[2]}, nil
}
}
// UnknownGroupIDError is returned by LookupGroupId when
// a group cannot be found.
type UnknownGroupIDError string
func (e UnknownGroupIDError) Error() string {
return "group: unknown groupid " + string(e)
}

105
vendor/github.com/vbatts/go-mtree/parse.go generated vendored Normal file
View File

@@ -0,0 +1,105 @@
package mtree
import (
"bufio"
"io"
"path/filepath"
"strings"
)
// ParseSpec reads a stream of an mtree specification, and returns the DirectoryHierarchy
func ParseSpec(r io.Reader) (*DirectoryHierarchy, error) {
s := bufio.NewScanner(r)
i := int(0)
creator := dhCreator{
DH: &DirectoryHierarchy{},
}
for s.Scan() {
str := s.Text()
trimmedStr := strings.TrimLeftFunc(str, func(c rune) bool {
return c == ' ' || c == '\t'
})
e := Entry{Pos: i}
switch {
case strings.HasPrefix(trimmedStr, "#"):
e.Raw = str
if strings.HasPrefix(trimmedStr, "#mtree") {
e.Type = SignatureType
} else {
e.Type = CommentType
// from here, the comment could be "# key: value" metadata
// or a relative path hint
}
case str == "":
e.Type = BlankType
// nothing else to do here
case strings.HasPrefix(str, "/"):
e.Type = SpecialType
// collapse any escaped newlines
for {
if strings.HasSuffix(str, `\`) {
str = str[:len(str)-1]
s.Scan()
str += s.Text()
} else {
break
}
}
// parse the options
f := strings.Fields(str)
e.Name = f[0]
e.Keywords = StringToKeyVals(f[1:])
if e.Name == "/set" {
creator.curSet = &e
} else if e.Name == "/unset" {
creator.curSet = nil
}
case len(strings.Fields(str)) > 0 && strings.Fields(str)[0] == "..":
e.Type = DotDotType
e.Raw = str
if creator.curDir != nil {
creator.curDir = creator.curDir.Parent
}
// nothing else to do here
case len(strings.Fields(str)) > 0:
// collapse any escaped newlines
for {
if strings.HasSuffix(str, `\`) {
str = str[:len(str)-1]
s.Scan()
str += s.Text()
} else {
break
}
}
// parse the options
f := strings.Fields(str)
e.Name = filepath.Clean(f[0])
if strings.Contains(e.Name, "/") {
e.Type = FullType
} else {
e.Type = RelativeType
}
e.Keywords = StringToKeyVals(f[1:])
// TODO: gather keywords if using tar stream
e.Parent = creator.curDir
for i := range e.Keywords {
kv := KeyVal(e.Keywords[i])
if kv.Keyword() == "type" {
if kv.Value() == "dir" {
creator.curDir = &e
} else {
creator.curEnt = &e
}
}
}
e.Set = creator.curSet
default:
// TODO(vbatts) log a warning?
continue
}
creator.DH.Entries = append(creator.DH.Entries, e)
i++
}
return creator.DH, s.Err()
}

202
vendor/github.com/vbatts/go-mtree/pkg/govis/COPYING generated vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

27
vendor/github.com/vbatts/go-mtree/pkg/govis/README.md generated vendored Normal file
View File

@@ -0,0 +1,27 @@
## `govis` ##
`govis` is a BSD-compatible `vis(3)` and `unvis(3)` encoding implementation
that is unicode aware and written in Go. None of this code comes from the
original BSD code, nor does it come from `go-mtree`'s port of said code.
Because 80s BSD code is not very nice to read.
### License ###
`govis` is licensed under the Apache 2.0 license.
```
govis: unicode aware vis(3) encoding implementation
Copyright (C) 2017 SUSE LLC.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```

39
vendor/github.com/vbatts/go-mtree/pkg/govis/govis.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
/*
* govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package govis
// VisFlag manipulates how the characters are encoded/decoded
type VisFlag uint
// vis() has a variety of flags when deciding what encodings to use. While
// mtree only uses one set of flags, implementing them all is necessary in
// order to have compatibility with BSD's vis() and unvis() commands.
const (
VisOctal VisFlag = (1 << iota) // VIS_OCTAL: Use octal \ddd format.
VisCStyle // VIS_CSTYLE: Use \[nrft0..] where appropriate.
VisSpace // VIS_SP: Also encode space.
VisTab // VIS_TAB: Also encode tab.
VisNewline // VIS_NL: Also encode newline.
VisSafe // VIS_SAFE: Encode unsafe characters.
VisNoSlash // VIS_NOSLASH: Inhibit printing '\'.
VisHTTPStyle // VIS_HTTPSTYLE: HTTP-style escape %xx.
VisGlob // VIS_GLOB: Encode glob(3) magics.
visMask VisFlag = (1 << iota) - 1 // Mask of all flags.
VisWhite VisFlag = (VisSpace | VisTab | VisNewline)
)

294
vendor/github.com/vbatts/go-mtree/pkg/govis/unvis.go generated vendored Normal file
View File

@@ -0,0 +1,294 @@
/*
* govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package govis
import (
"fmt"
"strconv"
"unicode"
)
// unvisParser stores the current state of the token parser.
type unvisParser struct {
tokens []rune
idx int
flag VisFlag
}
// Next moves the index to the next character.
func (p *unvisParser) Next() {
p.idx++
}
// Peek gets the current token.
func (p *unvisParser) Peek() (rune, error) {
if p.idx >= len(p.tokens) {
return unicode.ReplacementChar, fmt.Errorf("tried to read past end of token list")
}
return p.tokens[p.idx], nil
}
// End returns whether all of the tokens have been consumed.
func (p *unvisParser) End() bool {
return p.idx >= len(p.tokens)
}
func newParser(input string, flag VisFlag) *unvisParser {
return &unvisParser{
tokens: []rune(input),
idx: 0,
flag: flag,
}
}
// While a recursive descent parser is overkill for parsing simple escape
// codes, this is IMO much easier to read than the ugly 80s coroutine code used
// by the original unvis(3) parser. Here's the EBNF for an unvis sequence:
//
// <input> ::= (<rune>)*
// <rune> ::= ("\" <escape-sequence>) | ("%" <escape-hex>) | <plain-rune>
// <plain-rune> ::= any rune
// <escape-sequence> ::= ("x" <escape-hex>) | ("M" <escape-meta>) | ("^" <escape-ctrl) | <escape-cstyle> | <escape-octal>
// <escape-meta> ::= ("-" <escape-meta1>) | ("^" <escape-ctrl>)
// <escape-meta1> ::= any rune
// <escape-ctrl> ::= "?" | any rune
// <escape-cstyle> ::= "\" | "n" | "r" | "b" | "a" | "v" | "t" | "f"
// <escape-hex> ::= [0-9a-f] [0-9a-f]
// <escape-octal> ::= [0-7] ([0-7] ([0-7])?)?
func unvisPlainRune(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
return nil, fmt.Errorf("plain rune: %c", ch)
}
p.Next()
// XXX: Maybe we should not be converting to runes and then back to strings
// here. Are we sure that the byte-for-byte representation is the
// same? If the bytes change, then using these strings for paths will
// break...
str := string(ch)
return []byte(str), nil
}
func unvisEscapeCStyle(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
return nil, fmt.Errorf("escape hex: %s", err)
}
output := ""
switch ch {
case 'n':
output = "\n"
case 'r':
output = "\r"
case 'b':
output = "\b"
case 'a':
output = "\x07"
case 'v':
output = "\v"
case 't':
output = "\t"
case 'f':
output = "\f"
case 's':
output = " "
case 'E':
output = "\x1b"
case '\n':
// Hidden newline.
case '$':
// Hidden marker.
default:
// XXX: We should probably allow falling through and return "\" here...
return nil, fmt.Errorf("escape cstyle: unknown escape character: %q", ch)
}
p.Next()
return []byte(output), nil
}
func unvisEscapeDigits(p *unvisParser, base int, force bool) ([]byte, error) {
var code int
for i := int(0xFF); i > 0; i /= base {
ch, err := p.Peek()
if err != nil {
if !force && i != 0xFF {
break
}
return nil, fmt.Errorf("escape base %d: %s", base, err)
}
digit, err := strconv.ParseInt(string(ch), base, 8)
if err != nil {
if !force && i != 0xFF {
break
}
return nil, fmt.Errorf("escape base %d: could not parse digit: %s", base, err)
}
code = (code * base) + int(digit)
p.Next()
}
if code > unicode.MaxLatin1 {
return nil, fmt.Errorf("escape base %d: code %q outside latin-1 encoding", base, code)
}
char := byte(code & 0xFF)
return []byte{char}, nil
}
func unvisEscapeCtrl(p *unvisParser, mask byte) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
return nil, fmt.Errorf("escape ctrl: %s", err)
}
if ch > unicode.MaxLatin1 {
return nil, fmt.Errorf("escape ctrl: code %q outside latin-1 encoding", ch)
}
char := byte(ch) & 0x1f
if ch == '?' {
char = 0x7f
}
p.Next()
return []byte{mask | char}, nil
}
func unvisEscapeMeta(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
return nil, fmt.Errorf("escape meta: %s", err)
}
mask := byte(0x80)
switch ch {
case '^':
// The same as "\^..." except we apply a mask.
p.Next()
return unvisEscapeCtrl(p, mask)
case '-':
p.Next()
ch, err := p.Peek()
if err != nil {
return nil, fmt.Errorf("escape meta1: %s", err)
}
if ch > unicode.MaxLatin1 {
return nil, fmt.Errorf("escape meta1: code %q outside latin-1 encoding", ch)
}
// Add mask to character.
p.Next()
return []byte{mask | byte(ch)}, nil
}
return nil, fmt.Errorf("escape meta: unknown escape char: %s", err)
}
func unvisEscapeSequence(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
return nil, fmt.Errorf("escape sequence: %s", err)
}
switch ch {
case '\\':
p.Next()
return []byte("\\"), nil
case '0', '1', '2', '3', '4', '5', '6', '7':
return unvisEscapeDigits(p, 8, false)
case 'x':
p.Next()
return unvisEscapeDigits(p, 16, true)
case '^':
p.Next()
return unvisEscapeCtrl(p, 0x00)
case 'M':
p.Next()
return unvisEscapeMeta(p)
default:
return unvisEscapeCStyle(p)
}
}
func unvisRune(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
return nil, fmt.Errorf("rune: %s", err)
}
switch ch {
case '\\':
p.Next()
return unvisEscapeSequence(p)
case '%':
// % HEX HEX only applies to HTTPStyle encodings.
if p.flag&VisHTTPStyle == VisHTTPStyle {
p.Next()
return unvisEscapeDigits(p, 16, true)
}
fallthrough
default:
return unvisPlainRune(p)
}
}
func unvis(p *unvisParser) (string, error) {
var output []byte
for !p.End() {
ch, err := unvisRune(p)
if err != nil {
return "", fmt.Errorf("input: %s", err)
}
output = append(output, ch...)
}
return string(output), nil
}
// Unvis takes a string formatted with the given Vis flags (though only the
// VisHTTPStyle flag is checked) and output the un-encoded version of the
// encoded string. An error is returned if any escape sequences in the input
// string were invalid.
func Unvis(input string, flag VisFlag) (string, error) {
// TODO: Check all of the VisFlag bits.
p := newParser(input, flag)
output, err := unvis(p)
if err != nil {
return "", fmt.Errorf("unvis: %s", err)
}
if !p.End() {
return "", fmt.Errorf("unvis: trailing characters at end of input")
}
return output, nil
}

177
vendor/github.com/vbatts/go-mtree/pkg/govis/vis.go generated vendored Normal file
View File

@@ -0,0 +1,177 @@
/*
* govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package govis
import (
"fmt"
"unicode"
)
func isunsafe(ch rune) bool {
return ch == '\b' || ch == '\007' || ch == '\r'
}
func isglob(ch rune) bool {
return ch == '*' || ch == '?' || ch == '[' || ch == '#'
}
// ishttp is defined by RFC 1808.
func ishttp(ch rune) bool {
// RFC1808 does not really consider characters outside of ASCII, so just to
// be safe always treat characters outside the ASCII character set as "not
// HTTP".
if ch > unicode.MaxASCII {
return false
}
return unicode.IsDigit(ch) || unicode.IsLetter(ch) ||
// Safe characters.
ch == '$' || ch == '-' || ch == '_' || ch == '.' || ch == '+' ||
// Extra characters.
ch == '!' || ch == '*' || ch == '\'' || ch == '(' ||
ch == ')' || ch == ','
}
func isgraph(ch rune) bool {
return unicode.IsGraphic(ch) && !unicode.IsSpace(ch) && ch <= unicode.MaxASCII
}
// vis converts a single *byte* into its encoding. While Go supports the
// concept of runes (and thus native utf-8 parsing), in order to make sure that
// the bit-stream will be completely maintained through an Unvis(Vis(...))
// round-trip. The downside is that Vis() will never output unicode -- but on
// the plus side this is actually a benefit on the encoding side (it will
// always work with the simple unvis(3) implementation). It also means that we
// don't have to worry about different multi-byte encodings.
func vis(b byte, flag VisFlag) (string, error) {
// Treat the single-byte character as a rune.
ch := rune(b)
// XXX: This is quite a horrible thing to support.
if flag&VisHTTPStyle == VisHTTPStyle {
if !ishttp(ch) {
return "%" + fmt.Sprintf("%.2X", ch), nil
}
}
// Figure out if the character doesn't need to be encoded. Effectively, we
// encode most "normal" (graphical) characters as themselves unless we have
// been specifically asked not to. Note though that we *ALWAYS* encode
// everything outside ASCII.
// TODO: Switch this to much more logical code.
if ch > unicode.MaxASCII {
/* ... */
} else if flag&VisGlob == VisGlob && isglob(ch) {
/* ... */
} else if isgraph(ch) ||
(flag&VisSpace != VisSpace && ch == ' ') ||
(flag&VisTab != VisTab && ch == '\t') ||
(flag&VisNewline != VisNewline && ch == '\n') ||
(flag&VisSafe != 0 && isunsafe(ch)) {
encoded := string(ch)
if ch == '\\' && flag&VisNoSlash == 0 {
encoded += "\\"
}
return encoded, nil
}
// Try to use C-style escapes first.
if flag&VisCStyle == VisCStyle {
switch ch {
case ' ':
return "\\s", nil
case '\n':
return "\\n", nil
case '\r':
return "\\r", nil
case '\b':
return "\\b", nil
case '\a':
return "\\a", nil
case '\v':
return "\\v", nil
case '\t':
return "\\t", nil
case '\f':
return "\\f", nil
case '\x00':
// Output octal just to be safe.
return "\\000", nil
}
}
// For graphical characters we generate octal output (and also if it's
// being forced by the caller's flags). Also spaces should always be
// encoded as octal.
if flag&VisOctal == VisOctal || isgraph(ch) || ch&0x7f == ' ' {
// Always output three-character octal just to be safe.
return fmt.Sprintf("\\%.3o", ch), nil
}
// Now we have to output meta or ctrl escapes. As far as I can tell, this
// is not actually defined by any standard -- so this logic is basically
// copied from the original vis(3) implementation. Hopefully nobody
// actually relies on this (octal and hex are better).
encoded := ""
if flag&VisNoSlash == 0 {
encoded += "\\"
}
// Meta characters have 0x80 set, but are otherwise identical to control
// characters.
if b&0x80 != 0 {
b &= 0x7f
encoded += "M"
}
if unicode.IsControl(rune(b)) {
encoded += "^"
if b == 0x7f {
encoded += "?"
} else {
encoded += fmt.Sprintf("%c", b+'@')
}
} else {
encoded += fmt.Sprintf("-%c", b)
}
return encoded, nil
}
// Vis encodes the provided string to a BSD-compatible encoding using BSD's
// vis() flags. However, it will correctly handle multi-byte encoding (which is
// not done properly by BSD's vis implementation).
func Vis(src string, flag VisFlag) (string, error) {
if flag&visMask != flag {
return "", fmt.Errorf("vis: flag %q contains unknown or unsupported flags", flag)
}
output := ""
for _, ch := range []byte(src) {
encodedCh, err := vis(ch, flag)
if err != nil {
return "", err
}
output += encodedCh
}
return output, nil
}

11
vendor/github.com/vbatts/go-mtree/releases.md generated vendored Normal file
View File

@@ -0,0 +1,11 @@
# How to do releases:
* Create a changeset with an update to `version.go`
- this commit will be tagged
- add another commit putting it back with '-dev' appended
* gpg sign the commit with an incremented version, like 'vX.Y.Z'
* Push the tag
* Create a "release" from the tag on github
- include the binaries from `make build.arches`
- write about notable changes, and their contributors
- PRs merged for the release

18
vendor/github.com/vbatts/go-mtree/stat_unix.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
// +build !windows
package mtree
import (
"os"
"syscall"
)
func statIsUID(stat os.FileInfo, uid int) bool {
statT := stat.Sys().(*syscall.Stat_t)
return statT.Uid == uint32(uid)
}
func statIsGID(stat os.FileInfo, gid int) bool {
statT := stat.Sys().(*syscall.Stat_t)
return statT.Gid == uint32(gid)
}

12
vendor/github.com/vbatts/go-mtree/stat_windows.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build windows
package mtree
import "os"
func statIsUID(stat os.FileInfo, uid int) bool {
return false
}
func statIsGID(stat os.FileInfo, uid int) bool {
return false
}

461
vendor/github.com/vbatts/go-mtree/tar.go generated vendored Normal file
View File

@@ -0,0 +1,461 @@
package mtree
import (
"archive/tar"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/sirupsen/logrus"
"github.com/vbatts/go-mtree/pkg/govis"
)
// Streamer creates a file hierarchy out of a tar stream
type Streamer interface {
io.ReadCloser
Hierarchy() (*DirectoryHierarchy, error)
}
var tarDefaultSetKeywords = []KeyVal{
"type=file",
"flags=none",
"mode=0664",
}
// NewTarStreamer streams a tar archive and creates a file hierarchy based off
// of the tar metadata headers
func NewTarStreamer(r io.Reader, excludes []ExcludeFunc, keywords []Keyword) Streamer {
pR, pW := io.Pipe()
ts := &tarStream{
pipeReader: pR,
pipeWriter: pW,
creator: dhCreator{DH: &DirectoryHierarchy{}},
teeReader: io.TeeReader(r, pW),
tarReader: tar.NewReader(pR),
keywords: keywords,
hardlinks: map[string][]string{},
excludes: excludes,
}
go ts.readHeaders()
return ts
}
type tarStream struct {
root *Entry
hardlinks map[string][]string
creator dhCreator
pipeReader *io.PipeReader
pipeWriter *io.PipeWriter
teeReader io.Reader
tarReader *tar.Reader
keywords []Keyword
excludes []ExcludeFunc
err error
}
func (ts *tarStream) readHeaders() {
// remove "time" keyword
notimekws := []Keyword{}
for _, kw := range ts.keywords {
if !InKeywordSlice(kw, notimekws) {
if kw == "time" {
if !InKeywordSlice("tar_time", ts.keywords) {
notimekws = append(notimekws, "tar_time")
}
} else {
notimekws = append(notimekws, kw)
}
}
}
ts.keywords = notimekws
// We have to start with the directory we're in, and anything beyond these
// items is determined at the time a tar is extracted.
ts.root = &Entry{
Name: ".",
Type: RelativeType,
Prev: &Entry{
Raw: "# .",
Type: CommentType,
},
Set: nil,
Keywords: []KeyVal{"type=dir"},
}
// insert signature and metadata comments first (user, machine, tree, date)
for _, e := range signatureEntries("<user specified tar archive>") {
e.Pos = len(ts.creator.DH.Entries)
ts.creator.DH.Entries = append(ts.creator.DH.Entries, e)
}
// insert keyword metadata next
for _, e := range keywordEntries(ts.keywords) {
e.Pos = len(ts.creator.DH.Entries)
ts.creator.DH.Entries = append(ts.creator.DH.Entries, e)
}
hdrloop:
for {
hdr, err := ts.tarReader.Next()
if err != nil {
ts.pipeReader.CloseWithError(err)
return
}
for _, ex := range ts.excludes {
if ex(hdr.Name, hdr.FileInfo()) {
continue hdrloop
}
}
// Because the content of the file may need to be read by several
// KeywordFuncs, it needs to be an io.Seeker as well. So, just reading from
// ts.tarReader is not enough.
tmpFile, err := ioutil.TempFile("", "ts.payload.")
if err != nil {
ts.pipeReader.CloseWithError(err)
return
}
// for good measure
if err := tmpFile.Chmod(0600); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
return
}
if _, err := io.Copy(tmpFile, ts.tarReader); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
return
}
// Alright, it's either file or directory
encodedName, err := govis.Vis(filepath.Base(hdr.Name), DefaultVisFlags)
if err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
return
}
e := Entry{
Name: encodedName,
Type: RelativeType,
}
// Keep track of which files are hardlinks so we can resolve them later
if hdr.Typeflag == tar.TypeLink {
keyFunc := KeywordFuncs["link"]
kvs, err := keyFunc(hdr.Name, hdr.FileInfo(), nil)
if err != nil {
logrus.Warn(err)
break // XXX is breaking an okay thing to do here?
}
linkname, err := govis.Unvis(KeyVal(kvs[0]).Value(), DefaultVisFlags)
if err != nil {
logrus.Warn(err)
break // XXX is breaking an okay thing to do here?
}
if _, ok := ts.hardlinks[linkname]; !ok {
ts.hardlinks[linkname] = []string{hdr.Name}
} else {
ts.hardlinks[linkname] = append(ts.hardlinks[linkname], hdr.Name)
}
}
// now collect keywords on the file
for _, keyword := range ts.keywords {
if keyFunc, ok := KeywordFuncs[keyword.Prefix()]; ok {
// We can't extract directories on to disk, so "size" keyword
// is irrelevant for now
if hdr.FileInfo().IsDir() && keyword == "size" {
continue
}
kvs, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile)
if err != nil {
ts.setErr(err)
}
// for good measure, check that we actually get a value for a keyword
if len(kvs) > 0 && kvs[0] != "" {
e.Keywords = append(e.Keywords, kvs[0])
}
// don't forget to reset the reader
if _, err := tmpFile.Seek(0, 0); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
return
}
}
}
// collect meta-set keywords for a directory so that we can build the
// actual sets in `flatten`
if hdr.FileInfo().IsDir() {
s := Entry{
Name: "meta-set",
Type: SpecialType,
}
for _, setKW := range SetKeywords {
if keyFunc, ok := KeywordFuncs[setKW.Prefix()]; ok {
kvs, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile)
if err != nil {
ts.setErr(err)
}
for _, kv := range kvs {
if kv != "" {
s.Keywords = append(s.Keywords, kv)
}
}
if _, err := tmpFile.Seek(0, 0); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
}
}
}
e.Set = &s
}
err = populateTree(ts.root, &e, hdr)
if err != nil {
ts.setErr(err)
}
tmpFile.Close()
os.Remove(tmpFile.Name())
}
}
// populateTree creates a pseudo file tree hierarchy using an Entry's Parent and
// Children fields. When examining the Entry e to insert in the tree, we
// determine if the path to that Entry exists yet. If it does, insert it in the
// appropriate position in the tree. If not, create a path up until the Entry's
// directory that it is contained in. Then, insert the Entry.
// root: the "." Entry
// e: the Entry we are looking to insert
// hdr: the tar header struct associated with e
func populateTree(root, e *Entry, hdr *tar.Header) error {
if root == nil || e == nil {
return fmt.Errorf("cannot populate or insert nil Entry's")
} else if root.Prev == nil {
return fmt.Errorf("root needs to be an Entry associated with a directory")
}
isDir := hdr.FileInfo().IsDir()
wd := filepath.Clean(hdr.Name)
if !isDir {
// directory up until the actual file
wd = filepath.Dir(wd)
if wd == "." {
root.Children = append([]*Entry{e}, root.Children...)
e.Parent = root
return nil
}
}
dirNames := strings.Split(wd, "/")
parent := root
for _, name := range dirNames[:] {
encoded, err := govis.Vis(name, DefaultVisFlags)
if err != nil {
return err
}
if node := parent.Descend(encoded); node == nil {
// Entry for directory doesn't exist in tree relative to root.
// We don't know if this directory is an actual tar header (because a
// user could have just specified a path to a deep file), so we must
// specify this placeholder directory as a "type=dir", and Set=nil.
newEntry := Entry{
Name: encoded,
Type: RelativeType,
Parent: parent,
Keywords: []KeyVal{"type=dir"}, // temp data
Set: nil, // temp data
}
pathname, err := newEntry.Path()
if err != nil {
return err
}
newEntry.Prev = &Entry{
Type: CommentType,
Raw: "# " + pathname,
}
parent.Children = append(parent.Children, &newEntry)
parent = &newEntry
} else {
// Entry for directory exists in tree, just keep going
parent = node
}
}
if !isDir {
parent.Children = append([]*Entry{e}, parent.Children...)
e.Parent = parent
} else {
// fill in the actual data from e
parent.Keywords = e.Keywords
parent.Set = e.Set
}
return nil
}
// After constructing a pseudo file hierarchy tree, we want to "flatten" this
// tree by putting the Entries into a slice with appropriate positioning.
// root: the "head" of the sub-tree to flatten
// creator: a dhCreator that helps with the '/set' keyword
// keywords: keywords specified by the user that should be evaluated
func flatten(root *Entry, creator *dhCreator, keywords []Keyword) {
if root == nil || creator == nil {
return
}
if root.Prev != nil {
// root.Prev != nil implies root is a directory
creator.DH.Entries = append(creator.DH.Entries,
Entry{
Type: BlankType,
Pos: len(creator.DH.Entries),
})
root.Prev.Pos = len(creator.DH.Entries)
creator.DH.Entries = append(creator.DH.Entries, *root.Prev)
if root.Set != nil {
// Check if we need a new set
consolidatedKeys := keyvalSelector(append(tarDefaultSetKeywords, root.Set.Keywords...), keywords)
if creator.curSet == nil {
creator.curSet = &Entry{
Type: SpecialType,
Name: "/set",
Keywords: consolidatedKeys,
Pos: len(creator.DH.Entries),
}
creator.DH.Entries = append(creator.DH.Entries, *creator.curSet)
} else {
needNewSet := false
for _, k := range root.Set.Keywords {
if !inKeyValSlice(k, creator.curSet.Keywords) {
needNewSet = true
break
}
}
if needNewSet {
creator.curSet = &Entry{
Name: "/set",
Type: SpecialType,
Pos: len(creator.DH.Entries),
Keywords: consolidatedKeys,
}
creator.DH.Entries = append(creator.DH.Entries, *creator.curSet)
}
}
} else if creator.curSet != nil {
// Getting into here implies that the Entry's set has not and
// was not supposed to be evaluated, thus, we need to reset curSet
creator.DH.Entries = append(creator.DH.Entries, Entry{
Name: "/unset",
Type: SpecialType,
Pos: len(creator.DH.Entries),
})
creator.curSet = nil
}
}
root.Set = creator.curSet
if creator.curSet != nil {
root.Keywords = keyValDifference(root.Keywords, creator.curSet.Keywords)
}
root.Pos = len(creator.DH.Entries)
creator.DH.Entries = append(creator.DH.Entries, *root)
for _, c := range root.Children {
flatten(c, creator, keywords)
}
if root.Prev != nil {
// Show a comment when stepping out
root.Prev.Pos = len(creator.DH.Entries)
creator.DH.Entries = append(creator.DH.Entries, *root.Prev)
dotEntry := Entry{
Type: DotDotType,
Name: "..",
Pos: len(creator.DH.Entries),
}
creator.DH.Entries = append(creator.DH.Entries, dotEntry)
}
return
}
// resolveHardlinks goes through an Entry tree, and finds the Entry's associated
// with hardlinks and fills them in with the actual data from the base file.
func resolveHardlinks(root *Entry, hardlinks map[string][]string, countlinks bool) {
originals := make(map[string]*Entry)
for base, links := range hardlinks {
var basefile *Entry
if seen, ok := originals[base]; !ok {
basefile = root.Find(base)
if basefile == nil {
logrus.Printf("%s does not exist in this tree\n", base)
continue
}
originals[base] = basefile
} else {
basefile = seen
}
for _, link := range links {
linkfile := root.Find(link)
if linkfile == nil {
logrus.Printf("%s does not exist in this tree\n", link)
continue
}
linkfile.Keywords = basefile.Keywords
if countlinks {
linkfile.Keywords = append(linkfile.Keywords, KeyVal(fmt.Sprintf("nlink=%d", len(links)+1)))
}
}
if countlinks {
basefile.Keywords = append(basefile.Keywords, KeyVal(fmt.Sprintf("nlink=%d", len(links)+1)))
}
}
}
// filter takes in a pointer to an Entry, and returns a slice of Entry's that
// satisfy the predicate p
func filter(root *Entry, p func(*Entry) bool) []Entry {
if root != nil {
var validEntrys []Entry
if len(root.Children) > 0 || root.Prev != nil {
for _, c := range root.Children {
// filter the sub-directory
if c.Prev != nil {
validEntrys = append(validEntrys, filter(c, p)...)
}
if p(c) {
if c.Prev == nil {
validEntrys = append([]Entry{*c}, validEntrys...)
} else {
validEntrys = append(validEntrys, *c)
}
}
}
return validEntrys
}
}
return nil
}
func (ts *tarStream) setErr(err error) {
ts.err = err
}
func (ts *tarStream) Read(p []byte) (n int, err error) {
return ts.teeReader.Read(p)
}
func (ts *tarStream) Close() error {
return ts.pipeReader.Close()
}
// Hierarchy returns the DirectoryHierarchy of the archive. It flattens the
// Entry tree before returning the DirectoryHierarchy
func (ts *tarStream) Hierarchy() (*DirectoryHierarchy, error) {
if ts.err != nil && ts.err != io.EOF {
return nil, ts.err
}
if ts.root == nil {
return nil, fmt.Errorf("root Entry not found, nothing to flatten")
}
resolveHardlinks(ts.root, ts.hardlinks, InKeywordSlice(Keyword("nlink"), ts.keywords))
flatten(ts.root, &ts.creator, ts.keywords)
return ts.creator.DH, nil
}

154
vendor/github.com/vbatts/go-mtree/update.go generated vendored Normal file
View File

@@ -0,0 +1,154 @@
package mtree
import (
"container/heap"
"os"
"sort"
"github.com/sirupsen/logrus"
)
// DefaultUpdateKeywords is the default set of keywords that can take updates to the files on disk
var DefaultUpdateKeywords = []Keyword{
"uid",
"gid",
"mode",
"xattr",
"link",
"time",
}
// Update attempts to set the attributes of root directory path, given the values of `keywords` in dh DirectoryHierarchy.
func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval) ([]InodeDelta, error) {
creator := dhCreator{DH: dh}
curDir, err := os.Getwd()
if err == nil {
defer os.Chdir(curDir)
}
if err := os.Chdir(root); err != nil {
return nil, err
}
sort.Sort(byPos(creator.DH.Entries))
// This is for deferring the update of mtimes of directories, to unwind them
// in a most specific path first
h := &pathUpdateHeap{}
heap.Init(h)
results := []InodeDelta{}
for i, e := range creator.DH.Entries {
switch e.Type {
case SpecialType:
if e.Name == "/set" {
creator.curSet = &creator.DH.Entries[i]
} else if e.Name == "/unset" {
creator.curSet = nil
}
logrus.Debugf("%#v", e)
continue
case RelativeType, FullType:
e.Set = creator.curSet
pathname, err := e.Path()
if err != nil {
return nil, err
}
// filter the keywords to update on the file, from the keywords available for this entry:
var kvToUpdate []KeyVal
kvToUpdate = keyvalSelector(e.AllKeys(), keywords)
logrus.Debugf("kvToUpdate(%q): %#v", pathname, kvToUpdate)
for _, kv := range kvToUpdate {
if !InKeywordSlice(kv.Keyword().Prefix(), keywordPrefixes(keywords)) {
continue
}
logrus.Debugf("finding function for %q (%q)", kv.Keyword(), kv.Keyword().Prefix())
ukFunc, ok := UpdateKeywordFuncs[kv.Keyword().Prefix()]
if !ok {
logrus.Debugf("no UpdateKeywordFunc for %s; skipping", kv.Keyword())
continue
}
// TODO check for the type=dir of the entry as well
if kv.Keyword().Prefix() == "time" && e.IsDir() {
heap.Push(h, pathUpdate{
Path: pathname,
E: e,
KV: kv,
Func: ukFunc,
})
continue
}
if _, err := ukFunc(pathname, kv); err != nil {
results = append(results, InodeDelta{
diff: ErrorDifference,
path: pathname,
old: e,
keys: []KeyDelta{
{
diff: ErrorDifference,
name: kv.Keyword(),
err: err,
},
}})
}
// XXX really would be great to have a Check() or Compare() right here,
// to compare each entry as it is encountered, rather than just running
// Check() on this path after the whole update is finished.
}
}
}
for h.Len() > 0 {
pu := heap.Pop(h).(pathUpdate)
if _, err := pu.Func(pu.Path, pu.KV); err != nil {
results = append(results, InodeDelta{
diff: ErrorDifference,
path: pu.Path,
old: pu.E,
keys: []KeyDelta{
{
diff: ErrorDifference,
name: pu.KV.Keyword(),
err: err,
},
}})
}
}
return results, nil
}
type pathUpdateHeap []pathUpdate
func (h pathUpdateHeap) Len() int { return len(h) }
func (h pathUpdateHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
// This may end up looking backwards, but for container/heap, Less evaluates
// the negative priority. So when popping members of the array, it will be
// sorted by least. For this use-case, we want the most-qualified-name popped
// first (the longest path name), such that "." is the last entry popped.
func (h pathUpdateHeap) Less(i, j int) bool {
return len(h[i].Path) > len(h[j].Path)
}
func (h *pathUpdateHeap) Push(x interface{}) {
*h = append(*h, x.(pathUpdate))
}
func (h *pathUpdateHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
type pathUpdate struct {
Path string
E Entry
KV KeyVal
Func UpdateKeywordFunc
}

201
vendor/github.com/vbatts/go-mtree/updatefuncs.go generated vendored Normal file
View File

@@ -0,0 +1,201 @@
package mtree
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/vbatts/go-mtree/pkg/govis"
)
// UpdateKeywordFunc is the signature for a function that will restore a file's
// attributes. Where path is relative path to the file, and value to be
// restored to.
type UpdateKeywordFunc func(path string, kv KeyVal) (os.FileInfo, error)
// UpdateKeywordFuncs is the registered list of functions to update file attributes.
// Keyed by the keyword as it would show up in the manifest
var UpdateKeywordFuncs = map[Keyword]UpdateKeywordFunc{
"mode": modeUpdateKeywordFunc,
"time": timeUpdateKeywordFunc,
"tar_time": tartimeUpdateKeywordFunc,
"uid": uidUpdateKeywordFunc,
"gid": gidUpdateKeywordFunc,
"xattr": xattrUpdateKeywordFunc,
"link": linkUpdateKeywordFunc,
}
func uidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
uid, err := strconv.Atoi(kv.Value())
if err != nil {
return nil, err
}
stat, err := os.Lstat(path)
if err != nil {
return nil, err
}
if statIsUID(stat, uid) {
return stat, nil
}
if err := os.Lchown(path, uid, -1); err != nil {
return nil, err
}
return os.Lstat(path)
}
func gidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
gid, err := strconv.Atoi(kv.Value())
if err != nil {
return nil, err
}
stat, err := os.Lstat(path)
if err != nil {
return nil, err
}
if statIsGID(stat, gid) {
return stat, nil
}
if err := os.Lchown(path, -1, gid); err != nil {
return nil, err
}
return os.Lstat(path)
}
func modeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
info, err := os.Lstat(path)
if err != nil {
return nil, err
}
// don't set mode on symlinks, as it passes through to the backing file
if info.Mode()&os.ModeSymlink != 0 {
return info, nil
}
vmode, err := strconv.ParseInt(kv.Value(), 8, 32)
if err != nil {
return nil, err
}
stat, err := os.Lstat(path)
if err != nil {
return nil, err
}
if stat.Mode() == os.FileMode(vmode) {
return stat, nil
}
logrus.Debugf("path: %q, kv.Value(): %q, vmode: %o", path, kv.Value(), vmode)
if err := os.Chmod(path, os.FileMode(vmode)); err != nil {
return nil, err
}
return os.Lstat(path)
}
// since tar_time will only be second level precision, then when restoring the
// filepath from a tar_time, then compare the seconds first and only Chtimes if
// the seconds value is different.
func tartimeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
info, err := os.Lstat(path)
if err != nil {
return nil, err
}
v := strings.SplitN(kv.Value(), ".", 2)
if len(v) != 2 {
return nil, fmt.Errorf("expected a number like 1469104727.000000000")
}
sec, err := strconv.ParseInt(v[0], 10, 64)
if err != nil {
return nil, fmt.Errorf("expected seconds, but got %q", v[0])
}
// if the seconds are the same, don't do anything, because the file might
// have nanosecond value, and if using tar_time it would zero it out.
if info.ModTime().Unix() == sec {
return info, nil
}
vtime := time.Unix(sec, 0)
// if times are same then don't modify anything
// comparing Unix, since it does not include Nano seconds
if info.ModTime().Unix() == vtime.Unix() {
return info, nil
}
// symlinks are strange and most of the time passes through to the backing file
if info.Mode()&os.ModeSymlink != 0 {
if err := lchtimes(path, vtime, vtime); err != nil {
return nil, err
}
} else if err := os.Chtimes(path, vtime, vtime); err != nil {
return nil, err
}
return os.Lstat(path)
}
// this is nano second precision
func timeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
info, err := os.Lstat(path)
if err != nil {
return nil, err
}
v := strings.SplitN(kv.Value(), ".", 2)
if len(v) != 2 {
return nil, fmt.Errorf("expected a number like 1469104727.871937272")
}
nsec, err := strconv.ParseInt(v[0]+v[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("expected nano seconds, but got %q", v[0]+v[1])
}
logrus.Debugf("arg: %q; nsec: %d", v[0]+v[1], nsec)
vtime := time.Unix(0, nsec)
// if times are same then don't modify anything
if info.ModTime().Equal(vtime) {
return info, nil
}
// symlinks are strange and most of the time passes through to the backing file
if info.Mode()&os.ModeSymlink != 0 {
if err := lchtimes(path, vtime, vtime); err != nil {
return nil, err
}
} else if err := os.Chtimes(path, vtime, vtime); err != nil {
return nil, err
}
return os.Lstat(path)
}
func linkUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
linkname, err := govis.Unvis(kv.Value(), DefaultVisFlags)
if err != nil {
return nil, err
}
got, err := os.Readlink(path)
if err != nil {
return nil, err
}
if got == linkname {
return os.Lstat(path)
}
logrus.Debugf("linkUpdateKeywordFunc: removing %q to link to %q", path, linkname)
if err := os.Remove(path); err != nil {
return nil, err
}
if err := os.Symlink(linkname, path); err != nil {
return nil, err
}
return os.Lstat(path)
}

21
vendor/github.com/vbatts/go-mtree/updatefuncs_linux.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
// +build linux
package mtree
import (
"encoding/base64"
"os"
"github.com/vbatts/go-mtree/xattr"
)
func xattrUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
buf, err := base64.StdEncoding.DecodeString(kv.Value())
if err != nil {
return nil, err
}
if err := xattr.Set(path, kv.Keyword().Suffix(), buf); err != nil {
return nil, err
}
return os.Lstat(path)
}

View File

@@ -0,0 +1,11 @@
// +build !linux
package mtree
import (
"os"
)
func xattrUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
return os.Lstat(path)
}

23
vendor/github.com/vbatts/go-mtree/version.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package mtree
import "fmt"
const (
// AppName is the name ... of this library/application
AppName = "gomtree"
)
const (
// VersionMajor is for an API incompatible changes
VersionMajor = 0
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 5
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = "-dev"
)
// Version is the specification version that the package types support.
var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev)

385
vendor/github.com/vbatts/go-mtree/walk.go generated vendored Normal file
View File

@@ -0,0 +1,385 @@
package mtree
import (
"fmt"
"io"
"os"
"os/user"
"path/filepath"
"sort"
"strings"
"time"
"github.com/vbatts/go-mtree/pkg/govis"
)
// ExcludeFunc is the type of function called on each path walked to determine
// whether to be excluded from the assembled DirectoryHierarchy. If the func
// returns true, then the path is not included in the spec.
type ExcludeFunc func(path string, info os.FileInfo) bool
// ExcludeNonDirectories is an ExcludeFunc for excluding all paths that are not directories
var ExcludeNonDirectories = func(path string, info os.FileInfo) bool {
return !info.IsDir()
}
var defaultSetKeyVals = []KeyVal{"type=file", "nlink=1", "flags=none", "mode=0664"}
// Walk from root directory and assemble the DirectoryHierarchy
// * `excludes` provided are used to skip paths
// * `keywords` are the set to collect from the walked paths. The recommended default list is DefaultKeywords.
// * `fsEval` is the interface to use in evaluating files. If `nil`, then DefaultFsEval is used.
func Walk(root string, excludes []ExcludeFunc, keywords []Keyword, fsEval FsEval) (*DirectoryHierarchy, error) {
if fsEval == nil {
fsEval = DefaultFsEval{}
}
creator := dhCreator{DH: &DirectoryHierarchy{}, fs: fsEval}
// insert signature and metadata comments first (user, machine, tree, date)
for _, e := range signatureEntries(root) {
e.Pos = len(creator.DH.Entries)
creator.DH.Entries = append(creator.DH.Entries, e)
}
// insert keyword metadata next
for _, e := range keywordEntries(keywords) {
e.Pos = len(creator.DH.Entries)
creator.DH.Entries = append(creator.DH.Entries, e)
}
// walk the directory and add entries
err := startWalk(&creator, root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
for _, ex := range excludes {
if ex(path, info) {
return nil
}
}
entryPathName := filepath.Base(path)
if info.IsDir() {
creator.DH.Entries = append(creator.DH.Entries, Entry{
Type: BlankType,
Pos: len(creator.DH.Entries),
})
// Insert a comment of the full path of the directory's name
if creator.curDir != nil {
dirname, err := creator.curDir.Path()
if err != nil {
return err
}
creator.DH.Entries = append(creator.DH.Entries, Entry{
Pos: len(creator.DH.Entries),
Raw: "# " + filepath.Join(dirname, entryPathName),
Type: CommentType,
})
} else {
entryPathName = "."
creator.DH.Entries = append(creator.DH.Entries, Entry{
Pos: len(creator.DH.Entries),
Raw: "# .",
Type: CommentType,
})
}
// set the initial /set keywords
if creator.curSet == nil {
e := Entry{
Name: "/set",
Type: SpecialType,
Pos: len(creator.DH.Entries),
Keywords: keyvalSelector(defaultSetKeyVals, keywords),
}
for _, keyword := range SetKeywords {
err := func() error {
var r io.Reader
if info.Mode().IsRegular() {
fh, err := creator.fs.Open(path)
if err != nil {
return err
}
defer fh.Close()
r = fh
}
keyFunc, ok := KeywordFuncs[keyword.Prefix()]
if !ok {
return fmt.Errorf("Unknown keyword %q for file %q", keyword.Prefix(), path)
}
kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
if err != nil {
return err
}
for _, kv := range kvs {
if kv != "" {
e.Keywords = append(e.Keywords, kv)
}
}
return nil
}()
if err != nil {
return err
}
}
creator.curSet = &e
creator.DH.Entries = append(creator.DH.Entries, e)
} else if creator.curSet != nil {
// check the attributes of the /set keywords and re-set if changed
klist := []KeyVal{}
for _, keyword := range SetKeywords {
err := func() error {
var r io.Reader
if info.Mode().IsRegular() {
fh, err := creator.fs.Open(path)
if err != nil {
return err
}
defer fh.Close()
r = fh
}
keyFunc, ok := KeywordFuncs[keyword.Prefix()]
if !ok {
return fmt.Errorf("Unknown keyword %q for file %q", keyword.Prefix(), path)
}
kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
if err != nil {
return err
}
for _, kv := range kvs {
if kv != "" {
klist = append(klist, kv)
}
}
return nil
}()
if err != nil {
return err
}
}
needNewSet := false
for _, k := range klist {
if !inKeyValSlice(k, creator.curSet.Keywords) {
needNewSet = true
}
}
if needNewSet {
e := Entry{
Name: "/set",
Type: SpecialType,
Pos: len(creator.DH.Entries),
Keywords: keyvalSelector(append(defaultSetKeyVals, klist...), keywords),
}
creator.curSet = &e
creator.DH.Entries = append(creator.DH.Entries, e)
}
}
}
encodedEntryName, err := govis.Vis(entryPathName, DefaultVisFlags)
if err != nil {
return err
}
e := Entry{
Name: encodedEntryName,
Pos: len(creator.DH.Entries),
Type: RelativeType,
Set: creator.curSet,
Parent: creator.curDir,
}
for _, keyword := range keywords {
err := func() error {
var r io.Reader
if info.Mode().IsRegular() {
fh, err := creator.fs.Open(path)
if err != nil {
return err
}
defer fh.Close()
r = fh
}
keyFunc, ok := KeywordFuncs[keyword.Prefix()]
if !ok {
return fmt.Errorf("Unknown keyword %q for file %q", keyword.Prefix(), path)
}
kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
if err != nil {
return err
}
for _, kv := range kvs {
if kv != "" && !inKeyValSlice(kv, creator.curSet.Keywords) {
e.Keywords = append(e.Keywords, kv)
}
}
return nil
}()
if err != nil {
return err
}
}
if info.IsDir() {
if creator.curDir != nil {
creator.curDir.Next = &e
}
e.Prev = creator.curDir
creator.curDir = &e
} else {
if creator.curEnt != nil {
creator.curEnt.Next = &e
}
e.Prev = creator.curEnt
creator.curEnt = &e
}
creator.DH.Entries = append(creator.DH.Entries, e)
return nil
})
return creator.DH, err
}
// startWalk walks the file tree rooted at root, calling walkFn for each file or
// directory in the tree, including root. All errors that arise visiting files
// and directories are filtered by walkFn. The files are walked in lexical
// order, which makes the output deterministic but means that for very
// large directories Walk can be inefficient.
// Walk does not follow symbolic links.
func startWalk(c *dhCreator, root string, walkFn filepath.WalkFunc) error {
info, err := c.fs.Lstat(root)
if err != nil {
return walkFn(root, nil, err)
}
return walk(c, root, info, walkFn)
}
// walk recursively descends path, calling w.
func walk(c *dhCreator, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
err := walkFn(path, info, nil)
if err != nil {
if info.IsDir() && err == filepath.SkipDir {
return nil
}
return err
}
if !info.IsDir() {
return nil
}
names, err := readOrderedDirNames(c, path)
if err != nil {
return walkFn(path, info, err)
}
for _, name := range names {
filename := filepath.Join(path, name)
fileInfo, err := c.fs.Lstat(filename)
if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
return err
}
} else {
err = walk(c, filename, fileInfo, walkFn)
if err != nil {
if !fileInfo.IsDir() || err != filepath.SkipDir {
return err
}
}
}
}
c.DH.Entries = append(c.DH.Entries, Entry{
Name: "..",
Type: DotDotType,
Pos: len(c.DH.Entries),
})
if c.curDir != nil {
c.curDir = c.curDir.Parent
}
return nil
}
// readOrderedDirNames reads the directory and returns a sorted list of all
// entries with non-directories first, followed by directories.
func readOrderedDirNames(c *dhCreator, dirname string) ([]string, error) {
infos, err := c.fs.Readdir(dirname)
if err != nil {
return nil, err
}
names := []string{}
dirnames := []string{}
for _, info := range infos {
if info.IsDir() {
dirnames = append(dirnames, info.Name())
continue
}
names = append(names, info.Name())
}
sort.Strings(names)
sort.Strings(dirnames)
return append(names, dirnames...), nil
}
// signatureEntries is a simple helper function that returns a slice of Entry's
// that describe the metadata signature about the host. Items like date, user,
// machine, and tree (which is specified by argument `root`), are considered.
// These Entry's construct comments in the mtree specification, so if there is
// an error trying to obtain a particular metadata, we simply don't construct
// the Entry.
func signatureEntries(root string) []Entry {
var sigEntries []Entry
user, err := user.Current()
if err == nil {
userEntry := Entry{
Type: CommentType,
Raw: fmt.Sprintf("#%16s%s", "user: ", user.Username),
}
sigEntries = append(sigEntries, userEntry)
}
hostname, err := os.Hostname()
if err == nil {
hostEntry := Entry{
Type: CommentType,
Raw: fmt.Sprintf("#%16s%s", "machine: ", hostname),
}
sigEntries = append(sigEntries, hostEntry)
}
if tree := filepath.Clean(root); tree == "." || tree == ".." {
root, err := os.Getwd()
if err == nil {
// use parent directory of current directory
if tree == ".." {
root = filepath.Dir(root)
}
treeEntry := Entry{
Type: CommentType,
Raw: fmt.Sprintf("#%16s%s", "tree: ", filepath.Clean(root)),
}
sigEntries = append(sigEntries, treeEntry)
}
} else {
treeEntry := Entry{
Type: CommentType,
Raw: fmt.Sprintf("#%16s%s", "tree: ", filepath.Clean(root)),
}
sigEntries = append(sigEntries, treeEntry)
}
dateEntry := Entry{
Type: CommentType,
Raw: fmt.Sprintf("#%16s%s", "date: ", time.Now().Format("Mon Jan 2 15:04:05 2006")),
}
sigEntries = append(sigEntries, dateEntry)
return sigEntries
}
// keywordEntries returns a slice of entries including a comment of the
// keywords requested when generating this manifest.
func keywordEntries(keywords []Keyword) []Entry {
// Convert all of the keywords to zero-value keyvals.
return []Entry{
{
Type: CommentType,
Raw: fmt.Sprintf("#%16s%s", "keywords: ", strings.Join(FromKeywords(keywords), ",")),
},
}
}

42
vendor/github.com/vbatts/go-mtree/xattr/xattr.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
// +build linux
package xattr
import (
"strings"
"syscall"
)
// Get returns the extended attributes (xattr) on file `path`, for the given `name`.
func Get(path, name string) ([]byte, error) {
dest := make([]byte, 1024)
i, err := syscall.Getxattr(path, name, dest)
if err != nil {
return nil, err
}
return dest[:i], nil
}
// Set sets the extended attributes (xattr) on file `path`, for the given `name` and `value`
func Set(path, name string, value []byte) error {
return syscall.Setxattr(path, name, value, 0)
}
// List returns a list of all the extended attributes (xattr) for file `path`
func List(path string) ([]string, error) {
dest := make([]byte, 1024)
i, err := syscall.Listxattr(path, dest)
if err != nil {
return nil, err
}
// If the returned list is empty, return nil instead of []string{""}
str := string(dest[:i])
if str == "" {
return nil, nil
}
return strings.Split(strings.TrimRight(str, nilByte), nilByte), nil
}
const nilByte = "\x00"

View File

@@ -0,0 +1,21 @@
// +build !linux
package xattr
// Get would return the extended attributes, but this unsupported feature
// returns nil, nil
func Get(path, name string) ([]byte, error) {
return nil, nil
}
// Set would set the extended attributes, but this unsupported feature returns
// nil
func Set(path, name string, value []byte) error {
return nil
}
// List would return the keys of extended attributes, but this unsupported
// feature returns nil, nil
func List(path string) ([]string, error) {
return nil, nil
}