Compare commits
176 Commits
Author | SHA1 | Date |
---|---|---|
Vincent Batts | 1adae81a54 | |
Vincent Batts | 9de8611e15 | |
Vincent Batts | d02dbc5725 | |
Vincent Batts | ae608839f1 | |
Vincent Batts | e8d42c3634 | |
Vincent Batts | 8f06855856 | |
Vincent Batts | 66fcc4c2f9 | |
Vincent Batts | 8653fc588a | |
Vincent Batts | 0e749301e9 | |
Vincent Batts | 4d95ad8fcf | |
Vincent Batts | 3d350a825e | |
Vincent Batts | ced0ce3470 | |
Ubuntu | f2de0fb1a3 | |
Vincent Batts | e34c4f37a7 | |
Vincent Batts | 2e000987c5 | |
Vincent Batts | d6df813dbc | |
Tycho Andersen | f0ebb0ab5b | |
Vincent Batts | 2b899d3655 | |
Vincent Batts | ba9a13f463 | |
Vincent Batts | f849a9299a | |
Vincent Batts | 6912c50eda | |
Vincent Batts | 7ce6c9c696 | |
Vincent Batts | 8b6de6073c | |
Vincent Batts | 4885a690ee | |
Vincent Batts | 656cd41822 | |
Vincent Batts | 9be05594fe | |
Vincent Batts | 94a6c46bde | |
Vincent Batts | 134ba041d7 | |
Tycho Andersen | d8d43cd807 | |
Vincent Batts | 53e54ea2f7 | |
Vincent Batts | 40f4ce8108 | |
Vincent Batts | abf4d54fb2 | |
Vincent Batts | 367008df78 | |
Vincent Batts | 4ae4c48dc0 | |
Vincent Batts | acd3fa49ea | |
Aleksa Sarai | 4766cebac0 | |
Vincent Batts | 37d776ac40 | |
Vincent Batts | 03270d3d9e | |
Vincent Batts | 2f374a383e | |
Vincent Batts | f34a2e0d2b | |
Vincent Batts | dce9629991 | |
Vincent Batts | 68a6d43233 | |
Vincent Batts | 1bcf4de08f | |
Aleksa Sarai | be3abf053a | |
Aleksa Sarai | ffb4a05860 | |
Vincent Batts | 16da0f86ee | |
Vincent Batts | 144242ef1e | |
Vincent Batts | 005af4d18f | |
W. Trevor King | 72ac04e7ca | |
Vincent Batts | 03983e2cdc | |
Vincent Batts | de69569d25 | |
Vincent Batts | 2352d84626 | |
Vincent Batts | 3a0105dc85 | |
Vincent Batts | a30ab86d71 | |
Tobias Klauser | 3fe21921b5 | |
Tobias Klauser | 7742183cd4 | |
Vincent Batts | 8bcd48e401 | |
Vincent Batts | 020cebaa52 | |
Vincent Batts | 7023b74563 | |
Vincent Batts | 6fec2c6177 | |
Aleksa Sarai | 8e5c54f51d | |
Vincent Batts | ba49f2f918 | |
Vincent Batts | 85fac2fc14 | |
Vincent Batts | 2ede6ecf20 | |
Vincent Batts | f271d65127 | |
Vincent Batts | 73be830998 | |
Vincent Batts | a393e171c4 | |
Vincent Batts | 04230dccdc | |
Vincent Batts | 593cfb68b8 | |
Vincent Batts | 9408f0f4c0 | |
Vincent Batts | 0b5038d0bc | |
Vincent Batts | ed464af779 | |
Vincent Batts | fb4ec19981 | |
Vincent Batts | 50d22c5135 | |
Vincent Batts | c5b7548e35 | |
Vincent Batts | 14721e6869 | |
Vincent Batts | 64ecdb40ec | |
Vincent Batts | 9533b02a8e | |
Vincent Batts | bc45166bfc | |
Vincent Batts | 0ee52f7faf | |
Vincent Batts | abdee6fe11 | |
Vincent Batts | bbedbb3eaa | |
Vincent Batts | f09463164c | |
Vincent Batts | 5db2376250 | |
Vincent Batts | 68651d77d6 | |
Vincent Batts | 469590a575 | |
Matthew Garrett | cb1fb5dded | |
Vincent Batts | 93776cd69e | |
Vincent Batts | fc5450ed71 | |
Vincent Batts | 0b9f227e4e | |
Vincent Batts | e359fa7d2d | |
Vincent Batts | 6e336a525d | |
Vincent Batts | eca64ff621 | |
Vincent Batts | f165f4b7cc | |
Aleksa Sarai | f6c295f2e9 | |
Vincent Batts | 711a89aa4c | |
Aleksa Sarai | 4ad871ca46 | |
Vincent Batts | 7b6f89f33d | |
Aleksa Sarai | c5ec1c9f3a | |
Aleksa Sarai | 91d7ec8c89 | |
Aleksa Sarai | c889416068 | |
Aleksa Sarai | c9551d5820 | |
Aleksa Sarai | cbe20d4292 | |
Aleksa Sarai | 4045484afb | |
Aleksa Sarai | c2a9f1a56d | |
Aleksa Sarai | de223ffc92 | |
Aleksa Sarai | 7b16f3a307 | |
Aleksa Sarai | 35708696fe | |
Aleksa Sarai | 3b18d38388 | |
Aleksa Sarai | 44391840b6 | |
Aleksa Sarai | cd1de45ba5 | |
Aleksa Sarai | 1e8de82690 | |
Aleksa Sarai | 4c009fc4b2 | |
Aleksa Sarai | 85e7fd2d50 | |
Vincent Batts | 5685419c3e | |
Aleksa Sarai | f6dd726b66 | |
Vincent Batts | 0185fe9b62 | |
Vincent Batts | 67b30e3e57 | |
Vincent Batts | 1a3d369341 | |
Vincent Batts | 21a2577f01 | |
Vincent Batts | b7967864aa | |
Vincent Batts | b9e743fdf0 | |
Vincent Batts | 235566d9e0 | |
Vincent Batts | d1073b349b | |
Vincent Batts | b71ad0f21e | |
Vincent Batts | 97e571a55a | |
Vincent Batts | 2b97fe83d3 | |
Vincent Batts | a4e826f189 | |
Vincent Batts | 1f53bd1b61 | |
Lokesh Mandvekar | c9762c4d0e | |
Aleksa Sarai | 071977cef6 | |
Aleksa Sarai | 9cdd9152b3 | |
Aleksa Sarai | 3c76a35588 | |
Aleksa Sarai | f9adee80f4 | |
Vincent Batts | 94d7041e8e | |
Aleksa Sarai | ad35cae482 | |
Vincent Batts | 0dc720e861 | |
Aleksa Sarai | 08004a9a8c | |
Aleksa Sarai | e22043cb86 | |
Aleksa Sarai | 98824a87da | |
Vincent Batts | d42d5761a6 | |
Vincent Batts | 58660bbd83 | |
Vincent Batts | 556bb352d2 | |
Vincent Batts | d857d58104 | |
Vincent Batts | 26ccc7a48c | |
Vincent Batts | f15c0ad821 | |
Vincent Batts | 9fc0375857 | |
Vincent Batts | 0b88377e28 | |
Vincent Batts | 1f7a3d3784 | |
Vincent Batts | ca8faa91ae | |
Vincent Batts | 83dff044b3 | |
Vincent Batts | e2575bffa5 | |
Vincent Batts | 05a295f267 | |
Vincent Batts | 812981d95d | |
Vincent Batts | 08b1000418 | |
Vincent Batts | e42c679e89 | |
Vincent Batts | e2640e6dfa | |
Vincent Batts | efe17f56ae | |
Vincent Batts | f49f66f61e | |
Vincent Batts | 13131d516b | |
Brent Baude | 1b0ba0d70f | |
Vincent Batts | b6ed8e415d | |
Aleksa Sarai | 408b615c3c | |
Vincent Batts | a6df651d88 | |
Vincent Batts | 68a1a615b9 | |
Vincent Batts | 71b81f3ced | |
Vincent Batts | 81e5c5c8f0 | |
Vincent Batts | e76196a6d6 | |
Vincent Batts | c0a5cb25ec | |
Vincent Batts | 353436a031 | |
Vincent Batts | f51a3381de | |
Vincent Batts | 21723a3974 | |
Vincent Batts | 4eec68be4b | |
Vincent Batts | 5d26726bb1 | |
Vincent Batts | 627c6e9ddd | |
Vincent Batts | a8e4475c5e |
|
@ -1,17 +1,13 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.7.3
|
||||
- 1.6.3
|
||||
|
||||
sudo: false
|
||||
|
||||
before_install:
|
||||
kind: pipeline
|
||||
name: default
|
||||
steps:
|
||||
steps:
|
||||
- name: build
|
||||
image: golang
|
||||
commands:
|
||||
- git config --global url."https://".insteadOf git://
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- make install.tools
|
||||
- mkdir -p $GOPATH/src/github.com/vbatts && ln -sf $(pwd) $GOPATH/src/github.com/vbatts/go-mtree
|
||||
- go get ./...
|
||||
|
||||
install: true
|
||||
|
||||
script:
|
||||
- make validation
|
||||
- make validation.tags
|
||||
- make build.arches
|
|
@ -0,0 +1,28 @@
|
|||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
|
||||
- name: Build
|
||||
run: make
|
||||
|
||||
- name: Test
|
||||
run: make validation
|
||||
|
||||
- name: Build.Arches
|
||||
run: make build.arches
|
|
@ -0,0 +1,6 @@
|
|||
*~
|
||||
.cli.test
|
||||
.lint
|
||||
.test
|
||||
.vet
|
||||
gomtree
|
|
@ -0,0 +1,66 @@
|
|||
// Available variables which can be used inside of strings.
|
||||
// ${workspaceRoot}: the root folder of the team
|
||||
// ${file}: the current opened file
|
||||
// ${fileBasename}: the current opened file's basename
|
||||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "build",
|
||||
"type": "shell",
|
||||
"command": "time go build .",
|
||||
"problemMatcher": [
|
||||
"$go"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "silent",
|
||||
"focus": true,
|
||||
"panel": "shared"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskName": "build.arches",
|
||||
"type": "shell",
|
||||
"command": "make build.arches",
|
||||
"problemMatcher": [
|
||||
"$go"
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskName": "test",
|
||||
"command": "time go test -v .",
|
||||
"type": "shell",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$go"
|
||||
],
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
70
Makefile
70
Makefile
|
@ -1,45 +1,93 @@
|
|||
|
||||
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
|
||||
GO_VER := go1.14
|
||||
|
||||
default: validation build
|
||||
default: build validation
|
||||
|
||||
.PHONY: validation
|
||||
validation: test lint vet .cli.test
|
||||
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
|
||||
NO_VENDOR_DIR := $(shell find . -type f -name '*.go' ! -path './vendor*' ! -path './.git*' ! -path './.vscode*' -exec dirname "{}" \; | sort -u)
|
||||
|
||||
.test: $(SOURCE_FILES)
|
||||
go test -v ./... && touch $@
|
||||
go test -v $(NO_VENDOR_DIR) && touch $@
|
||||
|
||||
.test.tags: $(SOURCE_FILES)
|
||||
set -e ; for tag in $(TAGS) ; do go test -tags $$tag -v $(NO_VENDOR_DIR) ; done && touch $@
|
||||
|
||||
.PHONY: lint
|
||||
lint: .lint
|
||||
|
||||
CLEAN_FILES += .lint
|
||||
|
||||
.lint: $(SOURCE_FILES)
|
||||
golint -set_exit_status ./... && touch $@
|
||||
@if [ "$(findstring $(GO_VER),$(shell go version))" != "" ] ; then \
|
||||
set -e ; for dir in $(NO_VENDOR_DIR) ; do golint -set_exit_status $$dir ; done && touch $@ \
|
||||
else \
|
||||
touch $@ ; \
|
||||
fi
|
||||
|
||||
.PHONY: vet
|
||||
vet: .vet
|
||||
vet: .vet .vet.tags
|
||||
|
||||
CLEAN_FILES += .vet .vet.tags
|
||||
|
||||
.vet: $(SOURCE_FILES)
|
||||
go vet ./... && touch $@
|
||||
go vet $(NO_VENDOR_DIR) && touch $@
|
||||
|
||||
.vet.tags: $(SOURCE_FILES)
|
||||
set -e ; for tag in $(TAGS) ; do go vet -tags $$tag -v $(NO_VENDOR_DIR) ; done && touch $@
|
||||
|
||||
.PHONY: cli.test
|
||||
cli.test: .cli.test
|
||||
|
||||
CLEAN_FILES += .cli.test .cli.test.tags
|
||||
|
||||
.cli.test: $(BUILD) $(wildcard ./test/cli/*.sh)
|
||||
@ for test in ./test/cli/*.sh ; do \
|
||||
bash $$test $(CWD) ; \
|
||||
done && touch $@
|
||||
@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 ./cmd/$(BUILD)
|
||||
go build -o $(BUILD) $(BUILDPATH)
|
||||
|
||||
install.tools:
|
||||
@go get -u github.com/fatih/color ; \
|
||||
if [ "$(findstring $(GO_VER),$(shell go version))" != "" ] ; 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) .test .vet .lint .cli.test
|
||||
rm -rf $(BUILD) $(CLEAN_FILES)
|
||||
|
||||
|
|
22
README.md
22
README.md
|
@ -1,5 +1,7 @@
|
|||
# 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.
|
||||
|
||||
|
@ -183,9 +185,23 @@ go get github.com/vbatts/go-mtree/cmd/gomtree
|
|||
or
|
||||
|
||||
```bash
|
||||
git clone git://github.com/vbatts/go-mtree.git
|
||||
cd ./go-mtree/cmd/gomtree
|
||||
go build .
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
|
|
19
check.go
19
check.go
|
@ -5,27 +5,16 @@ package mtree
|
|||
// If keywords is nil, the check all present in the DirectoryHierarchy
|
||||
//
|
||||
// This is equivalent to creating a new DirectoryHierarchy with Walk(root, nil,
|
||||
// keywords) and then doing a Compare(dh, newDh, keywords).
|
||||
func Check(root string, dh *DirectoryHierarchy, keywords []string) ([]InodeDelta, error) {
|
||||
// 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 = CollectUsedKeywords(dh)
|
||||
keywords = dh.UsedKeywords()
|
||||
}
|
||||
|
||||
newDh, err := Walk(root, nil, keywords)
|
||||
newDh, err := Walk(root, nil, keywords, fs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Handle tar_time, if necessary.
|
||||
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 []string) ([]InodeDelta, error) {
|
||||
if keywords == nil {
|
||||
keywords = CollectUsedKeywords(dh)
|
||||
}
|
||||
return Compare(dh, tarDH, keywords)
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@ import (
|
|||
// simple walk of current directory, and imediately check it.
|
||||
// may not be parallelizable.
|
||||
func TestCheck(t *testing.T) {
|
||||
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
||||
dh, err := Walk(".", nil, append(DefaultKeywords, []Keyword{"sha1", "xattr"}...), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := Check(".", dh, nil)
|
||||
res, err := Check(".", dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -43,13 +43,13 @@ func TestCheckKeywords(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk this tempdir
|
||||
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check for sanity. This ought to pass.
|
||||
res, err := Check(dir, dh, nil)
|
||||
res, err := Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -58,25 +58,25 @@ func TestCheckKeywords(t *testing.T) {
|
|||
}
|
||||
|
||||
// Touch a file, so the mtime changes.
|
||||
now := time.Now()
|
||||
if err := os.Chtimes(tmpfn, now, now); err != nil {
|
||||
newtime := time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC)
|
||||
if err := os.Chtimes(tmpfn, newtime, newtime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check again. This ought to fail.
|
||||
res, err = Check(dir, dh, nil)
|
||||
res, err = Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) != 1 {
|
||||
t.Errorf("expected to get 1 delta on changed mtimes, but did not")
|
||||
t.Fatal("expected to get 1 delta on changed mtimes, but did not")
|
||||
}
|
||||
if res[0].Type() != Modified {
|
||||
t.Errorf("expected to get modified delta on changed mtimes, but did not")
|
||||
}
|
||||
|
||||
// Check again, but only sha1 and mode. This ought to pass.
|
||||
res, err = Check(dir, dh, []string{"sha1", "mode"})
|
||||
res, err = Check(dir, dh, []Keyword{"sha1", "mode"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -86,12 +86,12 @@ func TestCheckKeywords(t *testing.T) {
|
|||
}
|
||||
|
||||
func ExampleCheck() {
|
||||
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
||||
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
// handle error ...
|
||||
}
|
||||
|
||||
res, err := Check(".", dh, nil)
|
||||
res, err := Check(".", dh, nil, nil)
|
||||
if err != nil {
|
||||
// handle error ...
|
||||
}
|
||||
|
@ -103,11 +103,11 @@ func ExampleCheck() {
|
|||
// Tests default action for evaluating a symlink, which is just to compare the
|
||||
// link itself, not to follow it
|
||||
func TestDefaultBrokenLink(t *testing.T) {
|
||||
dh, err := Walk("./testdata/dirwithbrokenlink", nil, append(DefaultKeywords, "sha1"))
|
||||
dh, err := Walk("./testdata/dirwithbrokenlink", nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res, err := Check("./testdata/dirwithbrokenlink", dh, nil)
|
||||
res, err := Check("./testdata/dirwithbrokenlink", dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ func TestTimeComparison(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := Check(dir, dh, nil)
|
||||
res, err := Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -200,16 +200,16 @@ func TestTarTime(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keywords := CollectUsedKeywords(dh)
|
||||
keywords := dh.UsedKeywords()
|
||||
|
||||
// make sure "time" keyword works
|
||||
_, err = Check(dir, dh, keywords)
|
||||
_, err = Check(dir, dh, keywords, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// make sure tar_time wins
|
||||
res, err := Check(dir, dh, append(keywords, "tar_time"))
|
||||
res, err := Check(dir, dh, append(keywords, "tar_time"), nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ func TestIgnoreComments(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := Check(dir, dh, nil)
|
||||
res, err := Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ func TestIgnoreComments(t *testing.T) {
|
|||
`
|
||||
dh, err = ParseSpec(bytes.NewBufferString(spec))
|
||||
|
||||
res, err = Check(dir, dh, nil)
|
||||
res, err = Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -306,11 +306,11 @@ func TestCheckNeedsEncoding(t *testing.T) {
|
|||
t.Error(err)
|
||||
}
|
||||
|
||||
dh, err := Walk(dir, nil, DefaultKeywords)
|
||||
dh, err := Walk(dir, nil, DefaultKeywords, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res, err := Check(dir, dh, nil)
|
||||
res, err := Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -7,19 +7,24 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/go-mtree"
|
||||
)
|
||||
|
||||
var (
|
||||
// Flags common with mtree(8)
|
||||
flCreate = flag.Bool("c", false, "create a directory hierarchy spec")
|
||||
flFile = flag.String("f", "", "directory hierarchy spec to validate")
|
||||
flPath = flag.String("p", "", "root path that the hierarchy spec is relative to")
|
||||
flAddKeywords = flag.String("K", "", "Add the specified (delimited by comma or space) keywords to the current set of keywords")
|
||||
flUseKeywords = flag.String("k", "", "Use the specified (delimited by comma or space) keywords as the current set of keywords")
|
||||
flDirectoryOnly = flag.Bool("d", false, "Ignore everything except directory type files")
|
||||
flUpdateAttributes = flag.Bool("u", false, "Modify the owner, group, permissions and xattrs of files, symbolic links and devices, to match the provided specification. This is not compatible with '-T'.")
|
||||
|
||||
// Flags unique to gomtree
|
||||
flListKeywords = flag.Bool("list-keywords", false, "List the keywords available")
|
||||
flResultFormat = flag.String("result-format", "bsd", "output the validation results using the given format (bsd, json, path)")
|
||||
flTar = flag.String("T", "", "use tar archive to create or validate a directory hierarchy spec (\"-\" indicates stdin)")
|
||||
|
@ -29,14 +34,325 @@ var (
|
|||
flVersion = flag.Bool("version", false, "display the version of this tool")
|
||||
)
|
||||
|
||||
func main() {
|
||||
// so that defers cleanly exec
|
||||
if err := app(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func app() error {
|
||||
flag.Parse()
|
||||
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
if *flVersion {
|
||||
fmt.Printf("%s :: %s\n", mtree.AppName, mtree.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
// -list-keywords
|
||||
if *flListKeywords {
|
||||
fmt.Println("Available keywords:")
|
||||
for k := range mtree.KeywordFuncs {
|
||||
fmt.Print(" ")
|
||||
fmt.Print(k)
|
||||
if mtree.Keyword(k).Default() {
|
||||
fmt.Print(" (default)")
|
||||
}
|
||||
if !mtree.Keyword(k).Bsd() {
|
||||
fmt.Print(" (not upstream)")
|
||||
}
|
||||
fmt.Print("\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --result-format
|
||||
formatFunc, ok := formats[*flResultFormat]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid output format: %s", *flResultFormat)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
tmpKeywords []mtree.Keyword
|
||||
currentKeywords []mtree.Keyword
|
||||
)
|
||||
|
||||
// -k <keywords>
|
||||
if *flUseKeywords != "" {
|
||||
tmpKeywords = splitKeywordsArg(*flUseKeywords)
|
||||
if !mtree.InKeywordSlice("type", tmpKeywords) {
|
||||
tmpKeywords = append([]mtree.Keyword{"type"}, tmpKeywords...)
|
||||
}
|
||||
} else {
|
||||
if *flTar != "" {
|
||||
tmpKeywords = mtree.DefaultTarKeywords[:]
|
||||
} else {
|
||||
tmpKeywords = mtree.DefaultKeywords[:]
|
||||
}
|
||||
}
|
||||
|
||||
// -K <keywords>
|
||||
if *flAddKeywords != "" {
|
||||
for _, kw := range splitKeywordsArg(*flAddKeywords) {
|
||||
if !mtree.InKeywordSlice(kw, tmpKeywords) {
|
||||
tmpKeywords = append(tmpKeywords, kw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -bsd-keywords
|
||||
if *flBsdKeywords {
|
||||
for _, k := range tmpKeywords {
|
||||
if mtree.Keyword(k).Bsd() {
|
||||
currentKeywords = append(currentKeywords, k)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "INFO: ignoring %q as it is not an upstream keyword\n", k)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentKeywords = tmpKeywords
|
||||
}
|
||||
|
||||
// Check mutual exclusivity of keywords.
|
||||
// TODO(cyphar): Abstract this inside keywords.go.
|
||||
if mtree.InKeywordSlice("tar_time", currentKeywords) && mtree.InKeywordSlice("time", currentKeywords) {
|
||||
return fmt.Errorf("tar_time and time are mutually exclusive keywords")
|
||||
}
|
||||
|
||||
// If we're doing a comparison, we always are comparing between a spec and
|
||||
// state DH. If specDh is nil, we are generating a new one.
|
||||
var (
|
||||
specDh *mtree.DirectoryHierarchy
|
||||
stateDh *mtree.DirectoryHierarchy
|
||||
specKeywords []mtree.Keyword
|
||||
)
|
||||
|
||||
// -f <file>
|
||||
if *flFile != "" && !*flCreate {
|
||||
// load the hierarchy, if we're not creating a new spec
|
||||
fh, err := os.Open(*flFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
specDh, err = mtree.ParseSpec(fh)
|
||||
fh.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We can't check against more fields than in the specKeywords list, so
|
||||
// currentKeywords can only have a subset of specKeywords.
|
||||
specKeywords = specDh.UsedKeywords()
|
||||
}
|
||||
|
||||
// -list-used
|
||||
if *flListUsedKeywords {
|
||||
if specDh == nil {
|
||||
return fmt.Errorf("no specification provided. please provide a validation manifest")
|
||||
}
|
||||
|
||||
if *flResultFormat == "json" {
|
||||
// if they're asking for json, give it to them
|
||||
data := map[string][]mtree.Keyword{*flFile: specKeywords}
|
||||
buf, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(buf))
|
||||
} else {
|
||||
fmt.Printf("Keywords used in [%s]:\n", *flFile)
|
||||
for _, kw := range specKeywords {
|
||||
fmt.Printf(" %s", kw)
|
||||
if _, ok := mtree.KeywordFuncs[kw]; !ok {
|
||||
fmt.Print(" (unsupported)")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if specKeywords != nil {
|
||||
// If we didn't actually change the set of keywords, we can just use specKeywords.
|
||||
if *flUseKeywords == "" && *flAddKeywords == "" {
|
||||
currentKeywords = specKeywords
|
||||
}
|
||||
|
||||
for _, keyword := range currentKeywords {
|
||||
// As always, time is a special case.
|
||||
// TODO: Fix that.
|
||||
if (keyword == "time" && mtree.InKeywordSlice("tar_time", specKeywords)) || (keyword == "tar_time" && mtree.InKeywordSlice("time", specKeywords)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -p and -T are mutually exclusive
|
||||
if *flPath != "" && *flTar != "" {
|
||||
return fmt.Errorf("options -T and -p are mutually exclusive")
|
||||
}
|
||||
|
||||
// -p <path>
|
||||
var rootPath = "."
|
||||
if *flPath != "" {
|
||||
rootPath = *flPath
|
||||
}
|
||||
|
||||
excludes := []mtree.ExcludeFunc{}
|
||||
// -d
|
||||
if *flDirectoryOnly {
|
||||
excludes = append(excludes, mtree.ExcludeNonDirectories)
|
||||
}
|
||||
|
||||
// -u
|
||||
// Failing early here. Processing is done below.
|
||||
if *flUpdateAttributes && *flTar != "" {
|
||||
return fmt.Errorf("ERROR: -u can not be used with -T")
|
||||
}
|
||||
|
||||
// -T <tar file>
|
||||
if *flTar != "" {
|
||||
var input io.Reader
|
||||
if *flTar == "-" {
|
||||
input = os.Stdin
|
||||
} else {
|
||||
fh, err := os.Open(*flTar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
input = fh
|
||||
}
|
||||
ts := mtree.NewTarStreamer(input, excludes, currentKeywords)
|
||||
|
||||
if _, err := io.Copy(ioutil.Discard, ts); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if err := ts.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
stateDh, err = ts.Hierarchy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// with a root directory
|
||||
stateDh, err = mtree.Walk(rootPath, excludes, currentKeywords, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// -u
|
||||
if *flUpdateAttributes && stateDh != nil {
|
||||
// -u
|
||||
// this comes before the next case, intentionally.
|
||||
result, err := mtree.Update(rootPath, specDh, mtree.DefaultUpdateKeywords, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result != nil && len(result) > 0 {
|
||||
fmt.Printf("%#v\n", result)
|
||||
}
|
||||
|
||||
var res []mtree.InodeDelta
|
||||
// only check the keywords that we just updated
|
||||
res, err = mtree.Check(rootPath, specDh, mtree.DefaultUpdateKeywords, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res != nil {
|
||||
out := formatFunc(res)
|
||||
if _, err := os.Stdout.Write([]byte(out)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: This should be a flag. Allowing files to be added and
|
||||
// removed and still returning "it's all good" is simply
|
||||
// unsafe IMO.
|
||||
for _, diff := range res {
|
||||
if diff.Type() == mtree.Modified {
|
||||
return fmt.Errorf("mainfest validation failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// -c
|
||||
if *flCreate {
|
||||
fh := os.Stdout
|
||||
if *flFile != "" {
|
||||
fh, err = os.Create(*flFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// output stateDh
|
||||
stateDh.WriteTo(fh)
|
||||
return nil
|
||||
}
|
||||
|
||||
// no spec manifest has been provided yet, so look for it on stdin
|
||||
if specDh == nil {
|
||||
// load the hierarchy
|
||||
specDh, err = mtree.ParseSpec(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We can't check against more fields than in the specKeywords list, so
|
||||
// currentKeywords can only have a subset of specKeywords.
|
||||
specKeywords = specDh.UsedKeywords()
|
||||
}
|
||||
|
||||
// This is a validation.
|
||||
if specDh != nil && stateDh != nil {
|
||||
var res []mtree.InodeDelta
|
||||
res, err = mtree.Compare(specDh, stateDh, currentKeywords)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res != nil {
|
||||
if isTarSpec(specDh) || *flTar != "" {
|
||||
res = filterMissingKeywords(res)
|
||||
}
|
||||
|
||||
out := formatFunc(res)
|
||||
if _, err := os.Stdout.Write([]byte(out)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: This should be a flag. Allowing files to be added and
|
||||
// removed and still returning "it's all good" is simply
|
||||
// unsafe IMO.
|
||||
for _, diff := range res {
|
||||
if diff.Type() == mtree.Modified {
|
||||
return fmt.Errorf("mainfest validation failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("neither validating or creating a manifest. Please provide additional arguments")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var formats = map[string]func([]mtree.InodeDelta) string{
|
||||
// Outputs the errors in the BSD format.
|
||||
"bsd": func(d []mtree.InodeDelta) string {
|
||||
var buffer bytes.Buffer
|
||||
for _, delta := range d {
|
||||
if delta.Type() == mtree.Modified {
|
||||
fmt.Fprintln(&buffer, delta)
|
||||
}
|
||||
fmt.Fprintln(&buffer, delta)
|
||||
}
|
||||
return buffer.String()
|
||||
},
|
||||
|
@ -146,301 +462,10 @@ func isTarSpec(spec *mtree.DirectoryHierarchy) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
|
||||
// so that defers cleanly exec
|
||||
// TODO: Switch everything to being inside a function, to remove the need for isErr.
|
||||
var isErr bool
|
||||
defer func() {
|
||||
if isErr {
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
if *flVersion {
|
||||
fmt.Printf("%s :: %s\n", os.Args[0], mtree.Version)
|
||||
return
|
||||
}
|
||||
|
||||
// -list-keywords
|
||||
if *flListKeywords {
|
||||
fmt.Println("Available keywords:")
|
||||
for k := range mtree.KeywordFuncs {
|
||||
fmt.Print(" ")
|
||||
fmt.Print(k)
|
||||
if mtree.Keyword(k).Default() {
|
||||
fmt.Print(" (default)")
|
||||
}
|
||||
if !mtree.Keyword(k).Bsd() {
|
||||
fmt.Print(" (not upstream)")
|
||||
}
|
||||
fmt.Print("\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// --result-format
|
||||
formatFunc, ok := formats[*flResultFormat]
|
||||
if !ok {
|
||||
log.Printf("invalid output format: %s", *flResultFormat)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
tmpKeywords []string
|
||||
currentKeywords []string
|
||||
)
|
||||
|
||||
// -k <keywords>
|
||||
if *flUseKeywords != "" {
|
||||
tmpKeywords = splitKeywordsArg(*flUseKeywords)
|
||||
if !inSlice("type", tmpKeywords) {
|
||||
tmpKeywords = append([]string{"type"}, tmpKeywords...)
|
||||
}
|
||||
} else {
|
||||
if *flTar != "" {
|
||||
tmpKeywords = mtree.DefaultTarKeywords[:]
|
||||
} else {
|
||||
tmpKeywords = mtree.DefaultKeywords[:]
|
||||
}
|
||||
}
|
||||
|
||||
// -K <keywords>
|
||||
if *flAddKeywords != "" {
|
||||
for _, kw := range splitKeywordsArg(*flAddKeywords) {
|
||||
if !inSlice(kw, tmpKeywords) {
|
||||
tmpKeywords = append(tmpKeywords, kw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -bsd-keywords
|
||||
if *flBsdKeywords {
|
||||
for _, k := range tmpKeywords {
|
||||
if mtree.Keyword(k).Bsd() {
|
||||
currentKeywords = append(currentKeywords, k)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "INFO: ignoring %q as it is not an upstream keyword\n", k)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentKeywords = tmpKeywords
|
||||
}
|
||||
|
||||
// Check mutual exclusivity of keywords.
|
||||
// TODO(cyphar): Abstract this inside keywords.go.
|
||||
if inSlice("tar_time", currentKeywords) && inSlice("time", currentKeywords) {
|
||||
log.Printf("tar_time and time are mutually exclusive keywords")
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
|
||||
// If we're doing a comparison, we always are comparing between a spec and
|
||||
// state DH. If specDh is nil, we are generating a new one.
|
||||
var (
|
||||
specDh *mtree.DirectoryHierarchy
|
||||
stateDh *mtree.DirectoryHierarchy
|
||||
specKeywords []string
|
||||
)
|
||||
|
||||
// -f <file>
|
||||
if *flFile != "" && !*flCreate {
|
||||
// load the hierarchy, if we're not creating a new spec
|
||||
fh, err := os.Open(*flFile)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
specDh, err = mtree.ParseSpec(fh)
|
||||
fh.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
|
||||
// We can't check against more fields than in the specKeywords list, so
|
||||
// currentKeywords can only have a subset of specKeywords.
|
||||
specKeywords = mtree.CollectUsedKeywords(specDh)
|
||||
}
|
||||
|
||||
// -list-used
|
||||
if *flListUsedKeywords {
|
||||
if specDh == nil {
|
||||
log.Println("no specification provided. please provide a validation manifest")
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
|
||||
if *flResultFormat == "json" {
|
||||
// if they're asking for json, give it to them
|
||||
data := map[string][]string{*flFile: specKeywords}
|
||||
buf, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
defer os.Exit(1)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
fmt.Println(string(buf))
|
||||
} else {
|
||||
fmt.Printf("Keywords used in [%s]:\n", *flFile)
|
||||
for _, kw := range specKeywords {
|
||||
fmt.Printf(" %s", kw)
|
||||
if _, ok := mtree.KeywordFuncs[kw]; !ok {
|
||||
fmt.Print(" (unsupported)")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if specKeywords != nil {
|
||||
// If we didn't actually change the set of keywords, we can just use specKeywords.
|
||||
if *flUseKeywords == "" && *flAddKeywords == "" {
|
||||
currentKeywords = specKeywords
|
||||
}
|
||||
|
||||
for _, keyword := range currentKeywords {
|
||||
// As always, time is a special case.
|
||||
// TODO: Fix that.
|
||||
if (keyword == "time" && inSlice("tar_time", specKeywords)) || (keyword == "tar_time" && inSlice("time", specKeywords)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !inSlice(keyword, specKeywords) {
|
||||
log.Printf("cannot verify keywords not in mtree specification: %s\n", keyword)
|
||||
isErr = true
|
||||
}
|
||||
if isErr {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -p and -T are mutually exclusive
|
||||
if *flPath != "" && *flTar != "" {
|
||||
log.Println("options -T and -p are mutually exclusive")
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
|
||||
// -p <path>
|
||||
var rootPath = "."
|
||||
if *flPath != "" {
|
||||
rootPath = *flPath
|
||||
}
|
||||
|
||||
// -T <tar file>
|
||||
if *flTar != "" {
|
||||
var input io.Reader
|
||||
if *flTar == "-" {
|
||||
input = os.Stdin
|
||||
} else {
|
||||
fh, err := os.Open(*flTar)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
defer fh.Close()
|
||||
input = fh
|
||||
}
|
||||
ts := mtree.NewTarStreamer(input, currentKeywords)
|
||||
|
||||
if _, err := io.Copy(ioutil.Discard, ts); err != nil && err != io.EOF {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
if err := ts.Close(); err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
var err error
|
||||
stateDh, err = ts.Hierarchy()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// with a root directory
|
||||
stateDh, err = mtree.Walk(rootPath, nil, currentKeywords)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// -c
|
||||
if *flCreate {
|
||||
fh := os.Stdout
|
||||
if *flFile != "" {
|
||||
fh, err = os.Create(*flFile)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// output stateDh
|
||||
stateDh.WriteTo(fh)
|
||||
return
|
||||
}
|
||||
|
||||
// This is a validation.
|
||||
if specDh != nil && stateDh != nil {
|
||||
var res []mtree.InodeDelta
|
||||
|
||||
res, err = mtree.Compare(specDh, stateDh, currentKeywords)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
if res != nil {
|
||||
if isTarSpec(specDh) || *flTar != "" {
|
||||
res = filterMissingKeywords(res)
|
||||
}
|
||||
if len(res) > 0 {
|
||||
defer os.Exit(1)
|
||||
}
|
||||
|
||||
out := formatFunc(res)
|
||||
if _, err := os.Stdout.Write([]byte(out)); err != nil {
|
||||
log.Println(err)
|
||||
isErr = true
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Println("neither validating or creating a manifest. Please provide additional arguments")
|
||||
isErr = true
|
||||
return
|
||||
func splitKeywordsArg(str string) []mtree.Keyword {
|
||||
keywords := []mtree.Keyword{}
|
||||
for _, kw := range strings.Fields(strings.Replace(str, ",", " ", -1)) {
|
||||
keywords = append(keywords, mtree.KeywordSynonym(kw))
|
||||
}
|
||||
}
|
||||
|
||||
func splitKeywordsArg(str string) []string {
|
||||
return strings.Fields(strings.Replace(str, ",", " ", -1))
|
||||
}
|
||||
|
||||
func inSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return keywords
|
||||
}
|
||||
|
|
164
compare.go
164
compare.go
|
@ -29,6 +29,14 @@ const (
|
|||
// have different values (or have not been set in one of the
|
||||
// manifests).
|
||||
Modified DifferenceType = "modified"
|
||||
|
||||
// Same represents the case where two files are the same. These are
|
||||
// only generated from CompareSame().
|
||||
Same DifferenceType = "same"
|
||||
|
||||
// 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
|
||||
|
@ -123,9 +131,10 @@ func (i InodeDelta) String() string {
|
|||
// returned with InodeDelta.Diff().
|
||||
type KeyDelta struct {
|
||||
diff DifferenceType
|
||||
name string
|
||||
name Keyword
|
||||
old string
|
||||
new string
|
||||
err error // used for update delta results
|
||||
}
|
||||
|
||||
// Type returns the type of discrepancy encountered when comparing this key
|
||||
|
@ -136,7 +145,7 @@ func (k KeyDelta) Type() DifferenceType {
|
|||
|
||||
// Name returns the name (the key) of the KeyDeltaVal entry in the
|
||||
// DirectoryHierarchy.
|
||||
func (k KeyDelta) Name() string {
|
||||
func (k KeyDelta) Name() Keyword {
|
||||
return k.name
|
||||
}
|
||||
|
||||
|
@ -152,10 +161,10 @@ func (k KeyDelta) Old() *string {
|
|||
|
||||
// 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.
|
||||
// there was no entry in the "new" DirectoryHierarchy.
|
||||
func (k KeyDelta) New() *string {
|
||||
if k.diff == Modified || k.diff == Extra {
|
||||
return sPtr(k.old)
|
||||
return sPtr(k.new)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -164,7 +173,7 @@ func (k KeyDelta) New() *string {
|
|||
func (k KeyDelta) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Type DifferenceType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Name Keyword `json:"name"`
|
||||
Old string `json:"old"`
|
||||
New string `json:"new"`
|
||||
}{
|
||||
|
@ -184,11 +193,17 @@ func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
|
|||
New *KeyVal
|
||||
}
|
||||
|
||||
diffs := map[string]*stateT{}
|
||||
diffs := map[Keyword]*stateT{}
|
||||
oldKeys := oldEntry.AllKeys()
|
||||
newKeys := newEntry.AllKeys()
|
||||
|
||||
// Fill the map with the old keys first.
|
||||
for _, kv := range oldEntry.AllKeys() {
|
||||
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)
|
||||
|
@ -202,8 +217,12 @@ func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
|
|||
}
|
||||
|
||||
// Then fill the new keys.
|
||||
for _, kv := range newEntry.AllKeys() {
|
||||
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)
|
||||
|
@ -218,7 +237,7 @@ func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
|
|||
|
||||
// We need a full list of the keys so we can deal with different keyvalue
|
||||
// orderings.
|
||||
var kws []string
|
||||
var kws []Keyword
|
||||
for kw := range diffs {
|
||||
kws = append(kws, kw)
|
||||
}
|
||||
|
@ -226,7 +245,7 @@ func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
|
|||
// 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 inSlice("tar_time", kws) && inSlice("time", kws) {
|
||||
if InKeywordSlice("tar_time", kws) && InKeywordSlice("time", kws) {
|
||||
// Delete "time".
|
||||
timeStateT := diffs["time"]
|
||||
delete(diffs, "time")
|
||||
|
@ -284,7 +303,7 @@ func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
|
|||
|
||||
// Modified
|
||||
default:
|
||||
if !KeyValEqual(*diff.Old, *diff.New) {
|
||||
if !diff.Old.Equal(*diff.New) {
|
||||
results = append(results, KeyDelta{
|
||||
diff: Modified,
|
||||
name: name,
|
||||
|
@ -298,74 +317,59 @@ func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
|
|||
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.
|
||||
//
|
||||
// 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 []string) ([]InodeDelta, error) {
|
||||
// compare is the actual workhorse for Compare() and CompareSame()
|
||||
func compare(oldDh, newDh *DirectoryHierarchy, keys []Keyword, same bool) ([]InodeDelta, error) {
|
||||
// Represents the new and old states for an entry.
|
||||
type stateT struct {
|
||||
Old *Entry
|
||||
New *Entry
|
||||
}
|
||||
|
||||
// Make dealing with the keys mapping easier.
|
||||
keySet := map[string]struct{}{}
|
||||
for _, key := range keys {
|
||||
keySet[key] = struct{}{}
|
||||
}
|
||||
|
||||
// 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.
|
||||
for _, e := range oldDh.Entries {
|
||||
if e.Type == RelativeType || e.Type == FullType {
|
||||
path, err := e.Path()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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.
|
||||
copy := new(Entry)
|
||||
*copy = e
|
||||
// Cannot take &kv because it's the iterator.
|
||||
cEntry := new(Entry)
|
||||
*cEntry = e
|
||||
|
||||
_, ok := diffs[path]
|
||||
if !ok {
|
||||
diffs[path] = &stateT{}
|
||||
_, ok := diffs[path]
|
||||
if !ok {
|
||||
diffs[path] = &stateT{}
|
||||
}
|
||||
diffs[path].Old = cEntry
|
||||
}
|
||||
diffs[path].Old = copy
|
||||
}
|
||||
}
|
||||
|
||||
// Then, iterate over the new hierarchy.
|
||||
for _, e := range newDh.Entries {
|
||||
if e.Type == RelativeType || e.Type == FullType {
|
||||
path, err := e.Path()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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.
|
||||
copy := new(Entry)
|
||||
*copy = e
|
||||
// Cannot take &kv because it's the iterator.
|
||||
cEntry := new(Entry)
|
||||
*cEntry = e
|
||||
|
||||
_, ok := diffs[path]
|
||||
if !ok {
|
||||
diffs[path] = &stateT{}
|
||||
_, ok := diffs[path]
|
||||
if !ok {
|
||||
diffs[path] = &stateT{}
|
||||
}
|
||||
diffs[path].New = cEntry
|
||||
}
|
||||
diffs[path].New = copy
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -405,7 +409,7 @@ func Compare(oldDh, newDh *DirectoryHierarchy, keys []string) ([]InodeDelta, err
|
|||
if keys != nil {
|
||||
var filterChanged []KeyDelta
|
||||
for _, keyDiff := range changed {
|
||||
if _, ok := keySet[keyDiff.name]; ok {
|
||||
if InKeywordSlice(keyDiff.name.Prefix(), keys) {
|
||||
filterChanged = append(filterChanged, keyDiff)
|
||||
}
|
||||
}
|
||||
|
@ -421,9 +425,47 @@ func Compare(oldDh, newDh *DirectoryHierarchy, keys []string) ([]InodeDelta, err
|
|||
new: *diff.New,
|
||||
keys: changed,
|
||||
})
|
||||
} else if same {
|
||||
// this means that nothing changed, i.e. that
|
||||
// the files are the same.
|
||||
results = append(results, InodeDelta{
|
||||
diff: Same,
|
||||
path: path,
|
||||
old: *diff.Old,
|
||||
new: *diff.New,
|
||||
keys: changed,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return compare(oldDh, newDh, keys, false)
|
||||
}
|
||||
|
||||
// CompareSame is the same as Compare, except it also includes the entries
|
||||
// that are the same with a Same DifferenceType.
|
||||
func CompareSame(oldDh, newDh *DirectoryHierarchy, keys []Keyword) ([]InodeDelta, error) {
|
||||
return compare(oldDh, newDh, keys, true)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package mtree
|
|||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -14,12 +15,12 @@ import (
|
|||
// simple walk of current directory, and imediately check it.
|
||||
// may not be parallelizable.
|
||||
func TestCompare(t *testing.T) {
|
||||
old, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
||||
old, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
new, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
||||
new, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -58,7 +59,7 @@ func TestCompareModified(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk the current state.
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -69,7 +70,7 @@ func TestCompareModified(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk the new state.
|
||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -139,7 +140,7 @@ func TestCompareMissing(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk the current state.
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -158,7 +159,7 @@ func TestCompareMissing(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk the new state.
|
||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -214,7 +215,7 @@ func TestCompareExtra(t *testing.T) {
|
|||
defer os.RemoveAll(dir)
|
||||
|
||||
// Walk the current state.
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -236,7 +237,7 @@ func TestCompareExtra(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk the new state.
|
||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -308,7 +309,7 @@ func TestCompareKeys(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk the current state.
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -319,13 +320,13 @@ func TestCompareKeys(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk the new state.
|
||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Compare.
|
||||
diffs, err := Compare(old, new, []string{"size"})
|
||||
diffs, err := Compare(old, new, []Keyword{"size"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -381,7 +382,7 @@ func TestTarCompare(t *testing.T) {
|
|||
}
|
||||
|
||||
// Walk the current state.
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -391,7 +392,7 @@ func TestTarCompare(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
str := NewTarStreamer(bytes.NewBuffer(ts), append(DefaultTarKeywords, "sha1"))
|
||||
str := NewTarStreamer(bytes.NewBuffer(ts), nil, append(DefaultTarKeywords, "sha1"))
|
||||
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -443,7 +444,12 @@ func TestTarCompare(t *testing.T) {
|
|||
}
|
||||
|
||||
actualFailure = true
|
||||
t.Logf("FAILURE: diff[%d] = %#v", i, delta)
|
||||
buf, err := json.MarshalIndent(delta, "", " ")
|
||||
if err == nil {
|
||||
t.Logf("FAILURE: diff[%d] = %s", i, string(buf))
|
||||
} else {
|
||||
t.Logf("FAILURE: diff[%d] = %#v", i, delta)
|
||||
}
|
||||
}
|
||||
|
||||
if actualFailure {
|
||||
|
|
|
@ -3,6 +3,7 @@ package mtree
|
|||
// dhCreator is used in when building a DirectoryHierarchy
|
||||
type dhCreator struct {
|
||||
DH *DirectoryHierarchy
|
||||
fs FsEval
|
||||
curSet *Entry
|
||||
curDir *Entry
|
||||
curEnt *Entry
|
||||
|
|
18
debug.go
18
debug.go
|
@ -1,18 +0,0 @@
|
|||
package mtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DebugOutput is the where DEBUG output is written
|
||||
var DebugOutput = os.Stderr
|
||||
|
||||
// Debugf does formatted output to DebugOutput, only if DEBUG environment variable is set
|
||||
func Debugf(format string, a ...interface{}) (n int, err error) {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
return fmt.Fprintf(DebugOutput, "[%d] [DEBUG] %s\n", time.Now().UnixNano(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
return 0, nil
|
||||
}
|
74
entry.go
74
entry.go
|
@ -2,8 +2,11 @@ package mtree
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/vbatts/go-mtree/pkg/govis"
|
||||
)
|
||||
|
||||
type byPos []Entry
|
||||
|
@ -21,7 +24,7 @@ type Entry struct {
|
|||
Pos int // order in the spec
|
||||
Raw string // file or directory name
|
||||
Name string // file or directory name
|
||||
Keywords []string // TODO(vbatts) maybe a keyword typed set of values?
|
||||
Keywords []KeyVal // TODO(vbatts) maybe a keyword typed set of values?
|
||||
Type EntryType
|
||||
}
|
||||
|
||||
|
@ -47,7 +50,7 @@ func (e Entry) Descend(filename string) *Entry {
|
|||
func (e Entry) Find(filepath string) *Entry {
|
||||
resultnode := &e
|
||||
for _, path := range strings.Split(filepath, "/") {
|
||||
encoded, err := Vis(path)
|
||||
encoded, err := govis.Vis(path, DefaultVisFlags)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -65,21 +68,55 @@ 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 := Unvis(e.Name)
|
||||
decodedName, err := govis.Unvis(e.Name, DefaultVisFlags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
decodedName = CleanPath(decodedName)
|
||||
if e.Parent == nil || e.Type == FullType {
|
||||
return filepath.Clean(decodedName), nil
|
||||
return decodedName, nil
|
||||
}
|
||||
parentName, err := e.Parent.Path()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Clean(filepath.Join(parentName, decodedName)), nil
|
||||
return CleanPath(filepath.Join(parentName, decodedName)), nil
|
||||
}
|
||||
|
||||
// String joins a file with its associated keywords. The file name will be the
|
||||
|
@ -94,23 +131,30 @@ func (e Entry) String() string {
|
|||
if e.Type == DotDotType {
|
||||
return e.Name
|
||||
}
|
||||
if e.Type == SpecialType || e.Type == FullType || inSlice("type=dir", e.Keywords) {
|
||||
return fmt.Sprintf("%s %s", e.Name, strings.Join(e.Keywords, " "))
|
||||
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(e.Keywords, " "))
|
||||
return fmt.Sprintf(" %s %s", e.Name, strings.Join(KeyValToString(e.Keywords), " "))
|
||||
}
|
||||
|
||||
// AllKeys returns the full set of KeyVals for the given entry, based on the
|
||||
// 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() KeyVals {
|
||||
var kv KeyVals
|
||||
func (e Entry) AllKeys() []KeyVal {
|
||||
if e.Set != nil {
|
||||
kv = MergeSet(e.Set.Keywords, e.Keywords)
|
||||
} else {
|
||||
kv = NewKeyVals(e.Keywords)
|
||||
return MergeKeyValSet(e.Set.Keywords, e.Keywords)
|
||||
}
|
||||
return kv
|
||||
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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
package mtree
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var mockTime = time.Unix(1337888823, 0)
|
||||
|
||||
// Here be some dodgy testing. In particular, we have to mess around with some
|
||||
// of the FsEval functions. In particular, we change all of the FileInfos to a
|
||||
// different value.
|
||||
|
||||
type mockFileInfo struct {
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (fi mockFileInfo) Mode() os.FileMode {
|
||||
return os.FileMode(fi.FileInfo.Mode() | 0777)
|
||||
}
|
||||
|
||||
func (fi mockFileInfo) ModTime() time.Time {
|
||||
return mockTime
|
||||
}
|
||||
|
||||
type MockFsEval struct {
|
||||
open, lstat, readdir, keywordFunc int
|
||||
}
|
||||
|
||||
// Open must have the same semantics as os.Open.
|
||||
func (fs *MockFsEval) Open(path string) (*os.File, error) {
|
||||
fs.open++
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
// Lstat must have the same semantics as os.Lstat.
|
||||
func (fs *MockFsEval) Lstat(path string) (os.FileInfo, error) {
|
||||
fs.lstat++
|
||||
fi, err := os.Lstat(path)
|
||||
return mockFileInfo{fi}, err
|
||||
}
|
||||
|
||||
// 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 *MockFsEval) Readdir(path string) ([]os.FileInfo, error) {
|
||||
fs.readdir++
|
||||
fh, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
fis, err := fh.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for idx := range fis {
|
||||
fis[idx] = mockFileInfo{fis[idx]}
|
||||
}
|
||||
return fis, nil
|
||||
}
|
||||
|
||||
// KeywordFunc must return a wrapper around the provided function (in other
|
||||
// words, the returned function must refer to the same keyword).
|
||||
func (fs *MockFsEval) KeywordFunc(fn KeywordFunc) KeywordFunc {
|
||||
fs.keywordFunc++
|
||||
return fn
|
||||
}
|
||||
|
||||
func TestCheckFsEval(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "test-check-fs-eval")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir) // clean up
|
||||
|
||||
content := []byte("If you hide your ignorance, no one will hit you and you'll never learn.")
|
||||
tmpfn := filepath.Join(dir, "tmpfile")
|
||||
if err := ioutil.WriteFile(tmpfn, content, 0451); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Walk this tempdir
|
||||
mock := &MockFsEval{}
|
||||
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), mock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Make sure that mock functions have been called.
|
||||
if mock.open == 0 {
|
||||
t.Errorf("mock.Open not called")
|
||||
}
|
||||
if mock.lstat == 0 {
|
||||
t.Errorf("mock.Lstat not called")
|
||||
}
|
||||
if mock.readdir == 0 {
|
||||
t.Errorf("mock.Readdir not called")
|
||||
}
|
||||
if mock.keywordFunc == 0 {
|
||||
t.Errorf("mock.KeywordFunc not called")
|
||||
}
|
||||
|
||||
// Check for sanity. This ought to pass.
|
||||
mock = &MockFsEval{}
|
||||
res, err := Check(dir, dh, nil, mock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) > 0 {
|
||||
t.Errorf("%#v", res)
|
||||
}
|
||||
// Make sure that mock functions have been called.
|
||||
if mock.open == 0 {
|
||||
t.Errorf("mock.Open not called")
|
||||
}
|
||||
if mock.lstat == 0 {
|
||||
t.Errorf("mock.Lstat not called")
|
||||
}
|
||||
if mock.readdir == 0 {
|
||||
t.Errorf("mock.Readdir not called")
|
||||
}
|
||||
if mock.keywordFunc == 0 {
|
||||
t.Errorf("mock.KeywordFunc not called")
|
||||
}
|
||||
|
||||
// This should FAIL.
|
||||
res, err = Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) == 0 {
|
||||
t.Errorf("expected Check to fail")
|
||||
}
|
||||
|
||||
// Modify the metadata so you can get the right output.
|
||||
if err := os.Chmod(tmpfn, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chtimes(tmpfn, mockTime, mockTime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chmod(dir, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chtimes(dir, mockTime, mockTime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// It should now succeed.
|
||||
res, err = Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) > 0 {
|
||||
buf, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
t.Errorf("%#v", res)
|
||||
} else {
|
||||
t.Errorf("%s", buf)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
module github.com/vbatts/go-mtree
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fatih/color v1.12.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
|
||||
)
|
|
@ -0,0 +1,26 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
29
hierarchy.go
29
hierarchy.go
|
@ -26,26 +26,23 @@ func (dh DirectoryHierarchy) WriteTo(w io.Writer) (n int64, err error) {
|
|||
return sum, nil
|
||||
}
|
||||
|
||||
// CollectUsedKeywords collects and returns all the keywords used in a
|
||||
// UsedKeywords collects and returns all the keywords used in a
|
||||
// a DirectoryHierarchy
|
||||
func CollectUsedKeywords(dh *DirectoryHierarchy) []string {
|
||||
if dh != nil {
|
||||
usedkeywords := []string{}
|
||||
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()
|
||||
if !inSlice(kw, usedkeywords) {
|
||||
usedkeywords = append(usedkeywords, kw)
|
||||
}
|
||||
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
|
||||
}
|
||||
return nil
|
||||
return usedkeywords
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package mtree
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var checklist = []struct {
|
||||
blob string
|
||||
set []Keyword
|
||||
}{
|
||||
{blob: `
|
||||
# machine: bananaboat
|
||||
# tree: .git
|
||||
# date: Wed Nov 16 14:54:17 2016
|
||||
|
||||
# .
|
||||
/set type=file nlink=1 mode=0664 uid=1000 gid=100
|
||||
. size=4096 type=dir mode=0755 nlink=8 time=1479326055.423853146
|
||||
.COMMIT_EDITMSG.un~ size=1006 mode=0644 time=1479325423.450468662 sha1digest=dead0face
|
||||
.TAG_EDITMSG.un~ size=1069 mode=0600 time=1471362316.801317529 sha256digest=dead0face
|
||||
`, set: []Keyword{"size", "mode", "time", "sha256digest"}},
|
||||
{blob: `
|
||||
# user: cyphar
|
||||
# machine: ryuk
|
||||
# tree: xattr
|
||||
# date: Fri Sep 29 21:00:41 2017
|
||||
# keywords: size,type,uid,gid,mode,link,nlink,time,xattr
|
||||
|
||||
# .
|
||||
/set type=file nlink=1 mode=0664 uid=1000 gid=100 xattr.user.kira=SSdsbCB0YWtlIGEgcG90YXRvIGNoaXAuLi4gYW5kIGVhdCBpdCE=
|
||||
. size=8 type=dir mode=0755 time=1506666472.255992830
|
||||
file size=0 mode=0644 time=1506666472.255992830 xattr.user.something=dGVzdA==
|
||||
..
|
||||
`, set: []Keyword{"size", "type", "uid", "gid", "mode", "nlink", "time", "xattr"}},
|
||||
}
|
||||
|
||||
func TestUsedKeywords(t *testing.T) {
|
||||
for i, item := range checklist {
|
||||
dh, err := ParseSpec(strings.NewReader(item.blob))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
used := dh.UsedKeywords()
|
||||
for _, k := range item.set {
|
||||
if !InKeywordSlice(k, used) {
|
||||
t.Errorf("%d: expected to find %q in %q", i, k, used)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
)
|
|
@ -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
|
||||
}
|
||||
)
|
|
@ -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
|
||||
}
|
||||
)
|
|
@ -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
|
||||
}
|
||||
)
|
418
keywords.go
418
keywords.go
|
@ -1,68 +1,121 @@
|
|||
package mtree
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
"github.com/vbatts/go-mtree/pkg/govis"
|
||||
)
|
||||
|
||||
// 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) (string, error)
|
||||
// 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 inSlice(string(k), DefaultKeywords)
|
||||
return InKeywordSlice(k, DefaultKeywords)
|
||||
}
|
||||
|
||||
// Bsd returns whether this keyword is in the upstream FreeBSD mtree(8)
|
||||
func (k Keyword) Bsd() bool {
|
||||
return inSlice(string(k), BsdKeywords)
|
||||
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() string {
|
||||
func (kv KeyVal) Keyword() Keyword {
|
||||
if !strings.Contains(string(kv), "=") {
|
||||
return ""
|
||||
return Keyword("")
|
||||
}
|
||||
chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
|
||||
if !strings.Contains(chunks, ".") {
|
||||
return chunks
|
||||
}
|
||||
return strings.SplitN(chunks, ".", 2)[0]
|
||||
}
|
||||
|
||||
// KeywordSuffix is really only used for xattr, as the keyword is a prefix to
|
||||
// the xattr "namespace.key"
|
||||
func (kv KeyVal) KeywordSuffix() string {
|
||||
if !strings.Contains(string(kv), "=") {
|
||||
return ""
|
||||
}
|
||||
chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
|
||||
if !strings.Contains(chunks, ".") {
|
||||
return ""
|
||||
}
|
||||
return strings.SplitN(chunks, ".", 2)[1]
|
||||
return Keyword(strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0])
|
||||
}
|
||||
|
||||
// Value is the data/value portion of "keyword=value"
|
||||
|
@ -73,72 +126,105 @@ func (kv KeyVal) Value() string {
|
|||
return strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[1]
|
||||
}
|
||||
|
||||
// ChangeValue changes the value of a KeyVal
|
||||
func (kv KeyVal) ChangeValue(newval string) string {
|
||||
return fmt.Sprintf("%s=%s", kv.Keyword(), newval)
|
||||
// NewValue returns a new KeyVal with the newval
|
||||
func (kv KeyVal) NewValue(newval string) KeyVal {
|
||||
return KeyVal(fmt.Sprintf("%s=%s", kv.Keyword(), newval))
|
||||
}
|
||||
|
||||
// KeyValEqual returns whether two KeyVals are equivalent. This takes
|
||||
// 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 KeyValEqual(a, b KeyVal) bool {
|
||||
func (kv KeyVal) Equal(b KeyVal) bool {
|
||||
// TODO: Implement handling of tar_mtime.
|
||||
return a.Keyword() == b.Keyword() && a.Value() == b.Value()
|
||||
return kv.Keyword() == b.Keyword() && kv.Value() == b.Value()
|
||||
}
|
||||
|
||||
// keywordSelector takes an array of "keyword=value" and filters out that only the set of words
|
||||
func keywordSelector(keyval, words []string) []string {
|
||||
retList := []string{}
|
||||
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 inSlice(KeyVal(kv).Keyword(), words) {
|
||||
if InKeywordSlice(kv.Keyword().Prefix(), keywordPrefixes(keyset)) {
|
||||
retList = append(retList, kv)
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
|
||||
// NewKeyVals constructs a list of KeyVal from the list of strings, like "keyword=value"
|
||||
func NewKeyVals(keyvals []string) KeyVals {
|
||||
kvs := make(KeyVals, len(keyvals))
|
||||
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 {
|
||||
kvs[i] = KeyVal(keyvals[i])
|
||||
if keyvals[i].Keyword().Prefix() == keyword.Prefix() {
|
||||
kvs = append(kvs, keyvals[i])
|
||||
}
|
||||
}
|
||||
return kvs
|
||||
}
|
||||
|
||||
// KeyVals is a list of KeyVal
|
||||
type KeyVals []KeyVal
|
||||
|
||||
// Has the "keyword" present in the list of KeyVal, and returns the
|
||||
// corresponding KeyVal, else an empty string.
|
||||
func (kvs KeyVals) Has(keyword string) KeyVal {
|
||||
for i := range kvs {
|
||||
if kvs[i].Keyword() == keyword {
|
||||
return kvs[i]
|
||||
}
|
||||
}
|
||||
return emptyKV
|
||||
}
|
||||
|
||||
var emptyKV = KeyVal("")
|
||||
|
||||
// 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) KeyVals {
|
||||
retList := NewKeyVals(append([]string{}, setKeyVals...))
|
||||
eKVs := NewKeyVals(entryKeyVals)
|
||||
seenKeywords := []string{}
|
||||
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()
|
||||
if ekv := eKVs.Has(word); ekv != emptyKV {
|
||||
retList[i] = ekv
|
||||
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 eKVs {
|
||||
if !inSlice(eKVs[i].Keyword(), seenKeywords) {
|
||||
retList = append(retList, eKVs[i])
|
||||
for i := range entryKeyVals {
|
||||
if !InKeywordSlice(entryKeyVals[i].Keyword(), seenKeywords) {
|
||||
retList = append(retList, entryKeyVals[i])
|
||||
}
|
||||
}
|
||||
return retList
|
||||
|
@ -147,7 +233,7 @@ func MergeSet(setKeyVals, entryKeyVals []string) KeyVals {
|
|||
var (
|
||||
// DefaultKeywords has the several default keyword producers (uid, gid,
|
||||
// mode, nlink, type, size, mtime)
|
||||
DefaultKeywords = []string{
|
||||
DefaultKeywords = []Keyword{
|
||||
"size",
|
||||
"type",
|
||||
"uid",
|
||||
|
@ -160,7 +246,7 @@ var (
|
|||
|
||||
// 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 = []string{
|
||||
DefaultTarKeywords = []Keyword{
|
||||
"size",
|
||||
"type",
|
||||
"uid",
|
||||
|
@ -171,9 +257,8 @@ var (
|
|||
}
|
||||
|
||||
// BsdKeywords is the set of keywords that is only in the upstream FreeBSD mtree
|
||||
BsdKeywords = []string{
|
||||
BsdKeywords = []Keyword{
|
||||
"cksum",
|
||||
"device",
|
||||
"flags", // this one is really mostly BSD specific ...
|
||||
"ignore",
|
||||
"gid",
|
||||
|
@ -205,151 +290,38 @@ var (
|
|||
}
|
||||
|
||||
// SetKeywords is the default set of keywords calculated for a `/set` SpecialType
|
||||
SetKeywords = []string{
|
||||
SetKeywords = []Keyword{
|
||||
"uid",
|
||||
"gid",
|
||||
}
|
||||
// KeywordFuncs is the map of all keywords (and the functions to produce them)
|
||||
KeywordFuncs = map[string]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
|
||||
"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`
|
||||
|
||||
"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) (string, 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 fmt.Sprintf("mode=%#o", permissions), nil
|
||||
// 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
|
||||
}
|
||||
sizeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if sys, ok := info.Sys().(*tar.Header); ok {
|
||||
if sys.Typeflag == tar.TypeSymlink {
|
||||
return fmt.Sprintf("size=%d", len(sys.Linkname)), nil
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("size=%d", info.Size()), nil
|
||||
}
|
||||
cksumKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if !info.Mode().IsRegular() {
|
||||
return "", nil
|
||||
}
|
||||
sum, _, err := cksum(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return 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) (string, error) {
|
||||
if !info.Mode().IsRegular() {
|
||||
return "", nil
|
||||
}
|
||||
h := newHash()
|
||||
if _, err := io.Copy(h, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s=%x", name, h.Sum(nil)), nil
|
||||
}
|
||||
}
|
||||
tartimeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
return fmt.Sprintf("tar_time=%d.%9.9d", info.ModTime().Unix(), 0), nil
|
||||
}
|
||||
timeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
tSec := info.ModTime().Unix()
|
||||
tNano := info.ModTime().Nanosecond()
|
||||
return fmt.Sprintf("time=%d.%9.9d", tSec, tNano), nil
|
||||
}
|
||||
linkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if sys, ok := info.Sys().(*tar.Header); ok {
|
||||
if sys.Linkname != "" {
|
||||
linkname, err := Vis(sys.Linkname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("link=%s", linkname), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
str, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
linkname, err := Vis(str)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("link=%s", linkname), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
typeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if info.Mode().IsDir() {
|
||||
return "type=dir", nil
|
||||
}
|
||||
if info.Mode().IsRegular() {
|
||||
return "type=file", nil
|
||||
}
|
||||
if info.Mode()&os.ModeSocket != 0 {
|
||||
return "type=socket", nil
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
return "type=link", nil
|
||||
}
|
||||
if info.Mode()&os.ModeNamedPipe != 0 {
|
||||
return "type=fifo", nil
|
||||
}
|
||||
if info.Mode()&os.ModeDevice != 0 {
|
||||
if info.Mode()&os.ModeCharDevice != 0 {
|
||||
return "type=char", nil
|
||||
}
|
||||
return "type=device", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
)
|
||||
return Keyword(retname)
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
// +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) (string, error) {
|
||||
// ideally this will pull in from here https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2
|
||||
return "", nil
|
||||
}
|
||||
|
||||
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return 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 "", err
|
||||
}
|
||||
return fmt.Sprintf("uname=%s", u.Username), nil
|
||||
}
|
||||
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return fmt.Sprintf("uid=%d", hdr.Uid), nil
|
||||
}
|
||||
stat := info.Sys().(*syscall.Stat_t)
|
||||
return fmt.Sprintf("uid=%d", stat.Uid), nil
|
||||
}
|
||||
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return fmt.Sprintf("gid=%d", hdr.Gid), nil
|
||||
}
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
return fmt.Sprintf("gid=%d", stat.Gid), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
return fmt.Sprintf("nlink=%d", stat.Nlink), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
)
|
|
@ -1,87 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package mtree
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"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) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return 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 "", err
|
||||
}
|
||||
return fmt.Sprintf("uname=%s", u.Username), nil
|
||||
}
|
||||
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return fmt.Sprintf("uid=%d", hdr.Uid), nil
|
||||
}
|
||||
stat := info.Sys().(*syscall.Stat_t)
|
||||
return fmt.Sprintf("uid=%d", stat.Uid), nil
|
||||
}
|
||||
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return fmt.Sprintf("gid=%d", hdr.Gid), nil
|
||||
}
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
return fmt.Sprintf("gid=%d", stat.Gid), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
return fmt.Sprintf("nlink=%d", stat.Nlink), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
if len(hdr.Xattrs) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
klist := []string{}
|
||||
for k, v := range hdr.Xattrs {
|
||||
klist = append(klist, fmt.Sprintf("xattr.%s=%s", k, base64.StdEncoding.EncodeToString([]byte(v))))
|
||||
}
|
||||
return strings.Join(klist, " "), nil
|
||||
}
|
||||
if !info.Mode().IsRegular() && !info.Mode().IsDir() {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
xlist, err := xattr.List(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
klist := make([]string, len(xlist))
|
||||
for i := range xlist {
|
||||
data, err := xattr.Get(path, xlist[i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
klist[i] = fmt.Sprintf("xattr.%s=%s", xlist[i], base64.StdEncoding.EncodeToString(data))
|
||||
}
|
||||
return strings.Join(klist, " "), nil
|
||||
}
|
||||
)
|
|
@ -13,9 +13,14 @@ import (
|
|||
)
|
||||
|
||||
func TestXattr(t *testing.T) {
|
||||
// a bit dirty to create/destory a directory in cwd, but often /tmp is
|
||||
// mounted tmpfs and doesn't support xattrs
|
||||
dir, err := ioutil.TempDir(".", "test.xattrs.")
|
||||
testDir, present := os.LookupEnv("MTREE_TESTDIR")
|
||||
if present == false {
|
||||
// a bit dirty to create/destory a directory in cwd,
|
||||
// but often /tmp is mounted tmpfs and doesn't support
|
||||
// xattrs
|
||||
testDir = "."
|
||||
}
|
||||
dir, err := ioutil.TempDir(testDir, "test.xattrs.")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -46,12 +51,12 @@ func TestXattr(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
// Check the directory
|
||||
str, err := xattrKeywordFunc(dir, dirstat, nil)
|
||||
kvs, err := xattrKeywordFunc(dir, dirstat, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if str == "" {
|
||||
t.Errorf("expected a keyval; got %q", str)
|
||||
if len(kvs) == 0 {
|
||||
t.Errorf("expected a keyval; got none")
|
||||
}
|
||||
|
||||
filestat, err := fh.Stat()
|
||||
|
@ -59,12 +64,12 @@ func TestXattr(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
// Check the regular file
|
||||
str, err = xattrKeywordFunc(filepath.Join(dir, "file"), filestat, fh)
|
||||
kvs, err = xattrKeywordFunc(filepath.Join(dir, "file"), filestat, fh)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if str == "" {
|
||||
t.Errorf("expected a keyval; got %q", str)
|
||||
if len(kvs) == 0 {
|
||||
t.Errorf("expected a keyval; got none")
|
||||
}
|
||||
|
||||
linkstat, err := os.Lstat(filepath.Join(dir, "symlink"))
|
||||
|
|
|
@ -7,6 +7,47 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func TestKeyValRoundtrip(t *testing.T) {
|
||||
kv := KeyVal("xattr.security.selinux=dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==")
|
||||
expected := "xattr.security.selinux"
|
||||
got := string(kv.Keyword())
|
||||
if got != expected {
|
||||
t.Errorf("expected %q; got %q", expected, got)
|
||||
}
|
||||
|
||||
expected = "xattr"
|
||||
got = string(kv.Keyword().Prefix())
|
||||
if got != expected {
|
||||
t.Errorf("expected %q; got %q", expected, got)
|
||||
}
|
||||
|
||||
expected = "security.selinux"
|
||||
got = kv.Keyword().Suffix()
|
||||
if got != expected {
|
||||
t.Errorf("expected %q; got %q", expected, got)
|
||||
}
|
||||
|
||||
expected = "dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA=="
|
||||
got = kv.Value()
|
||||
if got != expected {
|
||||
t.Errorf("expected %q; got %q", expected, got)
|
||||
}
|
||||
|
||||
expected = "xattr.security.selinux=farts"
|
||||
got = string(kv.NewValue("farts"))
|
||||
if got != expected {
|
||||
t.Errorf("expected %q; got %q", expected, got)
|
||||
}
|
||||
|
||||
expected = "xattr.security.selinux=farts"
|
||||
kv1 := KeyVal(got)
|
||||
kv2 := kv.NewValue("farts")
|
||||
if !kv2.Equal(kv1) {
|
||||
t.Errorf("expected equality of %q and %q", kv1, kv2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type fakeFileInfo struct {
|
||||
mtime time.Time
|
||||
}
|
||||
|
@ -54,15 +95,18 @@ func TestKeywordsTimeNano(t *testing.T) {
|
|||
{857125628319, 0},
|
||||
} {
|
||||
mtime := time.Unix(test.sec, test.nsec)
|
||||
expected := fmt.Sprintf("time=%d.%9.9d", test.sec, test.nsec)
|
||||
expected := KeyVal(fmt.Sprintf("time=%d.%9.9d", test.sec, test.nsec))
|
||||
got, err := timeKeywordFunc("", fakeFileInfo{
|
||||
mtime: mtime,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error while parsing '%q': %q", mtime, err)
|
||||
}
|
||||
if expected != got {
|
||||
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got)
|
||||
if len(got) != 1 {
|
||||
t.Errorf("expected 1 KeyVal, but got %d", len(got))
|
||||
}
|
||||
if expected != got[0] {
|
||||
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,15 +125,49 @@ func TestKeywordsTimeTar(t *testing.T) {
|
|||
{857125628319, 0},
|
||||
} {
|
||||
mtime := time.Unix(test.sec, test.nsec)
|
||||
expected := fmt.Sprintf("tar_time=%d.%9.9d", test.sec, 0)
|
||||
expected := KeyVal(fmt.Sprintf("tar_time=%d.%9.9d", test.sec, 0))
|
||||
got, err := tartimeKeywordFunc("", fakeFileInfo{
|
||||
mtime: mtime,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error while parsing '%q': %q", mtime, err)
|
||||
}
|
||||
if expected != got {
|
||||
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got)
|
||||
if len(got) != 1 {
|
||||
t.Errorf("expected 1 KeyVal, but got %d", len(got))
|
||||
}
|
||||
if expected != got[0] {
|
||||
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeywordSynonym(t *testing.T) {
|
||||
checklist := []struct {
|
||||
give string
|
||||
expect Keyword
|
||||
}{
|
||||
{give: "time", expect: "time"},
|
||||
{give: "md5", expect: "md5digest"},
|
||||
{give: "md5digest", expect: "md5digest"},
|
||||
{give: "rmd160", expect: "ripemd160digest"},
|
||||
{give: "rmd160digest", expect: "ripemd160digest"},
|
||||
{give: "ripemd160digest", expect: "ripemd160digest"},
|
||||
{give: "sha1", expect: "sha1digest"},
|
||||
{give: "sha1digest", expect: "sha1digest"},
|
||||
{give: "sha256", expect: "sha256digest"},
|
||||
{give: "sha256digest", expect: "sha256digest"},
|
||||
{give: "sha384", expect: "sha384digest"},
|
||||
{give: "sha384digest", expect: "sha384digest"},
|
||||
{give: "sha512", expect: "sha512digest"},
|
||||
{give: "sha512digest", expect: "sha512digest"},
|
||||
{give: "xattr", expect: "xattr"},
|
||||
{give: "xattrs", expect: "xattr"},
|
||||
}
|
||||
|
||||
for i, check := range checklist {
|
||||
got := KeywordSynonym(check.give)
|
||||
if got != check.expect {
|
||||
t.Errorf("%d: expected %q; got %q", i, check.expect, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
// +build !linux,!darwin,!freebsd,!netbsd,!openbsd
|
||||
|
||||
package mtree
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return fmt.Sprintf("uname=%s", hdr.Uname), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return fmt.Sprintf("uid=%d", hdr.Uid), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
if hdr, ok := info.Sys().(*tar.Header); ok {
|
||||
return fmt.Sprintf("gid=%d", hdr.Gid), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
)
|
|
@ -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
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// +build windows
|
||||
|
||||
package mtree
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func lchtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// +build go1.7
|
||||
|
||||
package mtree
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
)
|
||||
|
||||
var lookupGroupID = user.LookupGroupId
|
|
@ -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)
|
||||
}
|
|
@ -4,25 +4,48 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
var (
|
||||
testFiles = []string{"testdata/source.mtree"}
|
||||
numEntries = map[EntryType]int{
|
||||
FullType: 0,
|
||||
RelativeType: 45,
|
||||
CommentType: 37,
|
||||
SpecialType: 7,
|
||||
DotDotType: 17,
|
||||
BlankType: 34,
|
||||
testFiles = []struct {
|
||||
Name string
|
||||
Counts map[EntryType]int
|
||||
Len int64
|
||||
}{
|
||||
{
|
||||
Name: "testdata/source.mtree",
|
||||
Counts: map[EntryType]int{
|
||||
FullType: 0,
|
||||
RelativeType: 45,
|
||||
CommentType: 37,
|
||||
SpecialType: 7,
|
||||
DotDotType: 17,
|
||||
BlankType: 34,
|
||||
},
|
||||
Len: int64(7887),
|
||||
},
|
||||
{
|
||||
Name: "testdata/source.casync-mtree",
|
||||
Counts: map[EntryType]int{
|
||||
FullType: 744,
|
||||
RelativeType: 56,
|
||||
CommentType: 37,
|
||||
SpecialType: 7,
|
||||
DotDotType: 17,
|
||||
BlankType: 34,
|
||||
},
|
||||
Len: int64(168439),
|
||||
},
|
||||
}
|
||||
expectedLength = int64(7887)
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
for _, file := range testFiles {
|
||||
for i, tf := range testFiles {
|
||||
_ = i
|
||||
func() {
|
||||
fh, err := os.Open(file)
|
||||
fh, err := os.Open(tf.Name)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -33,8 +56,17 @@ func TestParser(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if i == 1 {
|
||||
spew.Dump(dh)
|
||||
//buf, err := xml.MarshalIndent(dh, "", " ")
|
||||
//if err == nil {
|
||||
//t.Error(string(buf))
|
||||
//}
|
||||
}
|
||||
|
||||
gotNums := countTypes(dh)
|
||||
for typ, num := range numEntries {
|
||||
for typ, num := range tf.Counts {
|
||||
if gNum, ok := gotNums[typ]; ok {
|
||||
if num != gNum {
|
||||
t.Errorf("for type %s: expected %d, got %d", typ, num, gNum)
|
||||
|
@ -46,8 +78,8 @@ func TestParser(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != expectedLength {
|
||||
t.Errorf("expected to write %d, but wrote %d", expectedLength, i)
|
||||
if i != tf.Len {
|
||||
t.Errorf("expected to write %d, but wrote %d", tf.Len, i)
|
||||
}
|
||||
|
||||
}()
|
||||
|
|
4
parse.go
4
parse.go
|
@ -48,7 +48,7 @@ func ParseSpec(r io.Reader) (*DirectoryHierarchy, error) {
|
|||
// parse the options
|
||||
f := strings.Fields(str)
|
||||
e.Name = f[0]
|
||||
e.Keywords = f[1:]
|
||||
e.Keywords = StringToKeyVals(f[1:])
|
||||
if e.Name == "/set" {
|
||||
creator.curSet = &e
|
||||
} else if e.Name == "/unset" {
|
||||
|
@ -80,7 +80,7 @@ func ParseSpec(r io.Reader) (*DirectoryHierarchy, error) {
|
|||
} else {
|
||||
e.Type = RelativeType
|
||||
}
|
||||
e.Keywords = f[1:]
|
||||
e.Keywords = StringToKeyVals(f[1:])
|
||||
// TODO: gather keywords if using tar stream
|
||||
e.Parent = creator.curDir
|
||||
for i := range e.Keywords {
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
||||
```
|
|
@ -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)
|
||||
)
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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 (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const DefaultVisFlags = VisWhite | VisOctal | VisGlob
|
||||
|
||||
func TestRandomVisUnvis(t *testing.T) {
|
||||
// Randomly generate N strings.
|
||||
const N = 100
|
||||
|
||||
for i := 0; i < N; i++ {
|
||||
testBytes := make([]byte, 256)
|
||||
if n, err := rand.Read(testBytes); n != cap(testBytes) || err != nil {
|
||||
t.Fatalf("could not read enough bytes: err=%v n=%d", err, n)
|
||||
}
|
||||
test := string(testBytes)
|
||||
|
||||
for flag := VisFlag(0); flag <= visMask; flag++ {
|
||||
// VisNoSlash is frankly just a dumb flag, and it is impossible for us
|
||||
// to actually preserve things in a round-trip.
|
||||
if flag&VisNoSlash == VisNoSlash {
|
||||
continue
|
||||
}
|
||||
|
||||
enc, err := Vis(test, flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing vis(%q, %b): %s", test, flag, err)
|
||||
continue
|
||||
}
|
||||
dec, err := Unvis(enc, flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q, %b): %s", enc, flag, err)
|
||||
continue
|
||||
}
|
||||
if dec != test {
|
||||
t.Errorf("roundtrip failed: unvis(vis(%q, %b) = %q, %b) = %q", test, flag, enc, flag, dec)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomVisVisUnvisUnvis(t *testing.T) {
|
||||
// Randomly generate N strings.
|
||||
const N = 100
|
||||
|
||||
for i := 0; i < N; i++ {
|
||||
testBytes := make([]byte, 256)
|
||||
if n, err := rand.Read(testBytes); n != cap(testBytes) || err != nil {
|
||||
t.Fatalf("could not read enough bytes: err=%v n=%d", err, n)
|
||||
}
|
||||
test := string(testBytes)
|
||||
|
||||
for flag := VisFlag(0); flag <= visMask; flag++ {
|
||||
// VisNoSlash is frankly just a dumb flag, and it is impossible for us
|
||||
// to actually preserve things in a round-trip.
|
||||
if flag&VisNoSlash == VisNoSlash {
|
||||
continue
|
||||
}
|
||||
|
||||
enc, err := Vis(test, flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing vis(%q, %b): %s", test, flag, err)
|
||||
continue
|
||||
}
|
||||
enc2, err := Vis(enc, flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing vis(%q, %b): %s", enc, flag, err)
|
||||
continue
|
||||
}
|
||||
dec, err := Unvis(enc2, flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q, %b): %s", enc2, flag, err)
|
||||
continue
|
||||
}
|
||||
dec2, err := Unvis(dec, flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q, %b): %s", dec, flag, err)
|
||||
continue
|
||||
}
|
||||
if dec2 != test {
|
||||
t.Errorf("roundtrip failed: unvis(unvis(vis(vis(%q) = %q) = %q) = %q, %b) = %q", test, enc, enc2, dec, flag, dec2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisUnvis(t *testing.T) {
|
||||
for flag := VisFlag(0); flag <= visMask; flag++ {
|
||||
// VisNoSlash is frankly just a dumb flag, and it is impossible for us
|
||||
// to actually preserve things in a round-trip.
|
||||
if flag&VisNoSlash == VisNoSlash {
|
||||
continue
|
||||
}
|
||||
|
||||
// Round-trip testing.
|
||||
for _, test := range []string{
|
||||
"",
|
||||
"hello world",
|
||||
"THIS\\IS_A_TEST1234",
|
||||
"this.is.a.normal_string",
|
||||
"AC_Ra\u00edz_Certic\u00e1mara_S.A..pem",
|
||||
"NetLock_Arany_=Class_Gold=_F\u0151tan\u00fas\u00edtv\u00e1ny.pem",
|
||||
"T\u00dcB\u0130TAK_UEKAE_K\u00f6k_Sertifika_Hizmet_Sa\u011flay\u0131c\u0131s\u0131_-_S\u00fcr\u00fcm_3.pem",
|
||||
"hello world [ this string needs=enco ding! ]",
|
||||
"even \n more encoding necessary\a\a ",
|
||||
"\024 <-- some more weird characters --> \u4f60\u597d\uff0c\u4e16\u754c",
|
||||
"\\xff\\n double encoding is also great fun \\x",
|
||||
"AC_Ra\\M-C\\M--z_Certic\\M-C\\M-!mara_S.A..pem",
|
||||
"z^i3i$\u00d3\u008anqgh5/t\u00e5<86>\u00b2kzla\\e^lv\u00df\u0093nv\u00df\u00aea|3}\u00d8\u0088\u00d6\u0084",
|
||||
`z^i3i$\M-C\M^S\M-B\M^Jnqgh5/t\M-C\M-%<86>\M-B\M-2kzla\\e^lv\M-C\M^_\M-B\M^Snv\M-C\M^_\M-B\M-.a|3}\M-C\M^X\M-B\M^H\M-C\M^V\M-B\M^D`,
|
||||
"@?e1xs+.R_Kjo]7s8pgRP:*nXCE4{!c",
|
||||
"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb",
|
||||
`62_\M-C\M^F\M-C\M^F2\M-B\M-.\M-B\M-7m\M-C\M^[\M-C\M^Cr^\M-B\M-?p\M-C\M^Fu'q\M-C\M-;c2\M-C\M-0u\M-B\M-8\M-C\M^]\M-C\M-(v\M-C\M-?\M-B\M-0\M-C\M^\\M-C\M^B\M-C\M-53\M-C\M^[-k\M-C\M-2sd4\\p\M-C\M^Z\M-B\M-&\M-C\M^S\M-C\M-.a<\M-C\M-&s{\M-B\240p\M-C\M-0\M-C\M-?j\M-C\240\M-C\M-(\M-B\M-8\M-B\M-8\M-B\M-<\M-C\M-<b`,
|
||||
"\u9003\"9v1)T798|o;fly jnKX\u0489Be=",
|
||||
`\M-i\M^@\M^C"9v1)T798|o;fly jnKX\M-R\M^IBe=`,
|
||||
"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b",
|
||||
`'3Ze\M-T\M^N|\M-K\M^^l\M-Z\M^]u-Rpct4+Z5b={@_{b`,
|
||||
"1\u00c6\u00abTcz+Vda?)k1%\\\"P;`po`h",
|
||||
`1%C3%86%C2%ABTcz+Vda%3F)k1%25%5C%22P%3B%60po%60h`,
|
||||
} {
|
||||
enc, err := Vis(test, flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing vis(%q, %b): %s", test, flag, err)
|
||||
continue
|
||||
}
|
||||
dec, err := Unvis(enc, flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q, %b): %s", enc, flag, err)
|
||||
continue
|
||||
}
|
||||
if dec != test {
|
||||
t.Errorf("roundtrip failed: unvis(vis(%q, %b) = %q, %b) = %q", test, flag, enc, flag, dec)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteStrings(t *testing.T) {
|
||||
// It's important to make sure that we don't mess around with the layout of
|
||||
// bytes when doing a round-trip. Otherwise we risk outputting visually
|
||||
// identical but bit-stream non-identical strings (causing much confusion
|
||||
// when trying to access such files).
|
||||
|
||||
for _, test := range [][]byte{
|
||||
[]byte("This is a man in business suit levitating: \U0001f574"),
|
||||
{0x7f, 0x17, 0x01, 0x33},
|
||||
// TODO: Test arbitrary byte streams like the one below. Currently this
|
||||
// fails because Vis() is messing around with it (converting it
|
||||
// to a rune and spacing it out).
|
||||
//{'\xef', '\xae', 'h', '\077', 'k'},
|
||||
} {
|
||||
testString := string(test)
|
||||
enc, err := Vis(testString, DefaultVisFlags)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing vis(%q): %s", test, err)
|
||||
continue
|
||||
}
|
||||
dec, err := Unvis(enc, DefaultVisFlags)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q): %s", enc, err)
|
||||
continue
|
||||
}
|
||||
decBytes := []byte(dec)
|
||||
|
||||
if dec != testString {
|
||||
t.Errorf("roundtrip failed [string comparison]: unvis(vis(%q) = %q) = %q", test, enc, dec)
|
||||
}
|
||||
if !bytes.Equal(decBytes, test) {
|
||||
t.Errorf("roundtrip failed [byte comparison]: unvis(vis(%q) = %q) = %q", test, enc, dec)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* 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 (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnvisError(t *testing.T) {
|
||||
for _, test := range []string{
|
||||
// Octal escape codes allow you to specify invalid byte values.
|
||||
"\\777",
|
||||
"\\420\\322\\455",
|
||||
"\\652\\233",
|
||||
} {
|
||||
got, err := Unvis(test, DefaultVisFlags)
|
||||
if err == nil {
|
||||
t.Errorf("expected unvis(%q) to give an error, got %q", test, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnvisCStyleEscape(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"\\n\\v\\t\\s", "\n\v\t "},
|
||||
{"\\\\n\\tt", "\\n\tt"},
|
||||
{"\\b", "\b"},
|
||||
{"\\r\\b\\n", "\r\b\n"},
|
||||
{"\\a\\a\\b", "\x07\x07\b"},
|
||||
{"\\f\\s\\E", "\f \x1b"},
|
||||
// Hidden markers. They actually aren't generated by vis(3) but for
|
||||
// some reason, they're supported...
|
||||
{"test\\\ning", "testing"},
|
||||
{"test\\$\\$ing", "testing"},
|
||||
} {
|
||||
got, err := Unvis(test.input, DefaultVisFlags)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q): %q", test.input, err)
|
||||
continue
|
||||
}
|
||||
if got != test.expected {
|
||||
t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnvisMetaEscape(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"\\M^ ?\\^ ", "\x80?\x00"},
|
||||
{"\\M- ?\\^?", "\xa0?\x7f"},
|
||||
{"\\M-x butterfly\\M^?", "\xf8 butterfly\xff"},
|
||||
{"\\M^X steady-hand \\^& needle", "\x98 steady-hand \x06 needle"},
|
||||
// TODO: Add some more of these tests, but I need to have some
|
||||
// secondary source to verify these outputs properly.
|
||||
} {
|
||||
got, err := Unvis(test.input, DefaultVisFlags)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q): %q", test.input, err)
|
||||
continue
|
||||
}
|
||||
if got != test.expected {
|
||||
t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnvisOctalEscape(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"\\1", "\001"},
|
||||
{"\\01\\02\\3", "\001\002\003"},
|
||||
{"\\001\\023\\32", "\001\023\032"},
|
||||
{"this is a test\\0k1\\133", "this is a test\000k1\133"},
|
||||
{"\\170YET\\01another test\\1\\\\82", "\170YET\001another test\001\\82"},
|
||||
{"\\177MORE tests\\09a", "\177MORE tests\x009a"},
|
||||
{"\\\\710more\\1215testing", "\\710more\1215testing"},
|
||||
// Make sure that decoding unicode works properly, when it's been encoded as single bytes.
|
||||
{"\\360\\237\\225\\264", "\U0001f574"},
|
||||
{"T\\303\\234B\\304\\260TAK_UEKAE_K\\303\\266k_Sertifika_Hizmet_Sa\\304\\237lay\\304\\261c\\304\\261s\\304\\261_-_S\\303\\274r\\303\\274m_3.pem", "TÜBİTAK_UEKAE_Kök_Sertifika_Hizmet_Sağlayıcısı_-_Sürüm_3.pem"},
|
||||
// Some invalid characters...
|
||||
{"\\377\\2\\225\\264", "\xff\x02\x95\xb4"},
|
||||
} {
|
||||
got, err := Unvis(test.input, DefaultVisFlags)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q): %q", test.input, err)
|
||||
continue
|
||||
}
|
||||
if got != test.expected {
|
||||
t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnvisHexEscape(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"\\x01", "\x01"},
|
||||
{"\\x01\\x02\\x7a", "\x01\x02\x7a"},
|
||||
{"this is a test\\x13\\x52\\x6f", "this is a test\x13\x52\x6f"},
|
||||
{"\\x170YET\\x01a\\x22nother test\\x11", "\x170YET\x01a\x22nother test\x11"},
|
||||
{"\\\\x007more\\\\x215testing", "\\x007more\\x215testing"},
|
||||
// Make sure that decoding unicode works properly, when it's been encoded as single bytes.
|
||||
{"\\xf0\\x9f\\x95\\xb4", "\U0001f574"},
|
||||
{"T\\xc3\\x9cB\\xc4\\xb0TAK_UEKAE_K\\xc3\\xb6k_Sertifika_Hizmet_Sa\\xc4\\x9flay\\xc4\\xb1c\\xc4\\xb1s\\xc4\\xb1_-_S\\xc3\\xbcr\\xc3\\xbcm_3.pem", "TÜBİTAK_UEKAE_Kök_Sertifika_Hizmet_Sağlayıcısı_-_Sürüm_3.pem"},
|
||||
// Some invalid characters...
|
||||
{"\\xff\\x02\\x95\\xb4", "\xff\x02\x95\xb4"},
|
||||
} {
|
||||
got, err := Unvis(test.input, DefaultVisFlags)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q): %q", test.input, err)
|
||||
continue
|
||||
}
|
||||
if got != test.expected {
|
||||
t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnvisUnicode(t *testing.T) {
|
||||
// Ensure that unicode strings are not messed up by Unvis.
|
||||
for _, test := range []string{
|
||||
"",
|
||||
"this.is.a.normal_string",
|
||||
"AC_Raíz_Certicámara_S.A..pem",
|
||||
"NetLock_Arany_=Class_Gold=_Főtanúsítvány.pem",
|
||||
"TÜBİTAK_UEKAE_Kök_Sertifika_Hizmet_Sağlayıcısı_-_Sürüm_3.pem",
|
||||
} {
|
||||
got, err := Unvis(test, DefaultVisFlags)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error doing unvis(%q): %s", test, err)
|
||||
continue
|
||||
}
|
||||
if got != test {
|
||||
t.Errorf("expected %q to be unchanged, got %q", test, got)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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 (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVisUnchanged(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
flag VisFlag
|
||||
}{
|
||||
{"", DefaultVisFlags},
|
||||
{"helloworld", DefaultVisFlags},
|
||||
{"THIS_IS_A_TEST1234", DefaultVisFlags},
|
||||
{"SomeEncodingsAreCool", DefaultVisFlags},
|
||||
{"spaces are totally safe", DefaultVisFlags &^ VisSpace},
|
||||
{"tabs\tare\talso\tsafe!!", DefaultVisFlags &^ VisTab},
|
||||
{"just\a\atrustme\r\b\b!!", DefaultVisFlags | VisSafe},
|
||||
} {
|
||||
enc, err := Vis(test.input, test.flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error with %q: %s", test, err)
|
||||
}
|
||||
if enc != test.input {
|
||||
t.Errorf("expected encoding of %q (flag=%q) to be unchanged, got %q", test.input, test.flag, enc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisFlags(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
output string
|
||||
flag VisFlag
|
||||
}{
|
||||
// Default
|
||||
{"AC_Ra\u00edz_Certic\u00e1mara_S.A..pem", "AC_Ra\\M-C\\M--z_Certic\\M-C\\M-!mara_S.A..pem", 0},
|
||||
{"z^i3i$\u00d3\u008anqgh5/t\u00e5<86>\u00b2kzla\\e^lv\u00df\u0093nv\u00df\u00aea|3}\u00d8\u0088\u00d6\u0084", `z^i3i$\M-C\M^S\M-B\M^Jnqgh5/t\M-C\M-%<86>\M-B\M-2kzla\\e^lv\M-C\M^_\M-B\M^Snv\M-C\M^_\M-B\M-.a|3}\M-C\M^X\M-B\M^H\M-C\M^V\M-B\M^D`, 0},
|
||||
{"@?e1xs+.R_Kjo]7s8pgRP:*nXCE4{!c", "@?e1xs+.R_Kjo]7s8pgRP:*nXCE4{!c", 0},
|
||||
{"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_\M-C\M^F\M-C\M^F2\M-B\M-.\M-B\M-7m\M-C\M^[\M-C\M^Cr^\M-B\M-?p\M-C\M^Fu'q\M-C\M-;c2\M-C\M-0u\M-B\M-8\M-C\M^]\M-C\M-(v\M-C\M-?\M-B\M-0\M-C\M^\\M-C\M^B\M-C\M-53\M-C\M^[-k\M-C\M-2sd4\\p\M-C\M^Z\M-B\M-&\M-C\M^S\M-C\M-.a<\M-C\M-&s{\M-B\240p\M-C\M-0\M-C\M-?j\M-C\240\M-C\M-(\M-B\M-8\M-B\M-8\M-B\M-<\M-C\M-<b`, 0},
|
||||
{"\u9003\"9v1)T798|o;fly jnKX\u0489Be=", `\M-i\M^@\M^C"9v1)T798|o;fly jnKX\M-R\M^IBe=`, 0},
|
||||
// VisOctal
|
||||
{"", "", VisOctal},
|
||||
{"\022", "\\022", VisOctal},
|
||||
{"\n \t", "\\012\\040\t", VisNewline | VisSpace | VisOctal},
|
||||
{"\x12\f\a\n\v\b \U00012312", "\\022\\014\\007\n\\013\\010 \\360\\222\\214\\222", VisOctal},
|
||||
{"AC_Ra\u00edz_Certic\u00e1mara_S.A..pem", "AC_Ra\\303\\255z_Certic\\303\\241mara_S.A..pem", VisOctal},
|
||||
{"z^i3i$\u00d3\u008anqgh5/t\u00e5<86>\u00b2kzla\\e^lv\u00df\u0093nv\u00df\u00aea|3}\u00d8\u0088\u00d6\u0084", `z^i3i$\303\223\302\212nqgh5/t\303\245<86>\302\262kzla\\e^lv\303\237\302\223nv\303\237\302\256a|3}\303\230\302\210\303\226\302\204`, VisOctal},
|
||||
{"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_\303\206\303\2062\302\256\302\267m\303\233\303\203r^\302\277p\303\206u'q\303\273c2\303\260u\302\270\303\235\303\250v\303\277\302\260\303\234\303\202\303\2653\303\233-k\303\262sd4\\p\303\232\302\246\303\223\303\256a<\303\246s{\302\240p\303\260\303\277j\303\240\303\250\302\270\302\270\302\274\303\274b`, VisOctal},
|
||||
{"\u9003\"9v1)T798|o;fly jnKX\u0489Be=", `\351\200\203"9v1)T798|o;fly jnKX\322\211Be=`, VisOctal},
|
||||
// VisCStyle
|
||||
{"\x00 \f \a \n\v\b \r \t\r", "\\000 \\f \\a \n\\v\\b \\r \t\\r", VisCStyle},
|
||||
{"\t \n\v\b", "\\t \n\\v\\b", VisTab | VisCStyle},
|
||||
{"\n\v\t ", "\n\\v\t\\s\\s\\s", VisSpace | VisCStyle},
|
||||
{"\n \n ", "\\n \\n ", VisNewline | VisCStyle},
|
||||
{"z^i3i$\u00d3\u008anqgh5/t\u00e5<86>\u00b2kzla\\e^lv\u00df\u0093nv\u00df\u00aea|3}\u00d8\u0088\u00d6\u0084", `z^i3i$\M-C\M^S\M-B\M^Jnqgh5/t\M-C\M-%<86>\M-B\M-2kzla\\e^lv\M-C\M^_\M-B\M^Snv\M-C\M^_\M-B\M-.a|3}\M-C\M^X\M-B\M^H\M-C\M^V\M-B\M^D`, VisCStyle},
|
||||
{"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_\M-C\M^F\M-C\M^F2\M-B\M-.\M-B\M-7m\M-C\M^[\M-C\M^Cr^\M-B\M-?p\M-C\M^Fu'q\M-C\M-;c2\M-C\M-0u\M-B\M-8\M-C\M^]\M-C\M-(v\M-C\M-?\M-B\M-0\M-C\M^\\M-C\M^B\M-C\M-53\M-C\M^[-k\M-C\M-2sd4\\p\M-C\M^Z\M-B\M-&\M-C\M^S\M-C\M-.a<\M-C\M-&s{\M-B\240p\M-C\M-0\M-C\M-?j\M-C\240\M-C\M-(\M-B\M-8\M-B\M-8\M-B\M-<\M-C\M-<b`, VisCStyle},
|
||||
{"\u9003\"9v1)T798|o;fly jnKX\u0489Be=", `\M-i\M^@\M^C"9v1)T798|o;fly\sjnKX\M-R\M^IBe=`, VisCStyle | VisSpace},
|
||||
// VisSpace
|
||||
{" ", `\040\040`, VisSpace},
|
||||
{"\t \t", "\t\\040\t", VisSpace},
|
||||
{"\\040 plenty of characters here ", `\\040\040\040\040plenty\040of\040characters\040here\040\040\040`, VisSpace},
|
||||
{"Js9L\u00cd\u00b2o?4824y'$|P}FIr%mW /KL9$]~", `Js9L\M-C\M^M\M-B\M-2o?4824y'$|P}FIr%mW\040/KL9$]~`, VisWhite},
|
||||
{"1\u00c6\u00abTcz+Vda?)k1%\\\"P;`po`h", `1\M-C\M^F\M-B\M-+Tcz+Vda?)k1%\\"P;` + "`po`" + `h`, VisWhite},
|
||||
{"\u9003\"9v1)T798|o;fly jnKX\u0489Be=", `\M-i\M^@\M^C"9v1)T798|o;fly\040jnKX\M-R\M^IBe=`, VisSpace},
|
||||
// VisTab
|
||||
{"\t \v", "\\^I \\^K", VisTab},
|
||||
{"\t \v", "\\011 \\013", VisTab | VisOctal},
|
||||
// VisNewline
|
||||
{"\t\n \v\r\n", "\t\\^J \\^K\\^M\\^J", VisNewline},
|
||||
{"\t\n \v\r\n", "\t\\012 \\013\\015\\012", VisNewline | VisOctal},
|
||||
// VisSafe
|
||||
// VisHTTPStyle
|
||||
{"\x12\f\a\n\v\b \U00012312", `%12%0C%07%0A%0B%08%20%20%F0%92%8C%92`, VisHTTPStyle},
|
||||
{"1\u00c6\u00abTcz+Vda?)k1%\\\"P;`po`h", `1%C3%86%C2%ABTcz+Vda%3F)k1%25%5C%22P%3B%60po%60h`, VisHTTPStyle},
|
||||
{"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_%C3%86%C3%862%C2%AE%C2%B7m%C3%9B%C3%83r%5E%C2%BFp%C3%86u'q%C3%BBc2%C3%B0u%C2%B8%C3%9D%C3%A8v%C3%BF%C2%B0%C3%9C%C3%82%C3%B53%C3%9B-k%C3%B2sd4%5Cp%C3%9A%C2%A6%C3%93%C3%AEa%3C%C3%A6s%7B%C2%A0p%C3%B0%C3%BFj%C3%A0%C3%A8%C2%B8%C2%B8%C2%BC%C3%BCb`, VisHTTPStyle},
|
||||
{"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze%D4%8E%7C%CB%9El%DA%9Du-Rpct4+Z5b%3D%7B%40_%7Bb`, VisHTTPStyle},
|
||||
// VisGlob
|
||||
{"cat /proc/**/status | grep '[pid]' ;; # cool code here", `cat /proc/\052\052/status | grep '\133pid]' ;; \043 cool code here`, VisGlob},
|
||||
{"@?e1xs+.R_Kjo]7s8pgRP:*nXCE4{!c", `@\077e1xs+.R_Kjo]7s8pgRP:\052nXCE4{!c`, VisGlob},
|
||||
{"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_\M-C\M^F\M-C\M^F2\M-B\M-.\M-B\M-7m\M-C\M^[\M-C\M^Cr^\M-B\M-?p\M-C\M^Fu'q\M-C\M-;c2\M-C\M-0u\M-B\M-8\M-C\M^]\M-C\M-(v\M-C\M-?\M-B\M-0\M-C\M^\\M-C\M^B\M-C\M-53\M-C\M^[-k\M-C\M-2sd4\\p\M-C\M^Z\M-B\M-&\M-C\M^S\M-C\M-.a<\M-C\M-&s{\M-B\240p\M-C\M-0\M-C\M-?j\M-C\240\M-C\M-(\M-B\M-8\M-B\M-8\M-B\M-<\M-C\M-<b`, VisGlob},
|
||||
{"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_\303\206\303\2062\302\256\302\267m\303\233\303\203r^\302\277p\303\206u'q\303\273c2\303\260u\302\270\303\235\303\250v\303\277\302\260\303\234\303\202\303\2653\303\233-k\303\262sd4\\p\303\232\302\246\303\223\303\256a<\303\246s{\302\240p\303\260\303\277j\303\240\303\250\302\270\302\270\302\274\303\274b`, VisGlob | VisOctal},
|
||||
{"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze\M-T\M^N|\M-K\M^^l\M-Z\M^]u-Rpct4+Z5b={@_{b`, VisGlob},
|
||||
{"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze\324\216|\313\236l\332\235u-Rpct4+Z5b={@_{b`, VisGlob | VisOctal},
|
||||
} {
|
||||
enc, err := Vis(test.input, test.flag)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error with %q: %s", test, err)
|
||||
}
|
||||
if enc != test.output {
|
||||
t.Errorf("expected vis(%q, flag=%b) = %q, got %q", test.input, test.flag, test.output, enc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisChanged(t *testing.T) {
|
||||
for _, test := range []string{
|
||||
"hello world",
|
||||
"THIS\\IS_A_TEST1234",
|
||||
"AC_Ra\u00edz_Certic\u00e1mara_S.A..pem",
|
||||
} {
|
||||
enc, err := Vis(test, DefaultVisFlags)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error with %q: %s", test, err)
|
||||
}
|
||||
if enc == test {
|
||||
t.Errorf("expected encoding of %q to be changed", test)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
113
tar.go
113
tar.go
|
@ -5,10 +5,12 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"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
|
||||
|
@ -17,11 +19,15 @@ type Streamer interface {
|
|||
Hierarchy() (*DirectoryHierarchy, error)
|
||||
}
|
||||
|
||||
var tarDefaultSetKeywords = []string{"type=file", "flags=none", "mode=0664"}
|
||||
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, keywords []string) Streamer {
|
||||
func NewTarStreamer(r io.Reader, excludes []ExcludeFunc, keywords []Keyword) Streamer {
|
||||
pR, pW := io.Pipe()
|
||||
ts := &tarStream{
|
||||
pipeReader: pR,
|
||||
|
@ -31,6 +37,7 @@ func NewTarStreamer(r io.Reader, keywords []string) Streamer {
|
|||
tarReader: tar.NewReader(pR),
|
||||
keywords: keywords,
|
||||
hardlinks: map[string][]string{},
|
||||
excludes: excludes,
|
||||
}
|
||||
|
||||
go ts.readHeaders()
|
||||
|
@ -45,17 +52,18 @@ type tarStream struct {
|
|||
pipeWriter *io.PipeWriter
|
||||
teeReader io.Reader
|
||||
tarReader *tar.Reader
|
||||
keywords []string
|
||||
keywords []Keyword
|
||||
excludes []ExcludeFunc
|
||||
err error
|
||||
}
|
||||
|
||||
func (ts *tarStream) readHeaders() {
|
||||
// remove "time" keyword
|
||||
notimekws := []string{}
|
||||
notimekws := []Keyword{}
|
||||
for _, kw := range ts.keywords {
|
||||
if !inSlice(kw, notimekws) {
|
||||
if !InKeywordSlice(kw, notimekws) {
|
||||
if kw == "time" {
|
||||
if !inSlice("tar_time", ts.keywords) {
|
||||
if !InKeywordSlice("tar_time", ts.keywords) {
|
||||
notimekws = append(notimekws, "tar_time")
|
||||
}
|
||||
} else {
|
||||
|
@ -74,19 +82,32 @@ func (ts *tarStream) readHeaders() {
|
|||
Type: CommentType,
|
||||
},
|
||||
Set: nil,
|
||||
Keywords: []string{"type=dir"},
|
||||
Keywords: []KeyVal{"type=dir"},
|
||||
}
|
||||
metadataEntries := signatureEntries("<user specified tar archive>")
|
||||
for _, e := range metadataEntries {
|
||||
// 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.
|
||||
|
@ -109,7 +130,7 @@ func (ts *tarStream) readHeaders() {
|
|||
return
|
||||
}
|
||||
// Alright, it's either file or directory
|
||||
encodedName, err := Vis(filepath.Base(hdr.Name))
|
||||
encodedName, err := govis.Vis(filepath.Base(hdr.Name), DefaultVisFlags)
|
||||
if err != nil {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFile.Name())
|
||||
|
@ -123,16 +144,16 @@ func (ts *tarStream) readHeaders() {
|
|||
|
||||
// Keep track of which files are hardlinks so we can resolve them later
|
||||
if hdr.Typeflag == tar.TypeLink {
|
||||
linkFunc := KeywordFuncs["link"]
|
||||
kv, err := linkFunc(hdr.Name, hdr.FileInfo(), nil)
|
||||
keyFunc := KeywordFuncs["link"]
|
||||
kvs, err := keyFunc(hdr.Name, hdr.FileInfo(), nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
logrus.Warn(err)
|
||||
break // XXX is breaking an okay thing to do here?
|
||||
}
|
||||
linkname, err := Unvis(KeyVal(kv).Value())
|
||||
linkname, err := govis.Unvis(KeyVal(kvs[0]).Value(), DefaultVisFlags)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
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}
|
||||
|
@ -143,19 +164,19 @@ func (ts *tarStream) readHeaders() {
|
|||
|
||||
// now collect keywords on the file
|
||||
for _, keyword := range ts.keywords {
|
||||
if keyFunc, ok := KeywordFuncs[keyword]; ok {
|
||||
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
|
||||
}
|
||||
val, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile)
|
||||
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 val != "" {
|
||||
e.Keywords = append(e.Keywords, val)
|
||||
if len(kvs) > 0 && kvs[0] != "" {
|
||||
e.Keywords = append(e.Keywords, kvs[0])
|
||||
}
|
||||
|
||||
// don't forget to reset the reader
|
||||
|
@ -175,13 +196,15 @@ func (ts *tarStream) readHeaders() {
|
|||
Type: SpecialType,
|
||||
}
|
||||
for _, setKW := range SetKeywords {
|
||||
if keyFunc, ok := KeywordFuncs[setKW]; ok {
|
||||
val, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile)
|
||||
if keyFunc, ok := KeywordFuncs[setKW.Prefix()]; ok {
|
||||
kvs, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile)
|
||||
if err != nil {
|
||||
ts.setErr(err)
|
||||
}
|
||||
if val != "" {
|
||||
s.Keywords = append(s.Keywords, val)
|
||||
for _, kv := range kvs {
|
||||
if kv != "" {
|
||||
s.Keywords = append(s.Keywords, kv)
|
||||
}
|
||||
}
|
||||
if _, err := tmpFile.Seek(0, 0); err != nil {
|
||||
tmpFile.Close()
|
||||
|
@ -229,7 +252,7 @@ func populateTree(root, e *Entry, hdr *tar.Header) error {
|
|||
dirNames := strings.Split(wd, "/")
|
||||
parent := root
|
||||
for _, name := range dirNames[:] {
|
||||
encoded, err := Vis(name)
|
||||
encoded, err := govis.Vis(name, DefaultVisFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -242,7 +265,7 @@ func populateTree(root, e *Entry, hdr *tar.Header) error {
|
|||
Name: encoded,
|
||||
Type: RelativeType,
|
||||
Parent: parent,
|
||||
Keywords: []string{"type=dir"}, // temp data
|
||||
Keywords: []KeyVal{"type=dir"}, // temp data
|
||||
Set: nil, // temp data
|
||||
}
|
||||
pathname, err := newEntry.Path()
|
||||
|
@ -276,7 +299,7 @@ func populateTree(root, e *Entry, hdr *tar.Header) error {
|
|||
// 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 []string) {
|
||||
func flatten(root *Entry, creator *dhCreator, keywords []Keyword) {
|
||||
if root == nil || creator == nil {
|
||||
return
|
||||
}
|
||||
|
@ -292,18 +315,19 @@ func flatten(root *Entry, creator *dhCreator, keywords []string) {
|
|||
|
||||
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: keywordSelector(append(tarDefaultSetKeywords, root.Set.Keywords...), keywords),
|
||||
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 !inSlice(k, creator.curSet.Keywords) {
|
||||
if !inKeyValSlice(k, creator.curSet.Keywords) {
|
||||
needNewSet = true
|
||||
break
|
||||
}
|
||||
|
@ -313,7 +337,7 @@ func flatten(root *Entry, creator *dhCreator, keywords []string) {
|
|||
Name: "/set",
|
||||
Type: SpecialType,
|
||||
Pos: len(creator.DH.Entries),
|
||||
Keywords: keywordSelector(append(tarDefaultSetKeywords, root.Set.Keywords...), keywords),
|
||||
Keywords: consolidatedKeys,
|
||||
}
|
||||
creator.DH.Entries = append(creator.DH.Entries, *creator.curSet)
|
||||
}
|
||||
|
@ -331,7 +355,7 @@ func flatten(root *Entry, creator *dhCreator, keywords []string) {
|
|||
}
|
||||
root.Set = creator.curSet
|
||||
if creator.curSet != nil {
|
||||
root.Keywords = setDifference(root.Keywords, creator.curSet.Keywords)
|
||||
root.Keywords = keyValDifference(root.Keywords, creator.curSet.Keywords)
|
||||
}
|
||||
root.Pos = len(creator.DH.Entries)
|
||||
creator.DH.Entries = append(creator.DH.Entries, *root)
|
||||
|
@ -361,7 +385,7 @@ func resolveHardlinks(root *Entry, hardlinks map[string][]string, countlinks boo
|
|||
if seen, ok := originals[base]; !ok {
|
||||
basefile = root.Find(base)
|
||||
if basefile == nil {
|
||||
log.Printf("%s does not exist in this tree\n", base)
|
||||
logrus.Printf("%s does not exist in this tree\n", base)
|
||||
continue
|
||||
}
|
||||
originals[base] = basefile
|
||||
|
@ -371,16 +395,16 @@ func resolveHardlinks(root *Entry, hardlinks map[string][]string, countlinks boo
|
|||
for _, link := range links {
|
||||
linkfile := root.Find(link)
|
||||
if linkfile == nil {
|
||||
log.Printf("%s does not exist in this tree\n", link)
|
||||
logrus.Printf("%s does not exist in this tree\n", link)
|
||||
continue
|
||||
}
|
||||
linkfile.Keywords = basefile.Keywords
|
||||
if countlinks {
|
||||
linkfile.Keywords = append(linkfile.Keywords, fmt.Sprintf("nlink=%d", len(links)+1))
|
||||
linkfile.Keywords = append(linkfile.Keywords, KeyVal(fmt.Sprintf("nlink=%d", len(links)+1)))
|
||||
}
|
||||
}
|
||||
if countlinks {
|
||||
basefile.Keywords = append(basefile.Keywords, fmt.Sprintf("nlink=%d", len(links)+1))
|
||||
basefile.Keywords = append(basefile.Keywords, KeyVal(fmt.Sprintf("nlink=%d", len(links)+1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -410,19 +434,6 @@ func filter(root *Entry, p func(*Entry) bool) []Entry {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setDifference(this, that []string) []string {
|
||||
if len(this) == 0 {
|
||||
return that
|
||||
}
|
||||
diff := []string{}
|
||||
for _, kv := range this {
|
||||
if !inSlice(kv, that) {
|
||||
diff = append(diff, kv)
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func (ts *tarStream) setErr(err error) {
|
||||
ts.err = err
|
||||
}
|
||||
|
@ -444,7 +455,7 @@ func (ts *tarStream) Hierarchy() (*DirectoryHierarchy, error) {
|
|||
if ts.root == nil {
|
||||
return nil, fmt.Errorf("root Entry not found, nothing to flatten")
|
||||
}
|
||||
resolveHardlinks(ts.root, ts.hardlinks, inSlice("nlink", ts.keywords))
|
||||
resolveHardlinks(ts.root, ts.hardlinks, InKeywordSlice(Keyword("nlink"), ts.keywords))
|
||||
flatten(ts.root, &ts.creator, ts.keywords)
|
||||
return ts.creator.DH, nil
|
||||
}
|
||||
|
|
72
tar_test.go
72
tar_test.go
|
@ -6,6 +6,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -16,7 +17,7 @@ func ExampleStreamer() {
|
|||
if err != nil {
|
||||
// handle error ...
|
||||
}
|
||||
str := NewTarStreamer(fh, nil)
|
||||
str := NewTarStreamer(fh, nil, nil)
|
||||
if err := extractTar("/tmp/dir", str); err != nil {
|
||||
// handle error ...
|
||||
}
|
||||
|
@ -26,7 +27,7 @@ func ExampleStreamer() {
|
|||
// handle error ...
|
||||
}
|
||||
|
||||
res, err := Check("/tmp/dir/", dh, nil)
|
||||
res, err := Check("/tmp/dir/", dh, nil, nil)
|
||||
if err != nil {
|
||||
// handle error ...
|
||||
}
|
||||
|
@ -59,7 +60,7 @@ func TestTar(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
str := NewTarStreamer(fh, append(DefaultKeywords, "sha1"))
|
||||
str := NewTarStreamer(fh, nil, append(DefaultKeywords, "sha1"))
|
||||
|
||||
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
|
@ -78,11 +79,16 @@ func TestTar(t *testing.T) {
|
|||
t.Fatal("expected a DirectoryHierarchy struct, but got nil")
|
||||
}
|
||||
|
||||
fh, err = os.Create("./testdata/test.mtree")
|
||||
testDir, present := os.LookupEnv("MTREE_TESTDIR")
|
||||
if present == false {
|
||||
testDir = "."
|
||||
}
|
||||
testPath := filepath.Join(testDir, "test.mtree")
|
||||
fh, err = os.Create(testPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove("./testdata/test.mtree")
|
||||
defer os.Remove(testPath)
|
||||
|
||||
// put output of tar walk into test.mtree
|
||||
_, err = tdh.WriteTo(fh)
|
||||
|
@ -92,7 +98,7 @@ func TestTar(t *testing.T) {
|
|||
fh.Close()
|
||||
|
||||
// now simulate gomtree -T testdata/test.tar -f testdata/test.mtree
|
||||
fh, err = os.Open("./testdata/test.mtree")
|
||||
fh, err = os.Open(testPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -103,7 +109,7 @@ func TestTar(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := TarCheck(tdh, dh, append(DefaultKeywords, "sha1"))
|
||||
res, err := Compare(tdh, dh, append(DefaultKeywords, "sha1"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -128,7 +134,7 @@ func TestArchiveCreation(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
str := NewTarStreamer(fh, []string{"sha1"})
|
||||
str := NewTarStreamer(fh, nil, []Keyword{"sha1"})
|
||||
|
||||
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
|
@ -145,7 +151,7 @@ func TestArchiveCreation(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test the tar manifest against the actual directory
|
||||
res, err := Check("./testdata/collection", tdh, []string{"sha1"})
|
||||
res, err := Check("./testdata/collection", tdh, []Keyword{"sha1"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -158,7 +164,7 @@ func TestArchiveCreation(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test the tar manifest against itself
|
||||
res, err = TarCheck(tdh, tdh, []string{"sha1"})
|
||||
res, err = Compare(tdh, tdh, []Keyword{"sha1"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -170,11 +176,11 @@ func TestArchiveCreation(t *testing.T) {
|
|||
}
|
||||
|
||||
// Validate the directory manifest against the archive
|
||||
dh, err := Walk("./testdata/collection", nil, []string{"sha1"})
|
||||
dh, err := Walk("./testdata/collection", nil, []Keyword{"sha1"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res, err = TarCheck(tdh, dh, []string{"sha1"})
|
||||
res, err = Compare(tdh, dh, []Keyword{"sha1"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -196,7 +202,7 @@ func TestTreeTraversal(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
str := NewTarStreamer(fh, DefaultTarKeywords)
|
||||
str := NewTarStreamer(fh, nil, DefaultTarKeywords)
|
||||
|
||||
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
|
@ -212,7 +218,7 @@ func TestTreeTraversal(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := TarCheck(tdh, tdh, []string{"sha1"})
|
||||
res, err := Compare(tdh, tdh, []Keyword{"sha1"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -224,7 +230,7 @@ func TestTreeTraversal(t *testing.T) {
|
|||
}
|
||||
|
||||
// top-level "." directory will contain contents of traversal.tar
|
||||
res, err = Check("./testdata/.", tdh, []string{"sha1"})
|
||||
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -249,7 +255,7 @@ func TestTreeTraversal(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
str = NewTarStreamer(fh, DefaultTarKeywords)
|
||||
str = NewTarStreamer(fh, nil, DefaultTarKeywords)
|
||||
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -262,7 +268,7 @@ func TestTreeTraversal(t *testing.T) {
|
|||
}
|
||||
|
||||
// Implied top-level "." directory will contain the contents of singlefile.tar
|
||||
res, err = Check("./testdata/.", tdh, []string{"sha1"})
|
||||
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -287,7 +293,7 @@ func TestHardlinks(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
str := NewTarStreamer(fh, append(DefaultTarKeywords, "nlink"))
|
||||
str := NewTarStreamer(fh, nil, append(DefaultTarKeywords, "nlink"))
|
||||
|
||||
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
|
@ -369,3 +375,33 @@ func makeTarStream(ff []fakeFile) ([]byte, error) {
|
|||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func TestArchiveExcludeNonDirectory(t *testing.T) {
|
||||
fh, err := os.Open("./testdata/collection.tar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
str := NewTarStreamer(fh, []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"})
|
||||
|
||||
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := str.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fh.Close()
|
||||
// get DirectoryHierarcy struct from walking the tar archive
|
||||
tdh, err := str.Hierarchy()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := range tdh.Entries {
|
||||
for _, keyval := range tdh.Entries[i].AllKeys() {
|
||||
if tdh.Entries[i].Type == FullType || tdh.Entries[i].Type == RelativeType {
|
||||
if keyval.Keyword() == "type" && keyval.Value() != "dir" {
|
||||
t.Errorf("expected only directories, but %q is a %q", tdh.Entries[i].Name, keyval.Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
green := color.New(color.FgGreen).SprintFunc()
|
||||
red := color.New(color.FgRed).SprintFunc()
|
||||
|
||||
failed := 0
|
||||
for _, arg := range flag.Args() {
|
||||
cmd := exec.Command("bash", arg)
|
||||
if os.Getenv("TMPDIR") != "" {
|
||||
cmd.Env = append(cmd.Env, "TMPDIR="+os.Getenv("TMPDIR"))
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
failed++
|
||||
fmt.Fprintf(os.Stderr, red("FAILED: %s\n"), arg)
|
||||
}
|
||||
}
|
||||
if failed > 0 {
|
||||
fmt.Fprintf(os.Stderr, red("%d FAILED tests\n"), failed)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, green("SUCCESS: no cli tests failed\n"))
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
set -e
|
||||
|
||||
name=$(basename $0)
|
||||
root=$1
|
||||
gomtree=$(readlink -f ${root}/gomtree)
|
||||
t=$(mktemp -d /tmp/go-mtree.XXXXXX)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
t=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
|
||||
echo "[${name}] Running in ${t}"
|
||||
# This test is for basic running check of manifest, and check against tar and file system
|
||||
|
@ -12,33 +12,33 @@ echo "[${name}] Running in ${t}"
|
|||
|
||||
pushd ${root}
|
||||
|
||||
git archive --format=tar HEAD^{tree} . > ${t}/${name}.tar
|
||||
git archive --format=tar -o "${t}/${name}.tar" HEAD^{tree}
|
||||
|
||||
prev_umask=$(umask)
|
||||
umask 0 # this is so the tar command can set the mode's properly
|
||||
mkdir -p ${t}/extract
|
||||
tar -C ${t}/extract/ -xf ${t}/${name}.tar
|
||||
tar -C ${t}/extract/ -xf "${t}/${name}.tar"
|
||||
umask ${prev_umask}
|
||||
|
||||
# create manifest from tar
|
||||
${gomtree} -K sha256digest -c -T ${t}/${name}.tar > ${t}/${name}.mtree
|
||||
${gomtree} -K sha256digest -c -T "${t}/${name}.tar" > "${t}/${name}.mtree"
|
||||
|
||||
# check tar-manifest against the tar
|
||||
${gomtree} -f ${t}/${name}.mtree -T ${t}/${name}.tar
|
||||
${gomtree} -f ${t}/${name}.mtree -T "${t}/${name}.tar"
|
||||
|
||||
# check tar-manifest against the filesystem
|
||||
# git archive makes the uid/gid as 0, so don't check them for this test
|
||||
${gomtree} -k size,sha256digest,mode,type -f ${t}/${name}.mtree -p ${t}/extract/
|
||||
${gomtree} -k size,sha256digest,mode,type -f "${t}/${name}.mtree" -p ${t}/extract/
|
||||
|
||||
# create a manifest from filesystem
|
||||
${gomtree} -K sha256digest -c -p ${t}/extract/ > ${t}/${name}.mtree
|
||||
${gomtree} -K sha256digest -c -p "${t}/extract/" > "${t}/${name}.mtree"
|
||||
|
||||
# check filesystem-manifest against the filesystem
|
||||
${gomtree} -f ${t}/${name}.mtree -p ${t}/extract/
|
||||
${gomtree} -f "${t}/${name}.mtree" -p "${t}/extract/"
|
||||
|
||||
# check filesystem-manifest against the tar
|
||||
# git archive makes the uid/gid as 0, so don't check them for this test
|
||||
${gomtree} -k size,sha256digest,mode,type -f ${t}/${name}.mtree -T ${t}/${name}.tar
|
||||
${gomtree} -k size,sha256digest,mode,type -f "${t}/${name}.mtree" -T "${t}/${name}.tar"
|
||||
|
||||
popd
|
||||
rm -rf ${t}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
set -e
|
||||
|
||||
name=$(basename $0)
|
||||
root=$1
|
||||
gomtree=$(readlink -f ${root}/gomtree)
|
||||
t=$(mktemp -d /tmp/go-mtree.XXXXXX)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
t=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
|
||||
echo "[${name}] Running in ${t}"
|
||||
# This test is for basic running check of manifest, and check against tar and file system
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
name=$(basename $0)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
t=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
|
||||
setfattr -n user.has.xattrs -v "true" "${t}" || exit 0
|
||||
|
||||
echo "[${name}] Running in ${t}"
|
||||
|
||||
mkdir "${t}/dir"
|
||||
touch "${t}/dir/file"
|
||||
|
||||
setfattr -n user.mtree.testing -v "apples and=bananas" "${t}/dir/file"
|
||||
$gomtree -c -k "sha256digest,xattrs" -p ${t}/dir > ${t}/${name}.mtree
|
||||
|
||||
setfattr -n user.mtree.testing -v "bananas and lemons" "${t}/dir/file"
|
||||
! $gomtree -p ${t}/dir -f ${t}/${name}.mtree
|
||||
|
||||
setfattr -x user.mtree.testing "${t}/dir/file"
|
||||
! $gomtree -p ${t}/dir -f ${t}/${name}.mtree
|
||||
|
||||
setfattr -n user.mtree.testing -v "apples and=bananas" "${t}/dir/file"
|
||||
setfattr -n user.mtree.another -v "another a=b" "${t}/dir/file"
|
||||
! $gomtree -p ${t}/dir -f ${t}/${name}.mtree
|
||||
|
||||
setfattr -n user.mtree.testing -v "apples and=bananas" "${t}/dir/file"
|
||||
setfattr -x user.mtree.another "${t}/dir/file"
|
||||
$gomtree -p ${t}/dir -f ${t}/${name}.mtree
|
||||
|
||||
rm -fr ${t}
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
#set -x
|
||||
|
||||
name=$(basename $0)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
t=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
|
||||
echo "[${name}] Running in ${t}"
|
||||
|
||||
pushd ${root}
|
||||
git archive --format=tar HEAD^{tree} . > ${t}/${name}.tar
|
||||
mkdir -p ${t}/extract
|
||||
tar -C ${t}/extract/ -xf ${t}/${name}.tar
|
||||
|
||||
## This is a checking that keyword synonyms are respected
|
||||
${gomtree} -k sha1digest -c -p ${t}/extract/ > ${t}/${name}.mtree
|
||||
${gomtree} -k sha1 -f ${t}/${name}.mtree -p ${t}/extract/
|
||||
${gomtree} -k sha1 -c -p ${t}/extract/ > ${t}/${name}.mtree
|
||||
${gomtree} -k sha1digest -f ${t}/${name}.mtree -p ${t}/extract/
|
||||
|
||||
popd
|
||||
rm -rf ${t}
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
name=$(basename $0)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
t=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
|
||||
echo "[${name}] Running in ${t}"
|
||||
|
||||
pushd ${root}
|
||||
mkdir -p ${t}/extract
|
||||
git archive --format=tar HEAD^{tree} . | tar -C ${t}/extract/ -x
|
||||
|
||||
${gomtree} -k sha1digest -c -p ${t}/extract/ > ${t}/${name}.mtree
|
||||
${gomtree} -f ${t}/${name}.mtree -k md5digest -p ${t}/extract/
|
||||
|
||||
popd
|
||||
rm -rf ${t}
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
name=$(basename $0)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
t=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
|
||||
echo "[${name}] Running in ${t}"
|
||||
# This test is for basic running check of manifest, and check against tar and file system
|
||||
#
|
||||
|
||||
pushd ${root}
|
||||
|
||||
git archive --format=tar HEAD^{tree} . > ${t}/${name}.tar
|
||||
|
||||
prev_umask=$(umask)
|
||||
umask 0 # this is so the tar command can set the mode's properly
|
||||
mkdir -p ${t}/extract
|
||||
tar -C ${t}/extract/ -xf ${t}/${name}.tar
|
||||
umask ${prev_umask}
|
||||
|
||||
# create manifest from tar, ignoring non directories
|
||||
${gomtree} -d -c -k type -T ${t}/${name}.tar > ${t}/${name}.mtree
|
||||
|
||||
# check tar-manifest against the tar
|
||||
${gomtree} -d -f ${t}/${name}.mtree -T ${t}/${name}.tar
|
||||
|
||||
# check filesystem-manifest against the filesystem
|
||||
${gomtree} -f ${t}/${name}.mtree -p ${t}/extract/
|
||||
|
||||
popd
|
||||
rm -rf ${t}
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
name=$(basename $0)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
left=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
right=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
|
||||
echo "[${name}] Running in ${left} and ${right}"
|
||||
|
||||
touch ${left}/one
|
||||
touch ${left}/two
|
||||
cp -a ${left}/one ${right}/
|
||||
|
||||
$gomtree -K "sha256digest" -p ${left} -c > /tmp/left.mtree
|
||||
$gomtree -k "sha256digest" -p ${right} -f /tmp/left.mtree
|
||||
rm -fr ${left} ${right}
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
name=$(basename $0)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
t=$(mktemp -d /tmp/go-mtree.XXXXXX)
|
||||
|
||||
echo "[${name}] Running in ${t}"
|
||||
|
||||
pushd ${root}
|
||||
mkdir -p ${t}/extract
|
||||
git archive --format=tar HEAD^{tree} . | tar -C ${t}/extract/ -x
|
||||
|
||||
${gomtree} -K sha256digest -c -p ${t}/extract/ > ${t}/${name}.mtree
|
||||
|
||||
## This is a use-case for checking a directory, but by reading the manifest from stdin
|
||||
## since the `-f` flag is not provided.
|
||||
cat ${t}/${name}.mtree | ${gomtree} -p ${t}/extract/
|
||||
|
||||
popd
|
||||
rm -rf ${t}
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
name=$(basename $0)
|
||||
root="$(dirname $(dirname $(dirname $0)))"
|
||||
gomtree=$(go run ${root}/test/realpath.go ${root}/gomtree)
|
||||
t=$(mktemp -d -t go-mtree.XXXXXX)
|
||||
|
||||
echo "[${name}] Running in ${t}"
|
||||
|
||||
pushd ${root}
|
||||
|
||||
# Create some unicode files.
|
||||
mkdir ${t}/root
|
||||
echo "some data" > "${t}/root/$(printf 'this file has \u042a some unicode !!')"
|
||||
echo "more data" > "${t}/root/$(printf 'even more \x07 unicode \ua4ff characters \udead\ubeef\ucafe')"
|
||||
mkdir -p "${t}/root/$(printf '\024 <-- some more weird characters --> \u4f60\u597d\uff0c\u4e16\u754c')"
|
||||
ln -s "$(printf '62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u"q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db')" "${t}/root/$(printf 'k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb')"
|
||||
printf 'some lovely data 62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r' > "${t}/root/$(printf 'T\u00dcB\u0130TAK_UEKAE_K\u00f6k_Sertifika_Hizmet_Sa\u011flay\u0131c\u0131s\u0131_-_S\u00fcr\u00fcm_3.pem')"
|
||||
|
||||
# Create manifest and check it against the same root.
|
||||
${gomtree} -k uid,gid,size,type,link,nlink,sha256digest -c -p ${t}/root > ${t}/root.mtree
|
||||
${gomtree} -k uid,gid,size,type,link,nlink,sha256digest -f ${t}/root.mtree -p ${t}/root
|
||||
|
||||
# Modify it and make sure that it successfully figures out what changed.
|
||||
echo "othe data" > "${t}/root/$(printf 'this file has \u042a some unicode !!')"
|
||||
! ${gomtree} -k uid,gid,size,type,link,nlink,sha256digest -f ${t}/root.mtree -p ${t}/root
|
||||
|
||||
echo "some data" > "${t}/root/$(printf 'this file has \u042a some unicode !!')"
|
||||
${gomtree} -k uid,gid,size,type,link,nlink,sha256digest -f ${t}/root.mtree -p ${t}/root
|
||||
|
||||
popd
|
||||
rm -rf ${t}
|
|
@ -0,0 +1,3 @@
|
|||
package test
|
||||
|
||||
// place holder for test helpers
|
|
@ -0,0 +1,22 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
for _, arg := range flag.Args() {
|
||||
path, err := filepath.Abs(arg)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%s", path)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,800 @@
|
|||
. type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513088963.690901011
|
||||
.git type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513089565.023874273
|
||||
.git/FETCH_HEAD type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513089402.952611958 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
.git/HEAD type=file mode=0664 size=23 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.419901036 sha512256digest=459551401449a0555a2c1a1ef57f17eab8b70afccd1ae2fb089e1fe216d92041
|
||||
.git/branches type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.793867723
|
||||
.git/config type=file mode=0664 size=259 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.420901030 sha512256digest=18c4f2a977a0a6fb1039082eea88c34a7722f711f9bb40141eeae811077511ea
|
||||
.git/description type=file mode=0664 size=73 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807 sha512256digest=52f9f7b626fcc7377e618c29c72d2adfb40659bad81aa9cabe69b1ef7e48ec15
|
||||
.git/hooks type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807
|
||||
.git/hooks/applypatch-msg.sample type=file mode=0775 size=478 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.793867723 sha512256digest=50e4870022c7e5ea0c9fd2556b9de3ea9741e5cc6bbca86d7749532e091f94aa
|
||||
.git/hooks/commit-msg.sample type=file mode=0775 size=896 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.793867723 sha512256digest=b131f0eacbaf7d6f5e45bc59413bedebc000006554d37d0f6387f94bff3d7ba4
|
||||
.git/hooks/post-update.sample type=file mode=0775 size=189 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.793867723 sha512256digest=a861e78221dd2aee9ef902e918279d18d5776f45ff2f08f2d6d85def3e879c20
|
||||
.git/hooks/pre-applypatch.sample type=file mode=0775 size=424 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.793867723 sha512256digest=ef5dc4dd0d72149edaa2240740936d256c30fa43f0f175c3e03db7684d8b1a5d
|
||||
.git/hooks/pre-commit.sample type=file mode=0775 size=1642 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.793867723 sha512256digest=ac3a75622c41756863cfb0e6a5eac2f9247e0aa2de287fcebf3fd059bfe7b423
|
||||
.git/hooks/pre-push.sample type=file mode=0775 size=1348 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.793867723 sha512256digest=5cb63a560e7dab995cdffe1fcd1b7ddbadc434b0e5adae360e0d5daa79e41bd5
|
||||
.git/hooks/pre-rebase.sample type=file mode=0775 size=4898 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807 sha512256digest=575537b3286d2ffcf1f129cb674b49e995b6f3843b32fb213c8372d59fd588f8
|
||||
.git/hooks/pre-receive.sample type=file mode=0775 size=544 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807 sha512256digest=3c068c42aee0de45188ba1180c447f9b9387c1551ee486caba4511e8b40f7573
|
||||
.git/hooks/prepare-commit-msg.sample type=file mode=0775 size=1239 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807 sha512256digest=9d96b773060e0260d7d7face6b1daee38ec0ad977a0f74f9d45520905e3f830e
|
||||
.git/hooks/update.sample type=file mode=0775 size=3610 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807 sha512256digest=7990f12495cee686e19080c5633b7cd18a40fdca1120be1a6128937f5568aac0
|
||||
.git/index type=file mode=0664 size=76425 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513087136.393593011 sha512256digest=83b6bbe228b1b181ef87269fd937adc615bf7f18940697807d864dff3f95b66b
|
||||
.git/info type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807
|
||||
.git/info/exclude type=file mode=0664 size=240 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807 sha512256digest=b5a32a7a25ad2254f517806d1600ff2498e2f91fe501cb54f9dd517e9693bf7c
|
||||
.git/logs type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.419901036
|
||||
.git/logs/HEAD type=file mode=0664 size=186 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.419901036 sha512256digest=6ba5a37eb3a8363ce228172998c9120800d2099e59ff0365f8095e437c4e7e72
|
||||
.git/logs/refs type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.419901036
|
||||
.git/logs/refs/heads type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.420901030
|
||||
.git/logs/refs/heads/master type=file mode=0664 size=186 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.420901030 sha512256digest=6ba5a37eb3a8363ce228172998c9120800d2099e59ff0365f8095e437c4e7e72
|
||||
.git/logs/refs/remotes type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.418901041
|
||||
.git/logs/refs/remotes/origin type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.418901041
|
||||
.git/logs/refs/remotes/origin/HEAD type=file mode=0664 size=186 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.419901036 sha512256digest=6ba5a37eb3a8363ce228172998c9120800d2099e59ff0365f8095e437c4e7e72
|
||||
.git/objects type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807
|
||||
.git/objects/info type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807
|
||||
.git/objects/pack type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.420901030
|
||||
.git/objects/pack/pack-6002ca434fbc0435338603c137c716144940e3ac.idx type=file mode=0444 size=64128 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774393.883903855 sha512256digest=22029d26bebf556cfc42e05b2803631e69913316cfdc4f93f81bf0b811422965
|
||||
.git/objects/pack/pack-6002ca434fbc0435338603c137c716144940e3ac.pack type=file mode=0444 size=2847104 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774393.883903855 sha512256digest=edcbdb76328491490433392cc9d73473d7ed3c4b9d730071eadafff7bcbbe322
|
||||
.git/packed-refs type=file mode=0664 size=980 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.418901041 sha512256digest=64272b5a7707bc9de1644ffe9f4ea331e0b27edfe2afa46051765d650e4f027d
|
||||
.git/refs type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.418901041
|
||||
.git/refs/heads type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.420901030
|
||||
.git/refs/heads/master type=file mode=0664 size=41 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.419901036 sha512256digest=ff968619a51a3545a1b690defeb0e496221e87f44d7842f21e4479cd8323e2b3
|
||||
.git/refs/remotes type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.418901041
|
||||
.git/refs/remotes/origin type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.419901036
|
||||
.git/refs/remotes/origin/HEAD type=file mode=0664 size=32 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.419901036 sha512256digest=59c99dd84367c1f79cc6e2fc932283a596ed1351f89a378f473bf223a1bce4a6
|
||||
.git/refs/tags type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512773804.794867807
|
||||
.gitignore type=file mode=0664 size=38 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.422901020 sha512256digest=da309c361a1906cf290acfe3f9737761809f4651398f51e8ba3b595ce3890d8a
|
||||
.travis.yml type=file mode=0664 size=418 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.422901020 sha512256digest=4cc236b9fd00e9e1d7e77c06d5387fc78b88d37fcc138a97f889afea77237809
|
||||
.vscode type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.422901020
|
||||
.vscode/tasks.json type=file mode=0664 size=1903 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.422901020 sha512256digest=0f618420497f07ab3c52fb914a2a017f9c8f6cecb827139000d83d3924945bba
|
||||
LICENSE type=file mode=0664 size=1502 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.422901020 sha512256digest=a38c5d130d16a578fadb5b64272dfa21bc3ee81a170ddfaef602c889ad0e5f47
|
||||
Makefile type=file mode=0664 size=1842 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.422901020 sha512256digest=bee7d37dc3d204d42316f6970ec8394cac8797f41fd033230543c669ae65eb10
|
||||
README.md type=file mode=0664 size=6458 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=a0f4fff9726af7e296dbacde210abb160f8d3ffe3a13eac3eae315804fbefdf7
|
||||
Session.vim type=file mode=0644 size=6088 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513025969.072229243 sha512256digest=ac49e25a69333d34faa213ea419bba40afc971880e6433a031ad93b7e91cda73
|
||||
check.go type=file mode=0664 size=1066 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=36d6f22adfdb787d2c66dcc7847339146c0e9b239bbdd6e36628264b0a9f6bfb
|
||||
check_test.go type=file mode=0664 size=6668 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=dc8562fc88ab21aa1e1c2eb77caa0c07bedf0801d8e2cb315cab71c4d63b0de0
|
||||
cksum.go type=file mode=0664 size=727 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=a16b170014a48532ee0fe3ea61bbe6a45cfd20a984bcceaed0c71524652fe4d3
|
||||
cksum_test.go type=file mode=0664 size=606 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=ff9857d71266a4939b453900155da931122e35187500535d826656a15ad9ba41
|
||||
cmd type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015
|
||||
cmd/gomtree type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015
|
||||
cmd/gomtree/main.go type=file mode=0664 size=12826 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=81195682a5fbdd6a324e2cfa7c61ddf9e14eea6d92c3133da4a858ffadadf6b0
|
||||
compare.go type=file mode=0664 size=12077 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=e0a79e38e03830d965271f4b37dbc6567998a277011437dfb270fb39030b8147
|
||||
compare_test.go type=file mode=0664 size=10837 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=39d0b9638b0b869cf20310a5b0e16ae979b690f3e795eafc1da4502c86c3d601
|
||||
creator.go type=file mode=0664 size=188 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=ef1e8b19255cdc090719bcd947937555c7454b36df7ca501be9c617f1d66d693
|
||||
entry.go type=file mode=0664 size=4645 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=e267d8bcb083acacf43318a038cb50a6fff4658f1c7cfb2d3ebd881d294647b4
|
||||
fseval.go type=file mode=0664 size=1903 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=aa0911e07f7607ce3e4912e37109400d17a905837975f4da48cb225af347afb1
|
||||
fseval_test.go type=file mode=0664 size=3692 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=337e96a382b8a1d4c6fc8c3b57bb5c5598f2de52c33017ddbd9087a8a8cdb235
|
||||
glide.lock type=file mode=0664 size=455 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=1ef63ba5b06290869c7bcf6bfa225fc646645f293d6b8b84844359e10a71d9dd
|
||||
glide.yaml type=file mode=0664 size=415 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=1ca9b54955195442592840664081368529c919ae2d1d7dabbb2bf6c236ba443d
|
||||
hierarchy.go type=file mode=0664 size=1109 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=25d9d887b37972e923656c907a72a0ab40f525704657ed51718b15b1d9030b08
|
||||
hierarchy_test.go type=file mode=0664 size=1427 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.423901015 sha512256digest=cbb37bbaa235cb30670af42d5ae1fff018eee4d97d83628670cc85cda0a8eec8
|
||||
keywordfunc.go type=file mode=0664 size=7790 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=972ef809068af40d0f4d931c179c09e3387652e493ff3badcdb1336ad03c0034
|
||||
keywordfuncs_bsd.go type=file mode=0664 size=2167 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=3d5442bc82156768131be11b491f0a196cb40ef4845026ffef230047c10a84dc
|
||||
keywordfuncs_linux.go type=file mode=0664 size=3129 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=774f8189932431f2903fcc292e7bc6bb0e0e478fcd0d18d21710398fa196de84
|
||||
keywordfuncs_unsupported.go type=file mode=0664 size=1440 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=4062bfa6539d920fd470c459cf91b903135ee41384d4addcd0635c1b8b248fd8
|
||||
keywords.go type=file mode=0664 size=8255 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=1bd20d1919c02036b8f969c73e0dc391dcc9fef5f003b5c5da36b434e4640b15
|
||||
keywords_linux_test.go type=file mode=0664 size=1777 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=6b009753d97b46d3dd7d0209b1866d0da50327c102ef9becb1a80a5d770b4008
|
||||
keywords_test.go type=file mode=0664 size=4084 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=c6fe584af730c805033c85db2a422b608f7cf034fe1d5bac732a354676328cb3
|
||||
lchtimes_unix.go type=file mode=0664 size=484 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=56cf8545358550958badaa966a41e5a07cbd15138d42e19997936e7ae6c330cd
|
||||
lchtimes_unsupported.go type=file mode=0664 size=137 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=a0d24dbf1ffe27dce9e2dcd72c2189c0ba8d9b445fb1d6efea3ef6f43372b141
|
||||
lookup_new.go type=file mode=0664 size=94 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=98317093a5e1d05d3829935c4c68ac09887fff4ad9257760d6cb80051ec6c793
|
||||
lookup_old.go type=file mode=0664 size=2670 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=3d4410b652c64fb4079e7219435aa2de828d7ef0cec2a8dabe9c3e95709c5cba
|
||||
mtree_test.go type=file mode=0664 size=1628 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513089487.302748479 sha512256digest=b205a894416221a6459b1f57688007b6ea29a7e25ee07f867acb16ca4481b281
|
||||
parse.go type=file mode=0664 size=2404 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=dacb3ab6f3a2c2a84ee1eef603f2943561c905947bd75dd8711a944bd646a75a
|
||||
pkg type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009
|
||||
pkg/govis type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004
|
||||
pkg/govis/COPYING type=file mode=0664 size=11358 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=84e99c21df3d69d6bcb82420dc1c5ab9e877aa19ca516fa2644cd2f1e6c35840
|
||||
pkg/govis/README.md type=file mode=0664 size=966 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=e84c3fc90fec0e85d707689c7859535ddced586627d42281a8f33d89698c77ed
|
||||
pkg/govis/govis.go type=file mode=0664 size=1784 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=d942950dfa287f81605b726e95e995db1e309c2ad211d236648eeab5b6840b49
|
||||
pkg/govis/govis_test.go type=file mode=0664 size=6727 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=9a75d86c248a307c026b311fb0513a689138d3806137b342ed255d4f9ce166c2
|
||||
pkg/govis/unvis.go type=file mode=0664 size=6889 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=eeba6c37bb098970d1ecbf53aa32ecd3d161ca5ed52e6ca0d1e7280495f3082a
|
||||
pkg/govis/unvis_test.go type=file mode=0664 size=5302 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.424901009 sha512256digest=d995e7aa0846ab698cbd4b3ae0aa54d0b1bf628def1e76689ab2d908e4113097
|
||||
pkg/govis/vis.go type=file mode=0664 size=5034 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=4ff0d8874456bafff63d14227a56732fe8296993eb7ff827d2c3388174c7c040
|
||||
pkg/govis/vis_test.go type=file mode=0664 size=8862 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=b93c8a5d205abb50a8bda7ed9101011856d664d3abee187a8d223ccfaf0bce32
|
||||
releases.md type=file mode=0664 size=422 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=67b517b1725f58d3f5680d33f303825d15e651df6644f7ebfc7f8c6822069c82
|
||||
stat_unix.go type=file mode=0664 size=311 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=031b6589306025dde40c3bc9502bd8e01900550ded8f63a92ee2d1dbeefcb6d8
|
||||
stat_windows.go type=file mode=0664 size=177 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=5478d5912535771b76cfce0d4f8b6629d745cb4d7c76060b42f6ca9422da7307
|
||||
tar.go type=file mode=0664 size=12908 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=6e108ec0cce2c4845db2280aa0e800ced879a9263a91d5bb9d1c346386e58cfc
|
||||
tar_test.go type=file mode=0664 size=9332 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=5da79852086430337df7f87266e290d4888dc78a39e139d38a4bc96957bd9b5b
|
||||
test type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004
|
||||
test/cli type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004
|
||||
test/cli/0001.sh type=file mode=0664 size=1340 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=7c78b4d73ec17dbf77a1af6903fe2347fc3841f7dbb15cbd6a1bbc8b133350b3
|
||||
test/cli/0002.sh type=file mode=0664 size=614 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=1ef56e3423025f1eb7bbf965abd99399498cd5804c9983815bc13ca638336ca5
|
||||
test/cli/0003.sh type=file mode=0664 size=995 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=e9dc80a37f6ffb633024b394c621cb7f49e06828400703bfd1044b4f6776921c
|
||||
test/cli/0004.sh type=file mode=0664 size=660 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=22fad78aa558af47ca5839966cb5d49320ee8fd23b7414e26d7871991a29fcd0
|
||||
test/cli/0005.sh type=file mode=0664 size=440 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=e9a705d3fb58f37655070a29749c955d89c5f5897d20df63e8703aa046285121
|
||||
test/cli/0006.sh type=file mode=0664 size=860 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=fe5c5b969ae47ebf00855cb163acd2ee8e73fd0ce4506aab74315b1aecf8ddda
|
||||
test/cli/0007.sh type=file mode=0664 size=454 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=cedf7d50bd3d774d5d286148ac363e548e1b2bc39718cc3775c1e5018b196baf
|
||||
test/cli/0008.sh type=file mode=0664 size=562 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=1f0947e3f92c24a607c91d8e6bc4ca7a5d919fe860f5a8759b40fe966302ef16
|
||||
test/cli/0009.sh type=file mode=0664 size=1683 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=31ced90180929e0bb3297b196cd927644038e9c446d8fe85e6ceafa839264abe
|
||||
test/cli.go type=file mode=0664 size=508 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=7058ca6366fdf79c23531a4b4cb22bef2dc6de24ba0ef08ac9fd5ef4974341ab
|
||||
testdata type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513088963.690901011
|
||||
testdata/collection type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/collection/dir1 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/collection/dir1/file1 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/collection/dir2 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/collection/dir2/file2 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/collection/dir3 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/collection/dir3/file3 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/collection/dir4 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/collection/dir4/file4 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/collection/dir5 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/collection/dir5/dir6 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/collection/dir5/dir6/dir7 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/collection/dir5/dir6/dir7/lonelyfile type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/collection/file1 type=file mode=0664 size=3 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=f1015369e7da404777803c72d48afdbe3a0d1bd2c5feb2a9d5b3db5ac78e9b9c
|
||||
testdata/collection/file2 type=file mode=0664 size=6 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=7f3f0c0d5219f51459578305ed2bbc198588758da85d08024c79c1195d1cd611
|
||||
testdata/collection/file3 type=file mode=0664 size=12 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=1b88acd5ac9d970535991a5fbfbe6c1c5ca3434cea31cd8f9fc7de83e798e678
|
||||
testdata/collection.tar type=file mode=0664 size=10240 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.425901004 sha512256digest=9d5eed555694f1de43a60a1f5b282d0e63b20edda06cbd10430edde8b74803ad
|
||||
testdata/dirwithbrokenlink type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/\040file\040with\040spaces\040 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/dirwithbrokenlink/deepdir type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/deepdir/dir3 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/dir4 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/dir4/deepfile type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/dir4/dir5 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/dir4/dir5/dir6 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/dir4/dir5/dir6/deeperfile type=file mode=0664 size=19 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=b3b48f5ca2cfd0f9426432fbaaedb57ad2750b1bfe3235c712098e8953b96fbb
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/dir4/dir5/dir7 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/dir4/dir5/dir7/deeperfile type=file mode=0664 size=15 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c2c4f516ee9fef3b2ecf1e2aba9c3f4d4d793d5b7140532da6ba5468f69a5f4f
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/file type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/file6 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/dirwithbrokenlink/deepdir/dir3/file7 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/dirwithbrokenlink/deepdir/this_is_a_file type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/dirwithbrokenlink/dir1 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/dir1/.hiddenfile type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/dirwithbrokenlink/dir1/badlink type=link mode=0777 link=badfile uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/dir1/dir2 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/dir1/dir2/.hidden type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/dir1/dir2/.hidden/goodlink type=link mode=0777 link=../../goodfile uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999
|
||||
testdata/dirwithbrokenlink/dir1/goodfile type=file mode=0664 size=10 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=3b69a4f25d23a1a184f5d32e7d8a5a1e19f1062cefe57fd1ccc7dfd9c702fd8d
|
||||
testdata/dirwithbrokenlink/file1 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/dirwithbrokenlink/file2 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/hardlinks.tar type=file mode=0664 size=10240 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.426900999 sha512256digest=33d43be8aa1f5ad9ff90ac2c3ba732f89de25fef7ee6cc6233bce2cf8e7b7a92
|
||||
testdata/singlefile type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.427900993
|
||||
testdata/singlefile/dir2 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.427900993
|
||||
testdata/singlefile/dir2/dir3 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.427900993
|
||||
testdata/singlefile/dir2/dir3/file type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.427900993 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/singlefile.tar type=file mode=0664 size=10240 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.427900993 sha512256digest=c06b6d2223c9a35951d9e6cbbaea9a46565ca3ae7bba1339051019681820a39c
|
||||
testdata/source.mtree type=file mode=0664 size=9110 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.427900993 sha512256digest=ae80aea4b907dcd2ac567a201f39694e18b0f6cef76f71a154f5581446f33945
|
||||
testdata/test.tar type=file mode=0664 size=20480 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=f5624efd93b66b7bd144bdb2a7dc1f0af833bbfd23f0691a671a53704e620a9b
|
||||
testdata/traversal type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988
|
||||
testdata/traversal/dir2 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988
|
||||
testdata/traversal/dir2/dir3 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988
|
||||
testdata/traversal/dir2/dir3/actualdir1 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988
|
||||
testdata/traversal/dir2/dir3/actualdir1/file1 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/traversal/dir2/dir3/actualdir1/file2 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/traversal/dir2/dir3/actualdir2 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988
|
||||
testdata/traversal/dir2/dir3/actualdir2/a type=file mode=0664 size=6 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=7f3f0c0d5219f51459578305ed2bbc198588758da85d08024c79c1195d1cd611
|
||||
testdata/traversal/dir2/dir3/actualdir2/b type=file mode=0664 size=4 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=9e591fb9c0c021fd8639eb5a44fa29e62835381c94cfd0d7db3ebef83fc2ce18
|
||||
testdata/traversal/dir2/dir3/actualdir2/c type=file mode=0664 size=12 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=1b88acd5ac9d970535991a5fbfbe6c1c5ca3434cea31cd8f9fc7de83e798e678
|
||||
testdata/traversal/dir2/dir3/file type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/traversal/dir2/dir3/file3 type=file mode=0664 size=0 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
|
||||
testdata/traversal.tar type=file mode=0664 size=10240 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.428900988 sha512256digest=a69da7826437471c45c2ca6fad6860835ac2c5df5f652380bf316b2d0580a4aa
|
||||
testdata/xattr.casync-mtree type=file mode=0664 size=28672 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513089579.719898057 sha512256digest=d51e93315715b31a254cd21d1bb028bab6b9dec2653f4f7ba6557f89a7e5a153
|
||||
update.go type=file mode=0664 size=3865 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=f0bfc79f2f0dde387139117c6a1e5d5aa69c06aac233d53b0fc2be1e55cfba32
|
||||
update_linux_test.go type=file mode=0664 size=2330 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=c708daffa3d4c4869df009891af3c18a9b6ba6b200a28bed45b567b116dd117a
|
||||
update_test.go type=file mode=0664 size=2848 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=b5e0093cecd9f799fbebd651a3c63b159079bd86e31e02b1329078f78dd7219b
|
||||
updatefuncs.go type=file mode=0664 size=5049 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=b8a066b3544f745eb6233978f4322f9da15e30e9e009bcfd738a07f4ff12ef88
|
||||
updatefuncs_linux.go type=file mode=0664 size=391 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=de5d27e36de90d33f9efac533330f3cafb302cb1886ce63970107985dc2ab67b
|
||||
updatefuncs_unsupported.go type=file mode=0664 size=151 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=adec393ac2f40975877645a58fb43b01c5b94090f70bec2470b9b30efc9c821a
|
||||
vendor type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972
|
||||
vendor/github.com type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983
|
||||
vendor/github.com/sirupsen type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983
|
||||
vendor/github.com/sirupsen/logrus type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972
|
||||
vendor/github.com/sirupsen/logrus/.gitignore type=file mode=0664 size=7 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=004bbd91c2e11a52b6c11ef24a5c2578e653b9d25e60f28b077fb11b3b06af75
|
||||
vendor/github.com/sirupsen/logrus/.travis.yml type=file mode=0664 size=313 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=e7e6345826dfa1b3624659022f1e768ccfe3e9e16834dd8e7a443591fddc8ac5
|
||||
vendor/github.com/sirupsen/logrus/CHANGELOG.md type=file mode=0664 size=2509 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=49b7c9aec18b26e851c301f273001ddf6d6cefc3633b025b5391bcc88c4524f1
|
||||
vendor/github.com/sirupsen/logrus/LICENSE type=file mode=0664 size=1082 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=f163519149dc1ca924d74e9d228bee482ea4c939c89d7e9c53aa5c48b9e1bce5
|
||||
vendor/github.com/sirupsen/logrus/README.md type=file mode=0664 size=22063 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=29672a4ceb7a04cbb54b8c425d70d05bc2633597037bb8617259d74b0d381de5
|
||||
vendor/github.com/sirupsen/logrus/alt_exit.go type=file mode=0664 size=2246 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=707edd5510b2aa3780fcfd6fcf51471f5c9c9aee00b84814d1a6498847abe548
|
||||
vendor/github.com/sirupsen/logrus/alt_exit_test.go type=file mode=0664 size=1623 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=36cbdb077b1389d44178a47fa507c2acf008f4306785696ba8f6557ef9ef569e
|
||||
vendor/github.com/sirupsen/logrus/appveyor.yml type=file mode=0664 size=281 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=ca5a031100d1f288ebf376a86f63c66ee092c560a569d8a43344b39ade7278b1
|
||||
vendor/github.com/sirupsen/logrus/doc.go type=file mode=0664 size=586 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=80339eeb074e45314d9e38ec03e08fa587124f713483bf96de4246f3e5742d3a
|
||||
vendor/github.com/sirupsen/logrus/entry.go type=file mode=0664 size=6963 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=42adbbdf08f60a86e7415f160a0d69c0507d8cef72de4f449d750f4174be819e
|
||||
vendor/github.com/sirupsen/logrus/entry_test.go type=file mode=0664 size=1459 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=a729abde18f325b18ca396e22598bd0f51ccc677624e7fc1d27a832b8ab96370
|
||||
vendor/github.com/sirupsen/logrus/example_basic_test.go type=file mode=0664 size=2042 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=c8ea083f4a98401602895fa178ea5b74acd1b32a70236ba2e361d97eb75372cb
|
||||
vendor/github.com/sirupsen/logrus/example_hook_test.go type=file mode=0664 size=1001 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.429900983 sha512256digest=195cffcc9c4483ce0e4920803cc8bb9924045d39807c7495979e658825ef6cf7
|
||||
vendor/github.com/sirupsen/logrus/exported.go type=file mode=0664 size=4880 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=e229a9b34fb85ae1920db8e421dba771d7128cdc6712838fb3876a212b5c504b
|
||||
vendor/github.com/sirupsen/logrus/formatter.go type=file mode=0664 size=1368 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=cc8189494c6cae97fbe5e7dde3c9e9dcde2d1d8398b28271264949173d6e58c7
|
||||
vendor/github.com/sirupsen/logrus/formatter_bench_test.go type=file mode=0664 size=2143 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=e4db7f602a583206c8d730a826d0766263b2ea28838c129af21a967ff197bbcc
|
||||
vendor/github.com/sirupsen/logrus/hook_test.go type=file mode=0664 size=2128 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=8e050faa00cf410935bc0f7191ab3a9635e276e9bb865163d6d6ece954009e8d
|
||||
vendor/github.com/sirupsen/logrus/hooks type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978
|
||||
vendor/github.com/sirupsen/logrus/hooks/syslog type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978
|
||||
vendor/github.com/sirupsen/logrus/hooks/syslog/README.md type=file mode=0664 size=926 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=7f829c411b7ba02a3a07c022eeb012198b7502f5d832a97e910e3fdd0b79cc31
|
||||
vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go type=file mode=0664 size=1311 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=7237f9f92f9a7df0d434ca5186689a5a7334df38579e661db54037fd14af4128
|
||||
vendor/github.com/sirupsen/logrus/hooks/syslog/syslog_test.go type=file mode=0664 size=533 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=3d54002343235ab97738a8d2480bf9d602ac337951e62faac706456064d60ebe
|
||||
vendor/github.com/sirupsen/logrus/hooks/test type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978
|
||||
vendor/github.com/sirupsen/logrus/hooks/test/test.go type=file mode=0664 size=2096 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=0c4d6b7f985f366c427bca638347676dc3838c24f7aedc56b2e62df69156be1e
|
||||
vendor/github.com/sirupsen/logrus/hooks/test/test_test.go type=file mode=0664 size=907 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=9af8e14bec05237f73a048e3e77155315d223a8c5fb40d9c8e1f4602a743d2db
|
||||
vendor/github.com/sirupsen/logrus/hooks.go type=file mode=0664 size=1101 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=5708f971359b96da0b6d72189fe2d678b10048d3b023eadd7497b42d950ceb47
|
||||
vendor/github.com/sirupsen/logrus/json_formatter.go type=file mode=0664 size=1885 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=f049f1fb554fc95cae2a86a1103b1fdc098ac6cf1d65e41b62acb866fef18acd
|
||||
vendor/github.com/sirupsen/logrus/json_formatter_test.go type=file mode=0664 size=4492 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=fdb45612b177b289fd2e35d4dd8af009e167f8b9e01acc641f6d84100d084af8
|
||||
vendor/github.com/sirupsen/logrus/logger.go type=file mode=0664 size=7946 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=00b348ea5157129bdbab52c5bddad4f3d8895a7d7d83c173c1c53ab64a7b6b4f
|
||||
vendor/github.com/sirupsen/logrus/logger_bench_test.go type=file mode=0664 size=1344 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=ca5ccdae3164b30d0ed27ad002b60be443e459f0354b5f044b39e5644b8b118f
|
||||
vendor/github.com/sirupsen/logrus/logrus.go type=file mode=0664 size=3667 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=0a890ae067dc1c3f045787919c61d8297dc5b93834fcc39e8a30303b93acd680
|
||||
vendor/github.com/sirupsen/logrus/logrus_test.go type=file mode=0664 size=9160 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=2417c7d6f8397020046b12b868124cb22ea00456d60daceac1572f4593eed03d
|
||||
vendor/github.com/sirupsen/logrus/terminal_bsd.go type=file mode=0664 size=186 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=588ed9e26de1dc3f7de02f746a0975a292a54929183df26232f8181b91cd17b5
|
||||
vendor/github.com/sirupsen/logrus/terminal_linux.go type=file mode=0664 size=320 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=745de29db1cd82e46345904126f4175a05ba4d1646e40940f78fa5281a1c8a8b
|
||||
vendor/github.com/sirupsen/logrus/text_formatter.go type=file mode=0664 size=4349 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.430900978 sha512256digest=f8c203f7e13ee9ca132d07a29ae243105f3776b2a03a4c28170f9d43f97a56be
|
||||
vendor/github.com/sirupsen/logrus/text_formatter_test.go type=file mode=0664 size=3726 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=49dbdae1731e825fff63074bc1b92906bdb7a47db5c753b44bcd02cc7e918eba
|
||||
vendor/github.com/sirupsen/logrus/writer.go type=file mode=0664 size=1269 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=a40e7c61fc44d312f71d0fe7ffd07d79098909c9c2d121631205e2e7259e262e
|
||||
vendor/golang.org type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972
|
||||
vendor/golang.org/x type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899
|
||||
vendor/golang.org/x/crypto type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899
|
||||
vendor/golang.org/x/crypto/.gitattributes type=file mode=0664 size=345 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=121f311806f70b4e9278a7a7178bbb73cb8afc0ce20d6134f24cf6a9f637cf85
|
||||
vendor/golang.org/x/crypto/.gitignore type=file mode=0664 size=84 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=c1bc3cda1eb014d25dbebe52e50c7231f60762092811d111e2b60b2a847984d5
|
||||
vendor/golang.org/x/crypto/AUTHORS type=file mode=0664 size=173 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=2e6b61f243b5f5e42e480c2d4588f1538ff08617ce66c01e99589333d4199a2e
|
||||
vendor/golang.org/x/crypto/CONTRIBUTING.md type=file mode=0664 size=1031 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=9f99f8daf18ff8de732527ac23231c253c9800139002bf2b03250fce7372fdfb
|
||||
vendor/golang.org/x/crypto/CONTRIBUTORS type=file mode=0664 size=170 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=261a74c469ed7777fd933f3cd1544b351d162ad51d5cedb570aab4da0fbc62d7
|
||||
vendor/golang.org/x/crypto/LICENSE type=file mode=0664 size=1479 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=ea1cff0ac6655ef2801edc8f18ba083c7fae87b0fb0c542ea1d9628e46992f19
|
||||
vendor/golang.org/x/crypto/PATENTS type=file mode=0664 size=1303 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=2ea07f77ca1c22dc417acfa6942f0faf5f545f44bca4c9387dab0e6e5ca442af
|
||||
vendor/golang.org/x/crypto/README type=file mode=0664 size=145 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=0ac9ce2faeed2023c29348f068fab8049c29403430d37a0be5132b0bf762f8b9
|
||||
vendor/golang.org/x/crypto/bcrypt type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972
|
||||
vendor/golang.org/x/crypto/bcrypt/base64.go type=file mode=0664 size=817 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=44021e633e2cb964c43373d30380f1efacf8b115a5e3419697a3decd3d1e336a
|
||||
vendor/golang.org/x/crypto/bcrypt/bcrypt.go type=file mode=0664 size=7880 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=ddd672a8e6874c38ea15edc4b962e0a3ede43e090ef7f6f8199c6a0c7d37400d
|
||||
vendor/golang.org/x/crypto/bcrypt/bcrypt_test.go type=file mode=0664 size=6372 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=cd047b3a9a3d46c20774c10439572842c6270dcd7d48ee8c5fae8e12ce15491a
|
||||
vendor/golang.org/x/crypto/blowfish type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972
|
||||
vendor/golang.org/x/crypto/blowfish/block.go type=file mode=0664 size=6161 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=895b2605173026d2a411c25bdcacaec1793d31d5d5cdd84c4126a1887b47f058
|
||||
vendor/golang.org/x/crypto/blowfish/blowfish_test.go type=file mode=0664 size=10197 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=3683841d3b3eb3fc63fa79353f94a889e19e32b08c3b9e04e20e60c0f628717b
|
||||
vendor/golang.org/x/crypto/blowfish/cipher.go type=file mode=0664 size=3109 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=c99ed894413ef3a4fed02a8f0cf6606bbd7975e93b956f60b05adb435c250d87
|
||||
vendor/golang.org/x/crypto/blowfish/const.go type=file mode=0664 size=13131 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.431900972 sha512256digest=496b0f019d93c6bd67e526f7fe118619e2b7a1c7c308476d057c71776b1a22fc
|
||||
vendor/golang.org/x/crypto/bn256 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967
|
||||
vendor/golang.org/x/crypto/bn256/bn256.go type=file mode=0664 size=9621 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=9ff55812c49b84d5f07c63259b6ff6a5dce62ccd66d3e6b7f791995e8311054b
|
||||
vendor/golang.org/x/crypto/bn256/bn256_test.go type=file mode=0664 size=6696 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=a93b06fb3af2e227c99a7e2132e173de17e33343aa14d1343322276976966cb5
|
||||
vendor/golang.org/x/crypto/bn256/constants.go type=file mode=0664 size=2472 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=5df09cc395bc759610a4e8b135f77a27026160cdb7b8c878b27763efc4fb3c7d
|
||||
vendor/golang.org/x/crypto/bn256/curve.go type=file mode=0664 size=5477 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=e71600cb0f39d7fbad38eee3baee48eba90e223592846634b483d68a5d38b992
|
||||
vendor/golang.org/x/crypto/bn256/example_test.go type=file mode=0664 size=1163 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=69a4f1428227126eb577faff064fb8cbf2226598523e21306cf09252931030ee
|
||||
vendor/golang.org/x/crypto/bn256/gfp12.go type=file mode=0664 size=3645 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=2e0c360c93c85f2caeb396a3b886524c9e0be65a0eb7123b5acd07131d6bf2c1
|
||||
vendor/golang.org/x/crypto/bn256/gfp2.go type=file mode=0664 size=3792 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=911f33cfa4e32c62faa65ebe0af7b54edcbbfd50375209f14273fe35ce6a405f
|
||||
vendor/golang.org/x/crypto/bn256/gfp6.go type=file mode=0664 size=5777 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=e4b37174a01be9bbf18ef0b058452de8a733266fe2793e1dfe9967ddb0563515
|
||||
vendor/golang.org/x/crypto/bn256/optate.go type=file mode=0664 size=8667 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=06e551794879e82a39a69f2eb74844f55bd36d20924e3a7380d7be58c960a872
|
||||
vendor/golang.org/x/crypto/bn256/twist.go type=file mode=0664 size=5306 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=5f78e24a835bd60ebd5af4d556ac1108f4f361300d7069aa4d242c028fb76cb3
|
||||
vendor/golang.org/x/crypto/cast5 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967
|
||||
vendor/golang.org/x/crypto/cast5/cast5.go type=file mode=0664 size=32541 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=c36e5698d13994bd8e27e4b7a15ff063db1c1fd4b3fc4d86920958df9167a4f3
|
||||
vendor/golang.org/x/crypto/cast5/cast5_test.go type=file mode=0664 size=2624 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=6088d6e97f054dff9ce1480afae0b91aa248e73a48faf1c6acf210d2b8557098
|
||||
vendor/golang.org/x/crypto/curve25519 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962
|
||||
vendor/golang.org/x/crypto/curve25519/const_amd64.s type=file mode=0664 size=597 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=8d7059b5263f3e64abb06e8e1d4abf8315f2a0c16495e84f69424a2630721006
|
||||
vendor/golang.org/x/crypto/curve25519/cswap_amd64.s type=file mode=0664 size=1541 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=37da1fbe15113a31497643b79165687a7e4163afc0ac0ea36c3d7628de7d3078
|
||||
vendor/golang.org/x/crypto/curve25519/curve25519.go type=file mode=0664 size=22032 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.432900967 sha512256digest=2e24c7ca245ecd4d9b656338a801c3fab5875b7dc8eb7f0ad14062346b077d4d
|
||||
vendor/golang.org/x/crypto/curve25519/curve25519_test.go type=file mode=0664 size=605 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=53ef3c52714a7c3f9901b53f22c6dd4b3691b6471a588e55e78105adb71b7b4c
|
||||
vendor/golang.org/x/crypto/curve25519/doc.go type=file mode=0664 size=1043 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=e668799e8efdfb7b1a0f706346ed658e36513bfbef63f7191b79f35b9e6475b3
|
||||
vendor/golang.org/x/crypto/curve25519/freeze_amd64.s type=file mode=0664 size=1540 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=83cece5a83eff41c06d265e07de0c79b8cea0f0eba2a54c0c92980eb88cc241e
|
||||
vendor/golang.org/x/crypto/curve25519/ladderstep_amd64.s type=file mode=0664 size=20422 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=2009f13488ed4b547210c86886f5dd840c15427d1067eb2d856b20e088430aa8
|
||||
vendor/golang.org/x/crypto/curve25519/mont25519_amd64.go type=file mode=0664 size=5262 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=cdf4d03529d6a11d2cec2309fa920524a90f2c4fbb0eaeafdf396982fb30d32c
|
||||
vendor/golang.org/x/crypto/curve25519/mul_amd64.s type=file mode=0664 size=2869 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=8bb1581d8282d94dbf37983f7fa2778690fa9b37027404ead7efe8672b037d91
|
||||
vendor/golang.org/x/crypto/curve25519/square_amd64.s type=file mode=0664 size=2349 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=cc7355e0f4f3c1624276af40d2e100b7897510f2ae95be56a025df1a489a54ce
|
||||
vendor/golang.org/x/crypto/hkdf type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962
|
||||
vendor/golang.org/x/crypto/hkdf/example_test.go type=file mode=0664 size=1498 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=e4044f70276a817e4595b1b5dd8bc89bb0764bc586c069d34c0f488418427b35
|
||||
vendor/golang.org/x/crypto/hkdf/hkdf.go type=file mode=0664 size=1837 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=10cc33ce4c7bcba4145bb235a8400564b4a632cfd081feede850100af33ac7af
|
||||
vendor/golang.org/x/crypto/hkdf/hkdf_test.go type=file mode=0664 size=10871 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=0aac96303c4b2e30ed07cc01d2d81e13c943454cad859a2265b6b5b94b03ad7c
|
||||
vendor/golang.org/x/crypto/md4 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962
|
||||
vendor/golang.org/x/crypto/md4/md4.go type=file mode=0664 size=2124 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=198ecc800bb0b351aa4f2cdf2fb490c720038e7bed43c2b64f2e8e8fde22ca78
|
||||
vendor/golang.org/x/crypto/md4/md4_test.go type=file mode=0664 size=3307 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=95a1a27481451dadb75aa8418636ab99f59d42ea72bf7fd14a402dbb042b5d4f
|
||||
vendor/golang.org/x/crypto/md4/md4block.go type=file mode=0664 size=2036 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=5a7049b30a80f749120670f187e71f5affd5333f8d2d8d8c4e47b935c3e43463
|
||||
vendor/golang.org/x/crypto/nacl type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962
|
||||
vendor/golang.org/x/crypto/nacl/box type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962
|
||||
vendor/golang.org/x/crypto/nacl/box/box.go type=file mode=0664 size=3264 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=dd140b441292a6c700a07ed5ec5a97f0be88b863a067840c7cfc67c13cf80ce4
|
||||
vendor/golang.org/x/crypto/nacl/box/box_test.go type=file mode=0664 size=1904 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=1869dc20e30b0f6288c60b15c3cb896c1132f2303339f5d110e03bb6d0b75704
|
||||
vendor/golang.org/x/crypto/nacl/secretbox type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962
|
||||
vendor/golang.org/x/crypto/nacl/secretbox/secretbox.go type=file mode=0664 size=4747 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=44472e52cac37ba9fbc929e02cf9d130ded2ed1bb98feecb351c4c5d3b28262d
|
||||
vendor/golang.org/x/crypto/nacl/secretbox/secretbox_test.go type=file mode=0664 size=2065 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.433900962 sha512256digest=8ce4c36c2bb550845f13ff92668e20bedb2be0025a8fb75c2530f618de9586ad
|
||||
vendor/golang.org/x/crypto/ocsp type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957
|
||||
vendor/golang.org/x/crypto/ocsp/ocsp.go type=file mode=0664 size=17554 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=f4e66e3d111b30cbdc478ca68b4ebe6640a97266559d91a652db2e9e93b4418f
|
||||
vendor/golang.org/x/crypto/ocsp/ocsp_test.go type=file mode=0664 size=23500 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=85e9086879f3109d028909b97fecab89c09dc1ed50cf421fe7522ca04e948c12
|
||||
vendor/golang.org/x/crypto/openpgp type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941
|
||||
vendor/golang.org/x/crypto/openpgp/armor type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957
|
||||
vendor/golang.org/x/crypto/openpgp/armor/armor.go type=file mode=0664 size=5026 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=fd714e357b5ef7bab37830118cbdefbbec8eb283913eaa5f24be348a3347dc27
|
||||
vendor/golang.org/x/crypto/openpgp/armor/armor_test.go type=file mode=0664 size=3102 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=e261040ae16f64903cde837945ad3a81474a3b72a0f7551f6318830a1044ac70
|
||||
vendor/golang.org/x/crypto/openpgp/armor/encode.go type=file mode=0664 size=3392 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=7266294573e174cfb78e571a00cbb3e8d2f91908b80730eb066b8c2c7d87d9dc
|
||||
vendor/golang.org/x/crypto/openpgp/canonical_text.go type=file mode=0664 size=1143 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=4a3bfa165a78aa099605613e2b520985dbda9500c1c5e4a73a27114ff07e7a25
|
||||
vendor/golang.org/x/crypto/openpgp/canonical_text_test.go type=file mode=0664 size=1202 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=e531fddb89427fdaa50f1f6b56d3651bae5a0317f1253a9d881c8eaa3112f4c3
|
||||
vendor/golang.org/x/crypto/openpgp/clearsign type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957
|
||||
vendor/golang.org/x/crypto/openpgp/clearsign/clearsign.go type=file mode=0664 size=9457 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=9bbff408119e071e5769be5882fd381cc0c464e3508b09e947a87617abdca76e
|
||||
vendor/golang.org/x/crypto/openpgp/clearsign/clearsign_test.go type=file mode=0664 size=6104 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=e381a011f4d710751cefd0f64cde30d8b275e82bf649c37b0bd67442f7f8c628
|
||||
vendor/golang.org/x/crypto/openpgp/elgamal type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957
|
||||
vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go type=file mode=0664 size=3543 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=5c619abe82ea4d6b6e7533dabb23edfbf94b22502966883f302a8976aa089039
|
||||
vendor/golang.org/x/crypto/openpgp/elgamal/elgamal_test.go type=file mode=0664 size=1589 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.434900957 sha512256digest=9cd4fe1b7d0d992d30a0657f238fcf0633d39df2dda7b80fdcbe5a415f90715f
|
||||
vendor/golang.org/x/crypto/openpgp/errors type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952
|
||||
vendor/golang.org/x/crypto/openpgp/errors/errors.go type=file mode=0664 size=1883 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=da67ae346387c702f5cf9276a898e59c023522a00b6ac0b7d892ed2c78becd2e
|
||||
vendor/golang.org/x/crypto/openpgp/keys.go type=file mode=0664 size=17806 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=1a56374663da077c59b9be83b06cc3f2dce34fc6d622d0a7e3fb028f9266d694
|
||||
vendor/golang.org/x/crypto/openpgp/keys_test.go type=file mode=0664 size=20358 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=2186d10ac7393e1d76ad96d270b79757a8d8653a232da9539218951f3cd827f8
|
||||
vendor/golang.org/x/crypto/openpgp/packet type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941
|
||||
vendor/golang.org/x/crypto/openpgp/packet/compressed.go type=file mode=0664 size=3289 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=adecbe682175746c0b3cc99301427af62feb7ff4d64c1ffc780811635e735a07
|
||||
vendor/golang.org/x/crypto/openpgp/packet/compressed_test.go type=file mode=0664 size=897 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=05b50262a8d4c64c1eaa50c7af7b63f783040e5de10786767ef69964ef7cf3d7
|
||||
vendor/golang.org/x/crypto/openpgp/packet/config.go type=file mode=0664 size=2440 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=00a82226c5f090c6eeb281dc7d403ef738d223e8d524429ea7ae201ae0e06c75
|
||||
vendor/golang.org/x/crypto/openpgp/packet/encrypted_key.go type=file mode=0664 size=5166 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=8bd50af79b58eb0b211111c59cb0a05723d9e0a08775536931402e55a65bc4ce
|
||||
vendor/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go type=file mode=0664 size=3564 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=080566cfe10ab282aefa8ade0388f647e0ef5d5711c531011981d28d0afafbdc
|
||||
vendor/golang.org/x/crypto/openpgp/packet/literal.go type=file mode=0664 size=1924 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=996ba9ab21f2be954d24293920637763ce51b7fbdd9c79bebebf275e8d934add
|
||||
vendor/golang.org/x/crypto/openpgp/packet/ocfb.go type=file mode=0664 size=3707 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=65ba916f7d9281cc8c03c6052c07e31e1843b7462cb37e750e5692d0ab669041
|
||||
vendor/golang.org/x/crypto/openpgp/packet/ocfb_test.go type=file mode=0664 size=1269 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=f8dcff932881b568ecc34f6e147b000182704f07b17658c4d1ed28d70cab81d8
|
||||
vendor/golang.org/x/crypto/openpgp/packet/one_pass_signature.go type=file mode=0664 size=1778 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=9aed3906d54e5efe91f32aa2659de0776a71610411e5bb53d971c4a6cde29451
|
||||
vendor/golang.org/x/crypto/openpgp/packet/opaque.go type=file mode=0664 size=4094 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=64ae0e8f3c05a6a4e1c0fadc7a3dff3f61c0ac9a860d69b61a58dddad5717940
|
||||
vendor/golang.org/x/crypto/openpgp/packet/opaque_test.go type=file mode=0664 size=2501 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=0cfc6c1af52fc37cc846e7e0cf6c010047282e11bd6582f6be5b8d60f9f1a34f
|
||||
vendor/golang.org/x/crypto/openpgp/packet/packet.go type=file mode=0664 size=13568 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.435900952 sha512256digest=148d34e2e129a6b5f5b14cbd1a2111647aef73c0305fe02f1518ba35efe16d6b
|
||||
vendor/golang.org/x/crypto/openpgp/packet/packet_test.go type=file mode=0664 size=6342 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=f5ddc5664c217d6edae5172fa5f6fdb40d8973577a87e00d2b5fc6ec4ebf69c4
|
||||
vendor/golang.org/x/crypto/openpgp/packet/private_key.go type=file mode=0664 size=6917 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=736844634714bbf73107bfd732e1da57111bb3ce218da32d917f1e839f3a73dd
|
||||
vendor/golang.org/x/crypto/openpgp/packet/private_key_test.go type=file mode=0664 size=3102 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=2c0d6b7879839aaeb264bec35d1a82324ea63c8e54575466a715391690d8070f
|
||||
vendor/golang.org/x/crypto/openpgp/packet/public_key.go type=file mode=0664 size=19380 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=22d25c31bbc85b9835e712cb38204579c51f3e5e153f01e917aa7728eac7a469
|
||||
vendor/golang.org/x/crypto/openpgp/packet/public_key_test.go type=file mode=0664 size=8170 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=a03f67f13a9237e6d15bbede91ccb84d06e82b2eb552a17fc6c72124c47be8b8
|
||||
vendor/golang.org/x/crypto/openpgp/packet/public_key_v3.go type=file mode=0664 size=8001 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=a1e5e33772018db138a971699b329191c6911700bf8eb945f6402bbc09a39a46
|
||||
vendor/golang.org/x/crypto/openpgp/packet/public_key_v3_test.go type=file mode=0664 size=2366 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=83569fd6889935de5cebace9ca2c9b7eabf7df08ec88857eb8b1fece963b5a08
|
||||
vendor/golang.org/x/crypto/openpgp/packet/reader.go type=file mode=0664 size=1508 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=381c0747bcda0beca356b8d0334ef78b463c04c8fcf1af17c020b475571174c3
|
||||
vendor/golang.org/x/crypto/openpgp/packet/signature.go type=file mode=0664 size=19847 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=00603958d0cc55177125789ff43f93bc859ae8dff577ae5d8610bcfab24ae2a2
|
||||
vendor/golang.org/x/crypto/openpgp/packet/signature_test.go type=file mode=0664 size=1625 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=50f7af45db15cb6b161743f87403fdbda207642de457eaaebae128c130550039
|
||||
vendor/golang.org/x/crypto/openpgp/packet/signature_v3.go type=file mode=0664 size=3936 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=8b4ff517dc9c1736ea8ff0cfc75c27352cc559f58eb75cea9182d847ecb38b3b
|
||||
vendor/golang.org/x/crypto/openpgp/packet/signature_v3_test.go type=file mode=0664 size=2768 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=eaa81e4b4060c33f827dfd606a74a66bd0650c72db23ee269d31da958ae454d2
|
||||
vendor/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go type=file mode=0664 size=4736 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=591057f74c8e6ddb10cdd376b4643cd9fdc24b6b6c1466319ff66274e1ad1463
|
||||
vendor/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted_test.go type=file mode=0664 size=2464 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=d3d0f9559db93771161657b268eafa6de1e10b28109735913755ee89d9fe717e
|
||||
vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go type=file mode=0664 size=7244 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.436900946 sha512256digest=1103b66704f3a5de5cddad6ffa2a2ee957e6392b9737676a87549cf1c212525b
|
||||
vendor/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted_test.go type=file mode=0664 size=2811 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=c89d1f8b13174327621f0cbf286a71e67c6b31e1e2ac37c66b1c6269eb2d1835
|
||||
vendor/golang.org/x/crypto/openpgp/packet/userattribute.go type=file mode=0664 size=2534 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=1df2601d86a3a7445f056a6d0cefb98c8f3a4a195e359b69703fad3868d94460
|
||||
vendor/golang.org/x/crypto/openpgp/packet/userattribute_test.go type=file mode=0664 size=5873 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=9491f937bb41e35da2c67f9e4a3ac1c18b735d85d48494dfb3daebb2b2052b5c
|
||||
vendor/golang.org/x/crypto/openpgp/packet/userid.go type=file mode=0664 size=3518 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=a6a4cdb224eff2e9713b00d5f6d582a5ad0440ed9250a4983511bce2b4f5fc0e
|
||||
vendor/golang.org/x/crypto/openpgp/packet/userid_test.go type=file mode=0664 size=2308 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=3e8f041dc105a2d807bf2634e384ab814a778538f6d82d5af95b8b777ffddf68
|
||||
vendor/golang.org/x/crypto/openpgp/read.go type=file mode=0664 size=13011 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=4b42631372013dccdcf71ba38486d471c34488c38985e44e9abc4ce91a14e1ad
|
||||
vendor/golang.org/x/crypto/openpgp/read_test.go type=file mode=0664 size=35594 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=d28d4c839d61daa75ca7cb21b7875f84e61cadaa0840a088c1407eac8b5bafac
|
||||
vendor/golang.org/x/crypto/openpgp/s2k type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941
|
||||
vendor/golang.org/x/crypto/openpgp/s2k/s2k.go type=file mode=0664 size=7191 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=e605aac7209f947fac16b9dcf88bd439d84664b472976cda5ddcdaf738a83ab5
|
||||
vendor/golang.org/x/crypto/openpgp/s2k/s2k_test.go type=file mode=0664 size=3244 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=982e06de36758a70d17db7a1ac412ac30668e469372cc388b0374e537109ad16
|
||||
vendor/golang.org/x/crypto/openpgp/write.go type=file mode=0664 size=11569 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=0053b773963b78dc0202155677dc09906ef6e7bc1f70fc53935fd6e95c1cf1da
|
||||
vendor/golang.org/x/crypto/openpgp/write_test.go type=file mode=0664 size=5769 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=6452bddf4da69773644d31fccdfee40da8d70e35d4439947bd48732a7214a79f
|
||||
vendor/golang.org/x/crypto/otr type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936
|
||||
vendor/golang.org/x/crypto/otr/libotr_test_helper.c type=file mode=0664 size=5538 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.437900941 sha512256digest=4d83e23d963cb62e04f9939e1d4d347ef2bd70cc1fda8e6fa659fe8dee266e8a
|
||||
vendor/golang.org/x/crypto/otr/otr.go type=file mode=0664 size=35866 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=1726c522c18197e8cfa6052b1bfe54038faeca2fdb49a593c6edff446cd3ab2e
|
||||
vendor/golang.org/x/crypto/otr/otr_test.go type=file mode=0664 size=12128 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=8732ddf210b53102f7040b02ee3943215bcb3f69269874d4db5d1c0d86d40a45
|
||||
vendor/golang.org/x/crypto/otr/smp.go type=file mode=0664 size=11982 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=468f2cb9f1bf77631d1eb7c96bbc4a574bda0b38b8db41572db534a8ccf76ee7
|
||||
vendor/golang.org/x/crypto/pbkdf2 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936
|
||||
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go type=file mode=0664 size=2484 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=e4327789933349b342bc7e78ca8819563f51f6676095aacd1ab9e47e2b5b126e
|
||||
vendor/golang.org/x/crypto/pbkdf2/pbkdf2_test.go type=file mode=0664 size=3261 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=b73297d2911a29e1ba5d8784c9dc17dd6dc6f8289fbe287e6d204a3714676982
|
||||
vendor/golang.org/x/crypto/poly1305 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936
|
||||
vendor/golang.org/x/crypto/poly1305/const_amd64.s type=file mode=0664 size=1585 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=868380bb86c6df5d26d1ac3d2875b093b18b8f88a9def5615d51da76e086c604
|
||||
vendor/golang.org/x/crypto/poly1305/poly1305.go type=file mode=0664 size=1280 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=374d648ea2f6c58d942ba38a88dc060d3aa484364383c36fbcab54e0fab8fd13
|
||||
vendor/golang.org/x/crypto/poly1305/poly1305_amd64.s type=file mode=0664 size=8501 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=d58606faf9490f1de48fa186129b1c6deada09b29807156fede97fa1b4411ad8
|
||||
vendor/golang.org/x/crypto/poly1305/poly1305_test.go type=file mode=0664 size=1548 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=7d5e1af6be7fe92af67f49b67e6677ec9b0f4b1e128e7312a5e101cefc683095
|
||||
vendor/golang.org/x/crypto/poly1305/sum_amd64.go type=file mode=0664 size=690 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=2a77e9768db8741fc9d523e2d35893c09167caca46a18312ad5e9d63dd77e0b9
|
||||
vendor/golang.org/x/crypto/poly1305/sum_ref.go type=file mode=0664 size=20830 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=9d61336ce2b45e30daa00573ffee77a7fb098dc6e195da6266b390dec067d6dd
|
||||
vendor/golang.org/x/crypto/ripemd160 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936
|
||||
vendor/golang.org/x/crypto/ripemd160/ripemd160.go type=file mode=0664 size=2476 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=eb2bffaae443aef72931d6bd212aecd0be13349f675b7e6f4f9800cde40e8bb1
|
||||
vendor/golang.org/x/crypto/ripemd160/ripemd160_test.go type=file mode=0664 size=1791 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=10141ac7e5b05e08e4e1866646f7df2df55e9fe95f1dde32b106987d0b0b0480
|
||||
vendor/golang.org/x/crypto/ripemd160/ripemd160block.go type=file mode=0664 size=4257 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.438900936 sha512256digest=b9f1981e13546cc75bfb1db199fba960388718176950c5857508dc3a9fd8c997
|
||||
vendor/golang.org/x/crypto/salsa20 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931
|
||||
vendor/golang.org/x/crypto/salsa20/salsa type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931
|
||||
vendor/golang.org/x/crypto/salsa20/salsa/hsalsa20.go type=file mode=0664 size=4098 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=a08dc45427b8ff5b1f3ac216b4b7e21c6739afc2d0ae4d18a70a8f7cc8b6e352
|
||||
vendor/golang.org/x/crypto/salsa20/salsa/salsa2020_amd64.s type=file mode=0664 size=13929 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=ca7bc54db396ea5222791c76ba9a95572d09e91861aeb48664faa84ea346d53c
|
||||
vendor/golang.org/x/crypto/salsa20/salsa/salsa208.go type=file mode=0664 size=4983 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=9825dfd3b0ccb54e5900e62595ac931ef987e7319aa19217edb7c1a473074ea4
|
||||
vendor/golang.org/x/crypto/salsa20/salsa/salsa20_amd64.go type=file mode=0664 size=763 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=f551fd3a1a02b4f47a62fc7e2af30ac1b23dc49d6e8977f6f7bdf2dc1db9627d
|
||||
vendor/golang.org/x/crypto/salsa20/salsa/salsa20_ref.go type=file mode=0664 size=5752 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=d8b188cbf0ba29e6945f334b1103bb8b2e146cd4f82413208633c51006d4aca8
|
||||
vendor/golang.org/x/crypto/salsa20/salsa/salsa_test.go type=file mode=0664 size=1149 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=53026559296b4a3776bc81922b0c3fe5ef4e5835a0d62874b566108c2080a6e2
|
||||
vendor/golang.org/x/crypto/salsa20/salsa20.go type=file mode=0664 size=1919 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=2620142a4713ab1a21da3ff76edfe9c115cf2dc2db8307b9f208fa96f5280780
|
||||
vendor/golang.org/x/crypto/salsa20/salsa20_test.go type=file mode=0664 size=3771 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=6ff0e6274b13e84f03e7d96f745f5839b566d02a43b39a232cf430da682b852f
|
||||
vendor/golang.org/x/crypto/scrypt type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931
|
||||
vendor/golang.org/x/crypto/scrypt/scrypt.go type=file mode=0664 size=5866 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=47d016aa178d7e4195a8351dbe9015c26aac1b89a375ae5b98d15ea5f67b8b79
|
||||
vendor/golang.org/x/crypto/scrypt/scrypt_test.go type=file mode=0664 size=4455 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=ee694200743f2fd4bc26f4ae4368cbd307b49399d1e1e2c9bfe195e7ce296b4a
|
||||
vendor/golang.org/x/crypto/sha3 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.440900925
|
||||
vendor/golang.org/x/crypto/sha3/doc.go type=file mode=0664 size=3190 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=af288b26f47e4f23bfd02a0e907315dfa4a60269189c640ef75ddaf2e5c5169f
|
||||
vendor/golang.org/x/crypto/sha3/hashes.go type=file mode=0664 size=1909 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=e292f93708072e3e09bb21049046c8e2edea434f6679eb85137c2835ed406b95
|
||||
vendor/golang.org/x/crypto/sha3/keccakf.go type=file mode=0664 size=9920 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=64ef19e0d0b7504d6123fcd375b7dd33394484ec73b26a7db785740bf79cc053
|
||||
vendor/golang.org/x/crypto/sha3/register.go type=file mode=0664 size=413 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=b73fb07d6b614529c74265aee400e404e973955e3bc979707061d6ebdb7c8b14
|
||||
vendor/golang.org/x/crypto/sha3/sha3.go type=file mode=0664 size=5821 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=5793dbb766e0ae6d54a40098e9cdfab28300525f6365431f7d36f6b32d43b265
|
||||
vendor/golang.org/x/crypto/sha3/sha3_test.go type=file mode=0664 size=8887 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=0d7139bbc4eff00afc1b5bbb940c8f42ce7eb660bbc4da7abb95443c9a5e8a33
|
||||
vendor/golang.org/x/crypto/sha3/shake.go type=file mode=0664 size=1847 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931 sha512256digest=3c48cd175445e52f95bfa9e874564bad0f548eaf57aafab2355f05bfbe535880
|
||||
vendor/golang.org/x/crypto/sha3/testdata type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.439900931
|
||||
vendor/golang.org/x/crypto/sha3/testdata/keccakKats.json.deflate type=file mode=0664 size=521342 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.440900925 sha512256digest=e1c6d2c9f1b72c3cb256ed89d792b5d45d9918b0be9167e18c087a1bea40a1c7
|
||||
vendor/golang.org/x/crypto/sha3/xor.go type=file mode=0664 size=397 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.440900925 sha512256digest=e9715de8a4cb248f43844a9f7be46f108d63f4cc4b86da447359edec12721dda
|
||||
vendor/golang.org/x/crypto/sha3/xor_generic.go type=file mode=0664 size=680 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.440900925 sha512256digest=929eb30da60b8fb2b8bad6fcb3576475f25f70fba1a0d6a8130a0fed732c54cc
|
||||
vendor/golang.org/x/crypto/sha3/xor_unaligned.go type=file mode=0664 size=1066 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.440900925 sha512256digest=08f85fd66c354189243f1dc99c00282fb105b7e2b3d40055cc73a6f1382a66a2
|
||||
vendor/golang.org/x/crypto/ssh type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899
|
||||
vendor/golang.org/x/crypto/ssh/agent type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920
|
||||
vendor/golang.org/x/crypto/ssh/agent/client.go type=file mode=0664 size=13381 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.440900925 sha512256digest=f0cf9fec51893c03e7e4894c0bdb53e473e2190faf6956ab2d68b68c5c6c01ea
|
||||
vendor/golang.org/x/crypto/ssh/agent/client_test.go type=file mode=0664 size=6902 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.440900925 sha512256digest=a1e796a312222c67871b658d365d7820a0022dcff66a0998f2d2474c9e5ca814
|
||||
vendor/golang.org/x/crypto/ssh/agent/forward.go type=file mode=0664 size=2214 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=e631ebeaf66b3071580abd406bc662fb68d208557c0f95fe2d51529ac60db2ea
|
||||
vendor/golang.org/x/crypto/ssh/agent/keyring.go type=file mode=0664 size=3636 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=2850e7217bfe6a03a7579dbb8f431eb4a01d5bd5ff2bf00e297e730b330547a6
|
||||
vendor/golang.org/x/crypto/ssh/agent/server.go type=file mode=0664 size=4451 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=e02ea70abbb9b3f5ccf8fa80df2e8632611f1a595f51eff438bf78402e7fc0dc
|
||||
vendor/golang.org/x/crypto/ssh/agent/server_test.go type=file mode=0664 size=1646 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=eee9c87cd6b27f9815964bb42c034b60b465da7d2a75b583dcb33bec1e384155
|
||||
vendor/golang.org/x/crypto/ssh/agent/testdata_test.go type=file mode=0664 size=2223 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=ce5d6a9d303ce20d7db0cef98fc4fdf33e28029c10403be48c433eef1da48bfe
|
||||
vendor/golang.org/x/crypto/ssh/benchmark_test.go type=file mode=0664 size=2240 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=cacb493253628206c39e6971ba955347736f6770c13aa8e680f9321dfb165c14
|
||||
vendor/golang.org/x/crypto/ssh/buffer.go type=file mode=0664 size=2198 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=824c0c8e8f221a68a105bb08a3eb1c3d9f3849fe0485b571029e94c79133d7ed
|
||||
vendor/golang.org/x/crypto/ssh/buffer_test.go type=file mode=0664 size=2220 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=5ae9c9646f732ef5ec86e7d9768327e9f5e35e2c4fd1ae78373390de0131267e
|
||||
vendor/golang.org/x/crypto/ssh/certs.go type=file mode=0664 size=12773 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=5dddd8a2d3d212015434995d9d50279771cbcaf549a3531280b9acecc893bcd9
|
||||
vendor/golang.org/x/crypto/ssh/certs_test.go type=file mode=0664 size=4632 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=df67d08e8abee28ef6ec5e6886956559c6d283716b43da2a6d98ad47d64de277
|
||||
vendor/golang.org/x/crypto/ssh/channel.go type=file mode=0664 size=16003 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=5883b21db7cf6bac7544353fa856d4751e5b6ef7f6e6afb55947344cd2b74787
|
||||
vendor/golang.org/x/crypto/ssh/cipher.go type=file mode=0664 size=8992 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=1f9628e738d59032d159b67f2ce49feeec056f095bfe62b08e67c507c3a5ac26
|
||||
vendor/golang.org/x/crypto/ssh/cipher_test.go type=file mode=0664 size=1395 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=0179df5b60e6a8080e4b31a45e6f4345fb8f4dc8f4cee7bc9ae99826afeeb108
|
||||
vendor/golang.org/x/crypto/ssh/client.go type=file mode=0664 size=6054 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.441900920 sha512256digest=d30f0d102026bb78cb7d8013ae974c8fee1281d0f6b7270b18c76175ec7b9244
|
||||
vendor/golang.org/x/crypto/ssh/client_auth.go type=file mode=0664 size=11942 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=6914b2e6396f7ef7072783cbded541cae898eda0758a61e9667e0bfd62fe10ad
|
||||
vendor/golang.org/x/crypto/ssh/client_auth_test.go type=file mode=0664 size=10085 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=d7b67bfec3559ccb5d7a811da5567c44bea404a8dade62e10bca21f047785588
|
||||
vendor/golang.org/x/crypto/ssh/client_test.go type=file mode=0664 size=969 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=8271743ef8df9d930d98e895e3d65ab558d3c6c5203445f7bfb58c81a35ea031
|
||||
vendor/golang.org/x/crypto/ssh/common.go type=file mode=0664 size=9173 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=9a8f921bd96313452816683dc0d1b1974f3130275080df236369feb88e1f84f7
|
||||
vendor/golang.org/x/crypto/ssh/connection.go type=file mode=0664 size=3457 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=811635959bfe6c462844d501b5c9784a100ee99d9742a74989ccb7e88f5b98b1
|
||||
vendor/golang.org/x/crypto/ssh/doc.go type=file mode=0664 size=771 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=5ec367435a18dfdece2fea6f2ee3694bfb9d04724bfa745c9eb0fbc26114225e
|
||||
vendor/golang.org/x/crypto/ssh/example_test.go type=file mode=0664 size=5530 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=f822ecf3a659336d45e16e78ac296ff0e8e785443cd1f7f1d99e9ea1d01c6a9c
|
||||
vendor/golang.org/x/crypto/ssh/handshake.go type=file mode=0664 size=9852 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=fb21b28cab3bbfc6909905318125dd8c1f4107a8c3967d4e71ece7f7fbd47b11
|
||||
vendor/golang.org/x/crypto/ssh/handshake_test.go type=file mode=0664 size=7343 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=5597f84d0eaf571a593a2894a0ea4378d13b6deabf2e173bd351c2a6283500c9
|
||||
vendor/golang.org/x/crypto/ssh/kex.go type=file mode=0664 size=10119 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=54ff5406781208f812ebd48b5abd4830d607b7860b6805535a34f0cd7a12bdde
|
||||
vendor/golang.org/x/crypto/ssh/kex_test.go type=file mode=0664 size=1048 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.442900915 sha512256digest=0f699b3226d66922f91f1f61855e5ae8a13c35a364ef84dd296a8de0ce08ad0c
|
||||
vendor/golang.org/x/crypto/ssh/keys.go type=file mode=0664 size=15079 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=f0ff3e0127bb3dab71fbcd390d80147d3a58c02076d4a19387e04b293a985247
|
||||
vendor/golang.org/x/crypto/ssh/keys_test.go type=file mode=0664 size=8724 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=9c453fc0419b8e7d9e45b42e08958f7816a2671a5be4181a2a89c5391eaa1faa
|
||||
vendor/golang.org/x/crypto/ssh/mac.go type=file mode=0664 size=1088 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=1ec064847ffa0c8559d0fdc2427a7758800b7628721a6a27b5b0ca0fc281aa45
|
||||
vendor/golang.org/x/crypto/ssh/mempipe_test.go type=file mode=0664 size=2020 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=b5531caedc0a29970ab0b8b138b0da7452e8f548eafb86cb531a647e8e758dcc
|
||||
vendor/golang.org/x/crypto/ssh/messages.go type=file mode=0664 size=16798 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=2ca5a71322c6d54b3bce36c1f71b4432b5301ca0d8c264d0e9629437d87d6f27
|
||||
vendor/golang.org/x/crypto/ssh/messages_test.go type=file mode=0664 size=5236 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=54e422acfe5043cbd4e51bd0eaa8ac95a8c9316536d9fc2d3c4389957bfd6ba1
|
||||
vendor/golang.org/x/crypto/ssh/mux.go type=file mode=0664 size=7977 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=20cc881c08d1895a3726903e325f57b0a7a9f05173fef90f283c1bd952915558
|
||||
vendor/golang.org/x/crypto/ssh/mux_test.go type=file mode=0664 size=11321 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=a41bba8d62eee12dd424381fad56b20c7a19d90d027280a31199bdfcb0ad71ca
|
||||
vendor/golang.org/x/crypto/ssh/server.go type=file mode=0664 size=14515 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=510d9f14aa556c282c776ef8208b4bfe587cd3a5382bc8554ae2e9b024f004f2
|
||||
vendor/golang.org/x/crypto/ssh/session.go type=file mode=0664 size=14236 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.443900909 sha512256digest=d25a238f4a7a1c572ff862572dc714cb8e880c88ee5d762a36f2e22d59354d57
|
||||
vendor/golang.org/x/crypto/ssh/session_test.go type=file mode=0664 size=17398 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=2e6dff4f32b5894c0fcac7f1fcf98f99a892189149fab6693d627a3462f088e2
|
||||
vendor/golang.org/x/crypto/ssh/tcpip.go type=file mode=0664 size=10469 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=e254e27950bf25e48b6181acabe242942cb89eac6968b910e5ce8a09a42a7253
|
||||
vendor/golang.org/x/crypto/ssh/tcpip_test.go type=file mode=0664 size=496 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=8d5211a3008ca53f120be7988d5f15071a96a968db72d21a9304e9ddc0b1a9d8
|
||||
vendor/golang.org/x/crypto/ssh/terminal type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904
|
||||
vendor/golang.org/x/crypto/ssh/terminal/terminal.go type=file mode=0664 size=20992 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=9da93725ad97cb0b7984fdec297dc30db5c8172a2b31ccc177cc74299fea822e
|
||||
vendor/golang.org/x/crypto/ssh/terminal/terminal_test.go type=file mode=0664 size=5334 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=c9037e9bb9f3f70177437fcb09679e01916b3b83e33c0251d3cc3432bfecd8ae
|
||||
vendor/golang.org/x/crypto/ssh/terminal/util.go type=file mode=0664 size=4025 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=01d19c877d21a1ff519f6a056b3595a6bcfeb83f5cc03731344cb54aa6304cf2
|
||||
vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go type=file mode=0664 size=332 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=7bce0ed5ed05201c9a7ca18f5073473633730ac2d1bad9a12ff10fc3c7e92fa7
|
||||
vendor/golang.org/x/crypto/ssh/terminal/util_linux.go type=file mode=0664 size=457 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=82819f84cebea56c71d7c24967ebbd2bef70998637cc62ca3f6d5f35e6a762d8
|
||||
vendor/golang.org/x/crypto/ssh/terminal/util_windows.go type=file mode=0664 size=4423 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=2b0d8fdaab4bbae108661eeb457f518bcf80d48cf67fb30cb9bae142c5aff17b
|
||||
vendor/golang.org/x/crypto/ssh/test type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904
|
||||
vendor/golang.org/x/crypto/ssh/test/agent_unix_test.go type=file mode=0664 size=1271 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=bbbcefe888ef99fc65b74f91b65a7b096193333fdac3dcca2a90170b87f52756
|
||||
vendor/golang.org/x/crypto/ssh/test/cert_test.go type=file mode=0664 size=1096 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=8b54dbb9c3bcaec5e60f8cd236022e623dab8331018077779ceab532f43b5c87
|
||||
vendor/golang.org/x/crypto/ssh/test/doc.go type=file mode=0664 size=309 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=f76d97f2da6554ee7f72617707e38419f05b2a5527fcc7993a19b2f6d51939e2
|
||||
vendor/golang.org/x/crypto/ssh/test/forward_unix_test.go type=file mode=0664 size=3297 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=fa6d2a1d93fbefd55d8179b63f22d7a9ab31d1f25fcf8da863e62199c7f6301a
|
||||
vendor/golang.org/x/crypto/ssh/test/session_test.go type=file mode=0664 size=7075 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=922e4fa0837c9c067eef33c6da1f36726bc9a761380e40a44625d6431c380f56
|
||||
vendor/golang.org/x/crypto/ssh/test/tcpip_test.go type=file mode=0664 size=791 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=5cbfc65be04f9191137589879bfc14350cbbfdd504642b08c37522472853a899
|
||||
vendor/golang.org/x/crypto/ssh/test/test_unix_test.go type=file mode=0664 size=5877 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=4e7660d40f097c87d9ab815a7bc2ea6bf2470fbd7a14f19c23244974408e5d16
|
||||
vendor/golang.org/x/crypto/ssh/test/testdata_test.go type=file mode=0664 size=2222 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.444900904 sha512256digest=d586900390d00c0c02cfac0cf2b32a371c7cc36ef6f0dea8023979ab154febec
|
||||
vendor/golang.org/x/crypto/ssh/testdata type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899
|
||||
vendor/golang.org/x/crypto/ssh/testdata/doc.go type=file mode=0664 size=418 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=24b5b64697da8d1742f071127c836efe4a0a4834589b1c5bb376741d51478f10
|
||||
vendor/golang.org/x/crypto/ssh/testdata/keys.go type=file mode=0664 size=1916 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=19a366ead0a070dd9f01411b02b3fd6fdf4bed6b3ed613dd1665d6fe79afb675
|
||||
vendor/golang.org/x/crypto/ssh/testdata_test.go type=file mode=0664 size=2158 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=fac0ad360dd0f1a99bde8cb772ba9b906a66facddc6e18f8146444fc192fccaf
|
||||
vendor/golang.org/x/crypto/ssh/transport.go type=file mode=0664 size=8703 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=3ccccff7d8faf1623e75a548fda6517d0871cce2f82b15a0abb1cf4773684009
|
||||
vendor/golang.org/x/crypto/ssh/transport_test.go type=file mode=0664 size=2581 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=e128680809ae8ced7c8b5bd5e58aa4cc813f67c96b47737aec050376f37ea8e1
|
||||
vendor/golang.org/x/crypto/twofish type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899
|
||||
vendor/golang.org/x/crypto/twofish/twofish.go type=file mode=0664 size=11966 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=36212f712cd64514191deafcb83cc0c03c98352e8a9e8d808318e41d1e6fffb4
|
||||
vendor/golang.org/x/crypto/twofish/twofish_test.go type=file mode=0664 size=4851 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=cacd3e89572a5f5ab2c981dbc3151b6cb7a29567e7de4a9c6a7d3635486f3224
|
||||
vendor/golang.org/x/crypto/xtea type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899
|
||||
vendor/golang.org/x/crypto/xtea/block.go type=file mode=0664 size=1807 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=c34c9ca0e5e39db0ee2ca0b19278176979e8eaef082bd610e5c55bb1befb09bb
|
||||
vendor/golang.org/x/crypto/xtea/cipher.go type=file mode=0664 size=2436 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=77780a45a1a5a3fc72807552bd9ebc5e7d1bf8d34dafbe1c818c439f252c48ce
|
||||
vendor/golang.org/x/crypto/xtea/xtea_test.go type=file mode=0664 size=7417 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=eaf72d92ac9c3bd17af55ce576ab48641af2d4ef138231800f9daddc4ad298b4
|
||||
vendor/golang.org/x/crypto/xts type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899
|
||||
vendor/golang.org/x/crypto/xts/xts.go type=file mode=0664 size=4500 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=b13eeec77e4f1290c5a6819ced5dbc0a90499961fa3738441679450f064c5abf
|
||||
vendor/golang.org/x/crypto/xts/xts_test.go type=file mode=0664 size=8511 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=b03cbba79c93ad20dedd41dc2416634da95dafd4cb1fded45e37c1e72f43357f
|
||||
vendor/golang.org/x/sys type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783
|
||||
vendor/golang.org/x/sys/.gitattributes type=file mode=0664 size=345 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=121f311806f70b4e9278a7a7178bbb73cb8afc0ce20d6134f24cf6a9f637cf85
|
||||
vendor/golang.org/x/sys/.gitignore type=file mode=0664 size=84 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=c1bc3cda1eb014d25dbebe52e50c7231f60762092811d111e2b60b2a847984d5
|
||||
vendor/golang.org/x/sys/AUTHORS type=file mode=0664 size=173 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=2e6b61f243b5f5e42e480c2d4588f1538ff08617ce66c01e99589333d4199a2e
|
||||
vendor/golang.org/x/sys/CONTRIBUTING.md type=file mode=0664 size=1031 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.445900899 sha512256digest=9f99f8daf18ff8de732527ac23231c253c9800139002bf2b03250fce7372fdfb
|
||||
vendor/golang.org/x/sys/CONTRIBUTORS type=file mode=0664 size=170 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=261a74c469ed7777fd933f3cd1544b351d162ad51d5cedb570aab4da0fbc62d7
|
||||
vendor/golang.org/x/sys/LICENSE type=file mode=0664 size=1479 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=ea1cff0ac6655ef2801edc8f18ba083c7fae87b0fb0c542ea1d9628e46992f19
|
||||
vendor/golang.org/x/sys/PATENTS type=file mode=0664 size=1303 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=2ea07f77ca1c22dc417acfa6942f0faf5f545f44bca4c9387dab0e6e5ca442af
|
||||
vendor/golang.org/x/sys/README.md type=file mode=0664 size=628 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=4181c831648424320a2021eabe96ba5dd22b4ea951fe7adce5d5826ce05f005e
|
||||
vendor/golang.org/x/sys/codereview.cfg type=file mode=0664 size=21 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=6c7d6dcbcc4c211ab4c82aa98d2b61bf9c5fcbc72e607e6800eb85d3af3c6b1a
|
||||
vendor/golang.org/x/sys/plan9 type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888
|
||||
vendor/golang.org/x/sys/plan9/asm.s type=file mode=0664 size=215 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=5a3d197e8fabcda93134bcc11c5bdb171a6df3c4b01587cafb92b96cdcb4a8fa
|
||||
vendor/golang.org/x/sys/plan9/asm_plan9_386.s type=file mode=0664 size=702 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=22ba41e4bef8ee9849a1f995db164ffc5fa32e730923ce754017bcb35c4a063d
|
||||
vendor/golang.org/x/sys/plan9/asm_plan9_amd64.s type=file mode=0664 size=704 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=f75d590eab0ea207a360d6d211053d4f4c0f968dc7cc7c11ae06cf82db367022
|
||||
vendor/golang.org/x/sys/plan9/const_plan9.go type=file mode=0664 size=1004 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=4dad6553dd157eedfe97742d095e19b018d9d184e0d9d8422380793a960e9082
|
||||
vendor/golang.org/x/sys/plan9/dir_plan9.go type=file mode=0664 size=5734 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=206de341d6fc697b39175f3a1e5075768c32a48c65e3f60245e006eceba5628d
|
||||
vendor/golang.org/x/sys/plan9/env_plan9.go type=file mode=0664 size=489 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=34cd7e466b514dc08f4d27b2013512251f922bd8f61f5061e5ffd2f5ceacd153
|
||||
vendor/golang.org/x/sys/plan9/env_unset.go type=file mode=0664 size=307 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=13fb763dd3caaa775e66ab434f8da4c08c31331246dee76ee1382682a7fd7f87
|
||||
vendor/golang.org/x/sys/plan9/errors_plan9.go type=file mode=0664 size=1566 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=3c668aec0f76b7f92911f18e86c8f92c4b441f64bdcf9fbefc452f0f6e4cca58
|
||||
vendor/golang.org/x/sys/plan9/mkall.sh type=file mode=0775 size=4168 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=cddd84a6e5b34f35544710f9c04ab57209bd82ff60ec62fed903a3f1864f302c
|
||||
vendor/golang.org/x/sys/plan9/mkerrors.sh type=file mode=0775 size=6072 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=e6e3e69647827ab44983cae89884bf4fea84cc834224f0f794fb305048d02923
|
||||
vendor/golang.org/x/sys/plan9/mksyscall.pl type=file mode=0775 size=8021 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=3b35c22a555900734ba4dde0a3d526429aef479e1b22c17a7da9937a8543d3ce
|
||||
vendor/golang.org/x/sys/plan9/mksysnum_plan9.sh type=file mode=0775 size=459 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=e0d11c3df396de0fe85337441832431bf5d68d225a81663d89c11ac8aff0c1c8
|
||||
vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go type=file mode=0664 size=372 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=83789ed1a05a8a89640f3e2e3a34a5767db34c9cb67cd887d23d85328ba7ae2d
|
||||
vendor/golang.org/x/sys/plan9/pwd_plan9.go type=file mode=0664 size=412 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=21b0d0b25128d2f05ad887b3f2a36980b50c30659b7afb267d50ebdaf247f3e6
|
||||
vendor/golang.org/x/sys/plan9/race.go type=file mode=0664 size=584 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=55e9443d6e17a1fd16548deab237b97309181fc5d8178945c80e7fe31fc773bf
|
||||
vendor/golang.org/x/sys/plan9/race0.go type=file mode=0664 size=447 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=95d252584ca609d6c95dabf4e55f45b82eb1bbf4f8baf08dc101965282d38502
|
||||
vendor/golang.org/x/sys/plan9/str.go type=file mode=0664 size=499 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.446900894 sha512256digest=cb3e9d88c984ca1df8e389478d118fde3daf8bef24b899cd0c514681b775a014
|
||||
vendor/golang.org/x/sys/plan9/syscall.go type=file mode=0664 size=2532 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=29d56098a10fb715a78c7e721d917c997858b0522b7620b1fb715d87d58d99be
|
||||
vendor/golang.org/x/sys/plan9/syscall_plan9.go type=file mode=0664 size=7182 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=1f341831c56d56bc6b551697fd34c141bcc48c986bef9f5d95065fbbd4954d23
|
||||
vendor/golang.org/x/sys/plan9/syscall_test.go type=file mode=0664 size=782 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=801d44414ede88e0edd6d5aadbffb3e1c1bbf0064482362c16be80dbfd762829
|
||||
vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go type=file mode=0664 size=6539 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=754df0ad7ec30111f18c0dd6f25be38db868c018517b418fefbbb93aa8a2d131
|
||||
vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go type=file mode=0664 size=6539 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=754df0ad7ec30111f18c0dd6f25be38db868c018517b418fefbbb93aa8a2d131
|
||||
vendor/golang.org/x/sys/plan9/zsysnum_plan9.go type=file mode=0664 size=1057 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=98b81b36ae64287761f468dd265073d3ef00fbffd959c726cedae03dfc582c0e
|
||||
vendor/golang.org/x/sys/unix type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783
|
||||
vendor/golang.org/x/sys/unix/.gitignore type=file mode=0664 size=6 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=1793186c7cc109798a73b563d7e04d626a832a2de511b47b91adbe2ccac72cc9
|
||||
vendor/golang.org/x/sys/unix/README.md type=file mode=0664 size=8271 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=89d4796bb14f104b6b537db55d1227024b8a497f69c65732a91a603462cb9ad6
|
||||
vendor/golang.org/x/sys/unix/asm_darwin_386.s type=file mode=0664 size=675 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=726d7e55ddedbf9c6cf9bbcadd5aad631e81afd4b87d14a3f1ae0ef86e42d470
|
||||
vendor/golang.org/x/sys/unix/asm_darwin_amd64.s type=file mode=0664 size=678 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=ec946919220894a801322261a56193f02134dceb6f4e2d95e2bcada391802d99
|
||||
vendor/golang.org/x/sys/unix/asm_darwin_arm.s type=file mode=0664 size=686 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=aba41df4736ad5cf04990b4a69e385752b2e471823672fcd981b70bb0b5656a5
|
||||
vendor/golang.org/x/sys/unix/asm_darwin_arm64.s type=file mode=0664 size=691 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=f8e8e3e17de0dee99358680292805ffd69d3183ad00b73f5072c9225ab2b6227
|
||||
vendor/golang.org/x/sys/unix/asm_dragonfly_amd64.s type=file mode=0664 size=681 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=dd2e709a396b4b3673456d4b6e9f307b52e96d50c3663e7e0cef6890fd126262
|
||||
vendor/golang.org/x/sys/unix/asm_freebsd_386.s type=file mode=0664 size=676 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=170cf69100db944eb055beb7144c3e4b3a43012b661f5b587044085cb081f471
|
||||
vendor/golang.org/x/sys/unix/asm_freebsd_amd64.s type=file mode=0664 size=679 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=74d959abf910b671bb9d5e96cf8fa1aeaf7ca256d30d33d1c1d2a99e4c2cb7e3
|
||||
vendor/golang.org/x/sys/unix/asm_freebsd_arm.s type=file mode=0664 size=666 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=0d34b34692ea2b2685ff9734874efe562d60eee613863ae1779c977a60b4f6c0
|
||||
vendor/golang.org/x/sys/unix/asm_linux_386.s type=file mode=0664 size=797 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=9428798ab945dacc931a7ae180a024024d09813dd25b504b8b7b4c602fe50f20
|
||||
vendor/golang.org/x/sys/unix/asm_linux_amd64.s type=file mode=0664 size=677 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=f67b38dcda396c7a22e2e8e01e0140dafb50d2cfc5192e024209a9072d9f85e4
|
||||
vendor/golang.org/x/sys/unix/asm_linux_arm.s type=file mode=0664 size=649 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.447900888 sha512256digest=b233d7ccf7cea17a8ac70792ddc90cc44de4c093ece28a24df58a443d01b0247
|
||||
vendor/golang.org/x/sys/unix/asm_linux_arm64.s type=file mode=0664 size=591 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=687a1f04da242234b2da964cff7760b8342a790714cea57ecf3f893eeb463a0d
|
||||
vendor/golang.org/x/sys/unix/asm_linux_mips64x.s type=file mode=0664 size=650 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=24e16b2816b8d2c41175442855da8ecd128be257983c5b272fee02406c6a0645
|
||||
vendor/golang.org/x/sys/unix/asm_linux_mipsx.s type=file mode=0664 size=706 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=eb204c30e3cd4d40bc2575a805f481723cb16d7ad1686bc6af26b45e837066c4
|
||||
vendor/golang.org/x/sys/unix/asm_linux_ppc64x.s type=file mode=0664 size=643 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=692e2be4962a6c6ff289b4a02d4060521a2a5a48db8a65be10df4f424b34e8ef
|
||||
vendor/golang.org/x/sys/unix/asm_linux_s390x.s type=file mode=0664 size=635 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=14dbb2e6754cef06aaef3eddc20eaaab03ed351676277dc89481fae30fb0a297
|
||||
vendor/golang.org/x/sys/unix/asm_netbsd_386.s type=file mode=0664 size=675 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=2c8fe2f54ba73c855e63cba8e1504a24e5ce227f8481476875a62e0201b74973
|
||||
vendor/golang.org/x/sys/unix/asm_netbsd_amd64.s type=file mode=0664 size=678 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=28ce95cab4f28b2092414dd84a4585518bbcca9b9463f95f1b085ccfa53f58be
|
||||
vendor/golang.org/x/sys/unix/asm_netbsd_arm.s type=file mode=0664 size=665 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=e8c0f5836faa5fd8f9fb2aa79e003a2b2b2da394005e6f290b813b9ed1dcd069
|
||||
vendor/golang.org/x/sys/unix/asm_openbsd_386.s type=file mode=0664 size=676 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=2941230ba6460c420d518a7e27ccac530b8b0d32ff30fe1ee8ab2a17fbb7f539
|
||||
vendor/golang.org/x/sys/unix/asm_openbsd_amd64.s type=file mode=0664 size=679 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=57b818fcb687ce814d466ad5acd30521b6d7e10d5a911255fafd7653198ce31b
|
||||
vendor/golang.org/x/sys/unix/asm_openbsd_arm.s type=file mode=0664 size=666 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=dbd94d1bd5d04c7649ccf9707768f7d84289acd010d91a843c630865382fcd4a
|
||||
vendor/golang.org/x/sys/unix/asm_solaris_amd64.s type=file mode=0664 size=426 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=3590238b4e35decd6b5711639be9c39f5695d5c9ef80066d5cdd9b381ceae9e0
|
||||
vendor/golang.org/x/sys/unix/bluetooth_linux.go type=file mode=0664 size=655 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=1030f9b73bad872abc822c99c2108640448b1f7c03cb3db7bf4b2f789b889880
|
||||
vendor/golang.org/x/sys/unix/cap_freebsd.go type=file mode=0664 size=5182 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=ae588894492046820482fd5f96d17df2ee54f4d8017d35288fa738c09312c17c
|
||||
vendor/golang.org/x/sys/unix/constants.go type=file mode=0664 size=285 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=226650163c679eb604fefed06d54064935afa764d1c71b176d357410137538de
|
||||
vendor/golang.org/x/sys/unix/creds_test.go type=file mode=0664 size=3321 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=a3a20f89c28e87d8177d8d36e5d90f81bfd74a5e4e8478a2428d69c41f911fe5
|
||||
vendor/golang.org/x/sys/unix/dev_darwin.go type=file mode=0664 size=747 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=cdfe857262a7bd5392c46560ca6053aa5ec3598a48bc23fac45b157a743ea1a2
|
||||
vendor/golang.org/x/sys/unix/dev_darwin_test.go type=file mode=0664 size=1314 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=b9960e3cd2a6250876c8085444bfd614ccd0c8b7685ad119613ffd59c58df9e4
|
||||
vendor/golang.org/x/sys/unix/dev_dragonfly.go type=file mode=0664 size=1030 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=d22f6b9fb00bba13993cb6a509063a08bfe1a50dbb87d7ae9a0e176c0e5d85a8
|
||||
vendor/golang.org/x/sys/unix/dev_dragonfly_test.go type=file mode=0664 size=1302 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=6aaa03562474c04427fbd3e2f4100c8a7a8407d98315331bea0b4c4111f6826d
|
||||
vendor/golang.org/x/sys/unix/dev_freebsd.go type=file mode=0664 size=1013 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=6cc8187f528496d2b80427f63870b4815411c93d38032b8234b5d0f14f44e9c1
|
||||
vendor/golang.org/x/sys/unix/dev_linux.go type=file mode=0664 size=1579 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=a494207bd423324ebc0da5715705ed36316826f1b438a373895e7238965b6af0
|
||||
vendor/golang.org/x/sys/unix/dev_linux_test.go type=file mode=0664 size=1381 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=6d237daeea888bacd345b866e8fec3a01681917c9939b2627039bbd1512a3a20
|
||||
vendor/golang.org/x/sys/unix/dev_netbsd.go type=file mode=0664 size=913 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=31f2d12ec605526c215bb1e4ef33493edce83d85465e4f8eb124180e9aaa62b1
|
||||
vendor/golang.org/x/sys/unix/dev_netbsd_test.go type=file mode=0664 size=1303 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=7d0f5c5a0ccec8fca4db6c881d8a2bce2e9d6b9946622dff603fa31ccd4350bd
|
||||
vendor/golang.org/x/sys/unix/dev_openbsd.go type=file mode=0664 size=918 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=a7105d3970f060444f792032fa6abb1b665eaf16affe402f7104638924bf4586
|
||||
vendor/golang.org/x/sys/unix/dev_openbsd_test.go type=file mode=0664 size=1358 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=1550df1cd7ab5733ce3518c28543653cf038240a1d194496e96d5d11aaeab5ad
|
||||
vendor/golang.org/x/sys/unix/dev_solaris_test.go type=file mode=0664 size=1291 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.448900883 sha512256digest=5a4430134bbd0ac48b5be8b9e26b6e1c33af7f6945420269f0804f648bde78a8
|
||||
vendor/golang.org/x/sys/unix/dirent.go type=file mode=0664 size=3057 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.449900878 sha512256digest=8d5d612436e8689fe1ef6ca7765dc222ed34fffba91695d297c1eefaba57e8c0
|
||||
vendor/golang.org/x/sys/unix/endian_big.go type=file mode=0664 size=236 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.449900878 sha512256digest=98f588e96ba180389c63564ce258a1b28c994254efc8e9f0bcb4fc38e0b766ae
|
||||
vendor/golang.org/x/sys/unix/endian_little.go type=file mode=0664 size=266 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.449900878 sha512256digest=44e5449191c33c3c060922c3b22bf4acb42e3f69cf2e69b1636d75cf7176e2a8
|
||||
vendor/golang.org/x/sys/unix/env_unix.go type=file mode=0664 size=546 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.449900878 sha512256digest=661bcc5bd8869a15493c501d7ec418c1ac284104b88bff972791a46dee7da169
|
||||
vendor/golang.org/x/sys/unix/env_unset.go type=file mode=0664 size=306 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.449900878 sha512256digest=9226f64ef3bfd65d64b7429cee4e005217b7394986f9437e835215a61d75b0d3
|
||||
vendor/golang.org/x/sys/unix/errors_freebsd_386.go type=file mode=0664 size=9407 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.449900878 sha512256digest=6fc461195892bfb1d33962525df409b3146510bfb15c08dcf3b0d80fe092f866
|
||||
vendor/golang.org/x/sys/unix/errors_freebsd_amd64.go type=file mode=0664 size=9407 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.450900873 sha512256digest=f692cb9906d464200afc73286693be255103c3d678c95b5c56e8053392aa310f
|
||||
vendor/golang.org/x/sys/unix/errors_freebsd_arm.go type=file mode=0664 size=9037 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=54ce31d5caa959c92c18969837a4f7a3367022dc2737a252ca40e03cc69b8e96
|
||||
vendor/golang.org/x/sys/unix/export_test.go type=file mode=0664 size=256 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=2d27fd7d03cb3f04853298cbb97c38c7f091e13d66ab125a06bf9a1af04d501c
|
||||
vendor/golang.org/x/sys/unix/file_unix.go type=file mode=0664 size=614 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=09f51f620292bf32fc719e0b5dfba865bb21fdfe616ecfa1f657deb4b89b01d5
|
||||
vendor/golang.org/x/sys/unix/flock.go type=file mode=0664 size=695 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=6f8716b07a762d2fec17e039d151c696f79a2ff785b869876225e49dd4f7fecd
|
||||
vendor/golang.org/x/sys/unix/flock_linux_32bit.go type=file mode=0664 size=388 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=b70a7ed1969b483abd3092542c733e49cfa438f5812355d46a1e79638cbf2ff0
|
||||
vendor/golang.org/x/sys/unix/gccgo.go type=file mode=0664 size=1541 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=5c4a055bb1739f79743979bcb1434756c8f88c999105d08efcabe721c09ae92d
|
||||
vendor/golang.org/x/sys/unix/gccgo_c.c type=file mode=0664 size=1014 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=d0cf40675ee3a585699e7b87b2f5226014a05b0ecafa0eafc118b76ed94460e4
|
||||
vendor/golang.org/x/sys/unix/gccgo_linux_amd64.go type=file mode=0664 size=430 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=4f17fad3cf09feab7b51390bc0d5658279e52eaf364c2f09d2a3cdf3d972bd93
|
||||
vendor/golang.org/x/sys/unix/linux type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867
|
||||
vendor/golang.org/x/sys/unix/linux/Dockerfile type=file mode=0664 size=2088 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=b1cdee4ca2e3c4ea900d74a214ccf9933459dadb8e77ea4dae2e1d9c989fccf5
|
||||
vendor/golang.org/x/sys/unix/linux/mkall.go type=file mode=0664 size=10396 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=f07d3398f2737c77d3eafabd67e123fbd67bd9f169aad1c510b768b1db56cddb
|
||||
vendor/golang.org/x/sys/unix/linux/mksysnum.pl type=file mode=0775 size=1988 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=7dc0702eb661cd3b34ff193a268efd1e5303874e27dbe98591b55003da9beae6
|
||||
vendor/golang.org/x/sys/unix/linux/types.go type=file mode=0664 size=15985 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=9e4e427155e5dc58c6befaa68306ccc5362d7ea4df2111b02695e784f4c62ebc
|
||||
vendor/golang.org/x/sys/unix/mkall.sh type=file mode=0775 size=6474 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=63dd2f0784095b4c8e034b4f77a0ebe2626402760fcddb56708aa4adbe669764
|
||||
vendor/golang.org/x/sys/unix/mkerrors.sh type=file mode=0775 size=13831 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=85fb5a9f405d302ae8bfb6b860216d2dde9f8ad1fccf7be90a27c54fe6fb28ac
|
||||
vendor/golang.org/x/sys/unix/mkpost.go type=file mode=0664 size=2631 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=f7503cd4d657be68ad4b27f072199a4922abcabd388bf913cf89fe372f55d0e9
|
||||
vendor/golang.org/x/sys/unix/mksyscall.pl type=file mode=0775 size=8238 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=615490be180ba5c249d5a058450f41ef2e313f9dc65101500300e42d5558ad11
|
||||
vendor/golang.org/x/sys/unix/mksyscall_solaris.pl type=file mode=0775 size=7108 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=9c9819f91d9a011e9153f73da3f3274a3f9b0f3c6b39cc6e7eb4f6af198ec429
|
||||
vendor/golang.org/x/sys/unix/mksysctl_openbsd.pl type=file mode=0775 size=5320 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=d4b831d026acb1140a375f8f372523f0c2e253e3cc90b31a78f3ab7830eadede
|
||||
vendor/golang.org/x/sys/unix/mksysnum_darwin.pl type=file mode=0775 size=751 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=914efb2614e12d6e0a4c84f90965acb3c95ab0cfd288ca1c5970c934f93837e7
|
||||
vendor/golang.org/x/sys/unix/mksysnum_dragonfly.pl type=file mode=0775 size=1039 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.451900867 sha512256digest=78d7f62dcb67c3cea6737b60f785320da2172ca5a9b991f65549e9d5492dfe50
|
||||
vendor/golang.org/x/sys/unix/mksysnum_freebsd.pl type=file mode=0775 size=1041 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=e5b9363a3a8a7ae6226791d186ef18d7fd97f1688ec6cec6a2af999c8526bc75
|
||||
vendor/golang.org/x/sys/unix/mksysnum_netbsd.pl type=file mode=0775 size=1227 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=94956e55a1dd70e86770e79d7f4d5b9d7f4f5b736d20985a1c2fa56d56ae987e
|
||||
vendor/golang.org/x/sys/unix/mksysnum_openbsd.pl type=file mode=0775 size=1044 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=965e4c689cb205ff7751d8725876c27b5e28093e54df670fe3618b48de498fad
|
||||
vendor/golang.org/x/sys/unix/mmap_unix_test.go type=file mode=0664 size=844 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=be10e286da104abc70f545f80f2bdf0d55b813961e7e26dcbf1859a15fcef5f5
|
||||
vendor/golang.org/x/sys/unix/openbsd_pledge.go type=file mode=0664 size=889 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=80567386299b0a684ae97d442f0f6ad8bfc857278a789bd180d6227d0996519a
|
||||
vendor/golang.org/x/sys/unix/openbsd_test.go type=file mode=0664 size=2504 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=8068ce28ec3caa3b3fa253e3a189cd6606f3783c58b37ce64868dd3a7d1b2f41
|
||||
vendor/golang.org/x/sys/unix/pagesize_unix.go type=file mode=0664 size=364 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=4e6f8f46f385e2042e25990999224cdba1cc244ec3015a3e776f6e1c9d73875f
|
||||
vendor/golang.org/x/sys/unix/race.go type=file mode=0664 size=608 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=8dc91f6210f0e238fcaf7f86b553e934510ac6c75ce91bdd203d7676b328ebab
|
||||
vendor/golang.org/x/sys/unix/race0.go type=file mode=0664 size=506 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=96dd85162b94b9f524d30d2eff3bd2e54d4ed10d91f995aa1284c6f885646bb2
|
||||
vendor/golang.org/x/sys/unix/sockcmsg_linux.go type=file mode=0664 size=1072 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=bf0d5db8b7493fc034c430fcfb4766417e8e1a7508c5c61bf7328fd38d78319f
|
||||
vendor/golang.org/x/sys/unix/sockcmsg_unix.go type=file mode=0664 size=3004 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=7447109f9cbbb656603d308640c10ea9dc9a6624ac15505061bce0de01493237
|
||||
vendor/golang.org/x/sys/unix/str.go type=file mode=0664 size=611 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=e866bc1a11f3c577f6aabd7938872dbf41b1397616ce5bfb8909f638a0f71000
|
||||
vendor/golang.org/x/sys/unix/syscall.go type=file mode=0664 size=2473 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=105fcb77117a424f60f49b34dc5704af19cfea69c30a2028e5eae6694b91668e
|
||||
vendor/golang.org/x/sys/unix/syscall_bsd.go type=file mode=0664 size=16412 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=0e4d9ee5c5f6eb0439c12adf6f10acdca195b7cf98414142cac45ac3c182c22e
|
||||
vendor/golang.org/x/sys/unix/syscall_bsd_test.go type=file mode=0664 size=1303 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=19be54172834153be6b2c4f34e52b3d3f01ca4e61ab5c26da468b298e7f63aee
|
||||
vendor/golang.org/x/sys/unix/syscall_darwin.go type=file mode=0664 size=14402 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=ea6c694a516dc46df716e4766b8ae289007316201f95c4a3af14153b0c1d3068
|
||||
vendor/golang.org/x/sys/unix/syscall_darwin_386.go type=file mode=0664 size=1894 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=129cf0ea8786a9565e45c6fd26a8c3b403cdd35c3727c2549794c3570f8939b3
|
||||
vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go type=file mode=0664 size=1842 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=9caa8d9d1f56fd7608ebf5f7155ab4627e8d39280e7a05a534ab83e1e34c7c4e
|
||||
vendor/golang.org/x/sys/unix/syscall_darwin_arm.go type=file mode=0664 size=1700 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=fbe3117680c808a16306792195dcd2c8800ec83f95abeef305e57e001f47a551
|
||||
vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go type=file mode=0664 size=1849 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.452900862 sha512256digest=2ca5636a82e97bcae9513a7f8b22407347bff8db57ddb0c4fa62b9200d6901f6
|
||||
vendor/golang.org/x/sys/unix/syscall_dragonfly.go type=file mode=0664 size=10661 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=9fa9eb3d3bffd845f80e382fe2f7ae12cb6cb6335f15968b0264ed78ac87f59b
|
||||
vendor/golang.org/x/sys/unix/syscall_dragonfly_amd64.go type=file mode=0664 size=1362 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=9b98e533283c20fdf289d378ed094a25d5d920177f4249be0e285b8790edf169
|
||||
vendor/golang.org/x/sys/unix/syscall_freebsd.go type=file mode=0664 size=18984 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=fa308081980b38bcae99d9336d68a57a5e405717a3fc3bf287e3d47466bf0eb7
|
||||
vendor/golang.org/x/sys/unix/syscall_freebsd_386.go type=file mode=0664 size=1400 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=63e7a69c785d52d9971b56301bbd643ae25f15fe2714af0bba63d228f38ee2ec
|
||||
vendor/golang.org/x/sys/unix/syscall_freebsd_amd64.go type=file mode=0664 size=1360 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=53165dacd7642909b00fac1562322f1f9c6dddfc4893d14d20202a4d0ea6c4ff
|
||||
vendor/golang.org/x/sys/unix/syscall_freebsd_arm.go type=file mode=0664 size=1379 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=7392bee062164a2c1c7a58c6e234e069b621e34e9526a75c375d52dd8061b581
|
||||
vendor/golang.org/x/sys/unix/syscall_freebsd_test.go type=file mode=0664 size=7071 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=83beba3058fbfcc62ee365664098affbd4026f62a9633995f43e1b319748507f
|
||||
vendor/golang.org/x/sys/unix/syscall_linux.go type=file mode=0664 size=42337 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=4a669a12101a434a4827c7beab6ff6dd69870dd02b51ed835401d1191e946a83
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_386.go type=file mode=0664 size=11026 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=f9292452892c5c9b530799d150410896293b71c5daaea8b9f730e596169e6626
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_amd64.go type=file mode=0664 size=5238 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=eff154f2580f743e1deb3c5e772d990e588a112b77dcb2bf5e7cac6362570cb6
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_amd64_gc.go type=file mode=0664 size=297 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=2de31b70481346aa26eabefedd55c255d897057031f117dc67283126b1ca86f7
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_arm.go type=file mode=0664 size=7748 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=d4e69a5cdc3862c3b359cb7ae760f7bd1c040c7263e2c4923cbf19576aa83efc
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_arm64.go type=file mode=0664 size=6022 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=6b65045f06e4cd07588e93bf6192eefee02836a6df663ac607fa470c81b2f444
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go type=file mode=0664 size=6287 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.453900857 sha512256digest=af605829a33d9c4641f38da8eedf83bf470b81819a0ce7389da8f98fe76df2bd
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go type=file mode=0664 size=7022 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=67221ce149fa75aba755dca6b05c1e1851970bedc2a735a1e0ababf538781618
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go type=file mode=0664 size=4996 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=b237b58fd9d190e6dfb9bc3a1ef4146a01152953a419d0e25969f6a2098b0d51
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_s390x.go type=file mode=0664 size=9951 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=f7435ad0eef255574c86faf1457d38d6c25cb6cf5b81c4d5d785666c473404ac
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go type=file mode=0664 size=5115 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=c85ef216ba3f9d8ed038f50d0be1ce5df683b940faa9bb042f2eee4c92abb75b
|
||||
vendor/golang.org/x/sys/unix/syscall_linux_test.go type=file mode=0664 size=4802 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=b2d63724d8ea6361a1dd883b846e088e812e7f6fb8bfb9fe8de85f549ac1306d
|
||||
vendor/golang.org/x/sys/unix/syscall_netbsd.go type=file mode=0664 size=11241 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=ac46c9fe0fbf79ac20f3efbe3c577a7b60f061b3606e99691dd2dc5d7720848a
|
||||
vendor/golang.org/x/sys/unix/syscall_netbsd_386.go type=file mode=0664 size=912 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=9da2c69933b98eb81fd581694d6588826320e336e140e4dd2c893abb8729c008
|
||||
vendor/golang.org/x/sys/unix/syscall_netbsd_amd64.go type=file mode=0664 size=914 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=3de21a18511954cacfe1ed2df2c8c5ef23c00c54cbd07a6d38c4ed4fdb92359c
|
||||
vendor/golang.org/x/sys/unix/syscall_netbsd_arm.go type=file mode=0664 size=912 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=3b01b576d4646af34ecb3dcd731d8ce4f622770c9529292bc610a4a7d56898b0
|
||||
vendor/golang.org/x/sys/unix/syscall_no_getwd.go type=file mode=0664 size=301 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=9b650a395b572244827e9fb8f5f4b247b1ec8ac99aeb3168c88714daf0ee4263
|
||||
vendor/golang.org/x/sys/unix/syscall_openbsd.go type=file mode=0664 size=7605 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=c32af07cf02dac38ffda73c11e9bc700ad25f3fc5938c1dbda8edc5ec5d0b081
|
||||
vendor/golang.org/x/sys/unix/syscall_openbsd_386.go type=file mode=0664 size=912 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=c18a20eded970b5ade02f631d6ce778981d7813841227b952ea451b1aab7acff
|
||||
vendor/golang.org/x/sys/unix/syscall_openbsd_amd64.go type=file mode=0664 size=886 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=3aa8446604313927f241cd5bf15f3ba1976a2228f60c7d127eae1e31f61c78e2
|
||||
vendor/golang.org/x/sys/unix/syscall_openbsd_arm.go type=file mode=0664 size=912 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=eabf9ae74565592ebcea748db2645a91ec5954fb9c002e59a83b1b985d35771e
|
||||
vendor/golang.org/x/sys/unix/syscall_solaris.go type=file mode=0664 size=20369 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=53d6595ad46ace1758496fe3de4a8fa9ae49567dd56735e6c34b664b504d355b
|
||||
vendor/golang.org/x/sys/unix/syscall_solaris_amd64.go type=file mode=0664 size=842 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=dd5eebc32ff0c38d41738cdf9c968698370335d652d955f2e047a67fbbe8bdbb
|
||||
vendor/golang.org/x/sys/unix/syscall_solaris_test.go type=file mode=0664 size=692 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=47fc7b857f1b6cad4f89c30c2b9221bddeb588ed69562b5200484ccbd0f533ae
|
||||
vendor/golang.org/x/sys/unix/syscall_test.go type=file mode=0664 size=1085 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=e7f53a051857dc28a36968e80916fd51f3f7a5ec68128d73677de215823f835b
|
||||
vendor/golang.org/x/sys/unix/syscall_unix.go type=file mode=0664 size=6739 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=af6e0879d0339ccc0a682dddd82ba2d9a2723a4f7eef85c0d5db7ca365a91551
|
||||
vendor/golang.org/x/sys/unix/syscall_unix_gc.go type=file mode=0664 size=606 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=e26eb2003583c1b39b4bbf2cce40efd46186717cb76a1baefd7afb5cd1193e3b
|
||||
vendor/golang.org/x/sys/unix/syscall_unix_test.go type=file mode=0664 size=8586 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.454900852 sha512256digest=122d71e217d0bfe6d9902e6da38cb5f1cf883e92be9c6031d785f2739dcf61e4
|
||||
vendor/golang.org/x/sys/unix/types_darwin.go type=file mode=0664 size=5260 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=7db87818277ea2692e348fab5d2947c0024f641a0f4fabfc5e3d1f7e5f9110e4
|
||||
vendor/golang.org/x/sys/unix/types_dragonfly.go type=file mode=0664 size=5117 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=c458d5be72f8c2fab144ede0d57e32fde2d47784684e670a86216f93688ee8b1
|
||||
vendor/golang.org/x/sys/unix/types_freebsd.go type=file mode=0664 size=8237 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=519ceed197f54aaf2be3fec476cdb3368d49829b98edbdee499ee50a09a9713e
|
||||
vendor/golang.org/x/sys/unix/types_netbsd.go type=file mode=0664 size=4840 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=b8f297577459faccf04bfc7d6eefc2e194e7f7a9533df36d94bb3f0619f0b81e
|
||||
vendor/golang.org/x/sys/unix/types_openbsd.go type=file mode=0664 size=5110 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=7e50488eeeeef3677d374ee767a44d397092c8e08c22ef29b0d8584084524ec3
|
||||
vendor/golang.org/x/sys/unix/types_solaris.go type=file mode=0664 size=5518 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=f0247afd6cb889629d0911a0b723cff193f8a903975304a58a7d68430538d748
|
||||
vendor/golang.org/x/sys/unix/zerrors_darwin_386.go type=file mode=0664 size=68231 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=a33eab019d5ccca084ac0b47a2017b4c0afe182f95a73b1876af95d8a95a24fe
|
||||
vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go type=file mode=0664 size=68233 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=0b33099abaf9c17a4b503726242048d2ae0f9fead8c8a9e72a04433bfb517b20
|
||||
vendor/golang.org/x/sys/unix/zerrors_darwin_arm.go type=file mode=0664 size=68221 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.455900846 sha512256digest=f3d204933352bb8b93397aa24b122b2295a6c630e81abe4c5c80eebf25ca3874
|
||||
vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go type=file mode=0664 size=68233 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.456900841 sha512256digest=632cab198b9b4221cd7f019baeeb51d669498c20459ce8f7c218fad29ee1431e
|
||||
vendor/golang.org/x/sys/unix/zerrors_dragonfly_amd64.go type=file mode=0664 size=63577 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.456900841 sha512256digest=274f5185e212d88c23c92211cdc1e6d21d33749cb66a6b11caf5247d0bba5146
|
||||
vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go type=file mode=0664 size=68239 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.456900841 sha512256digest=6370d1cbdbd2822263643ab854a6d778e535efe70b70eb7377daedaaf13dc17e
|
||||
vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go type=file mode=0664 size=68291 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.456900841 sha512256digest=688c406dcf94d3ef10362fdeaa70c2aba4173874608daa2f826e25a5494a59a3
|
||||
vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go type=file mode=0664 size=68597 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.457900836 sha512256digest=4b0c8e80f9d02f5624f0d923d1ad736473e10a6253dd63300938c25c9a21a915
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_386.go type=file mode=0664 size=97027 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.457900836 sha512256digest=a7644f0d89abe85929d9b559077b6e5195551cfe04fc4cac2bec5ee3608aebf9
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go type=file mode=0664 size=97065 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.457900836 sha512256digest=c8cbc2603231f3031c1b088be5fd8d1f8a3053294b3fed96ff1d5270c41b64a8
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_arm.go type=file mode=0664 size=97251 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.457900836 sha512256digest=f1df67cf7e1f51b6611c46c15cd2d5f0ccf52469845b949a6baac6074987d0c3
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go type=file mode=0664 size=96646 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.457900836 sha512256digest=1e771c581c2bc2fbed8bd31ab565279316d1aff64e263f44f53e876f1335bc97
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_mips.go type=file mode=0664 size=97708 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.457900836 sha512256digest=e84e872372021ae4f68cad00bd733439277c2a0138cc52b72c7716295e94bedc
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go type=file mode=0664 size=97695 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.458900830 sha512256digest=139793b02799b4dec2e8092dfdac401a811d2a584346d7609aa96b1b40c2c7b9
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go type=file mode=0664 size=97697 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.458900830 sha512256digest=b761cc77d4df5ba0eeb09ad814eea95e60527384026fed2853c0ee8502863502
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go type=file mode=0664 size=97710 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.458900830 sha512256digest=d103223fd93b4adbfec0112410433f152104a141e08af6a935db9b51be4a548b
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go type=file mode=0664 size=99998 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.458900830 sha512256digest=56ed15af23a0dab5293d80829146e4ba4fe4e59bb03e51c62870e4830b07cbbb
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go type=file mode=0664 size=100000 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.458900830 sha512256digest=bdcb442e3b0e2e10d775f68b1193440005ad6df5f72915167e32039f830b7c44
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go type=file mode=0664 size=99869 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.458900830 sha512256digest=f45253fe17e10e54a854489b6f9639efd8bf6bf8bc3216518365ea55cfe637db
|
||||
vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go type=file mode=0664 size=85936 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.458900830 sha512256digest=a828bcbef60831441c9cd00adf9eb3cc394215b1a3f54c662858f99a96ff595e
|
||||
vendor/golang.org/x/sys/unix/zerrors_netbsd_386.go type=file mode=0664 size=70116 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.459900825 sha512256digest=297a3d8d5122e49d512edac795448344a864fe7f37fa08173a7d582432ae5d46
|
||||
vendor/golang.org/x/sys/unix/zerrors_netbsd_amd64.go type=file mode=0664 size=69698 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.459900825 sha512256digest=89306c2c8bd2c274f8c8340ab22ca11a2a1fa287437ad2fc9de95948ca80757d
|
||||
vendor/golang.org/x/sys/unix/zerrors_netbsd_arm.go type=file mode=0664 size=69229 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.459900825 sha512256digest=b39f894f1fa33682ce96ea3e6c2914761d08c7860a140e99174cbc7802b55eb4
|
||||
vendor/golang.org/x/sys/unix/zerrors_openbsd_386.go type=file mode=0664 size=64910 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.459900825 sha512256digest=74a535ae53c0705d19df8a24417d538779c7850692057fa19b7fe9565af6f6a1
|
||||
vendor/golang.org/x/sys/unix/zerrors_openbsd_amd64.go type=file mode=0664 size=64866 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.459900825 sha512256digest=7f688c7b98a03330fcdb9edb862f015debaaf1ac306daa0804c91fc81f8d2c45
|
||||
vendor/golang.org/x/sys/unix/zerrors_openbsd_arm.go type=file mode=0664 size=64986 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.459900825 sha512256digest=90f59fe6b5039b204ddf528a716edb62692fdf5ec14cee26e97907792e24c74e
|
||||
vendor/golang.org/x/sys/unix/zerrors_solaris_amd64.go type=file mode=0664 size=55962 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=7bc25fa518a08c9714731f10c67d5a5e27bc2eeb92cf558cd076f5210ed9e6db
|
||||
vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go type=file mode=0664 size=38195 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=443808e7c6eb58eba34b01052820d465a522c7b7d097f043617def471f1147fc
|
||||
vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go type=file mode=0664 size=38048 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=f1ecaa7daed979f46aaeaf6a1f28fd3eaf26cd4324996ef0373a7609b3cd26dd
|
||||
vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.go type=file mode=0664 size=38188 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=57153855d62f86ccf8a6d6db363331c6029eeabf464f09e7309e4664cf208b60
|
||||
vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go type=file mode=0664 size=38048 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=853d6cda427e1498cfa5f78384f6b981aa6cfab2fe6de56182a304441986b063
|
||||
vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go type=file mode=0664 size=34135 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=f842c5256c4ff8b23f31ed27be09bc5dab493c2ecf6f7635f8afe760c4019ef7
|
||||
vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go type=file mode=0664 size=45674 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=435e7c5c2697ebd765574e0a23928170771f3d41f98ec8ce51da1b7483d84dfb
|
||||
vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go type=file mode=0664 size=45491 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=88b1a5d562da338b7cf49fc9e049732a4d908b20beb8e537c2b23c1aac3fb8f2
|
||||
vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go type=file mode=0664 size=45708 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.460900820 sha512256digest=a1ab37154ae91e251ec89abf2fa962a5e968dd6fbabc73df147362b264c23a9c
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_386.go type=file mode=0664 size=47539 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=1d6cdaddbfbfff8cf0b91199d3279dd4fcc00ea02c567bb61d6644c874765ae8
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go type=file mode=0664 size=52740 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=0286f20cdd76406e013fd105e108ff24c5face6760b234dba6a16493f6c22e7a
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go type=file mode=0664 size=50716 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=7dcd28210157bce72c6d7a7d1714c01e0f56d3a295ee1d0b3df98cea3c30ff97
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go type=file mode=0664 size=49734 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=2fa70856a5f0e76b3f156e96396398d4369f5b9348622e24f69554460a1029af
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go type=file mode=0664 size=52082 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=d8dcbbadbb2520889bf68fb67090977f8ab5f7cc7043357411b58cdf2398933e
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go type=file mode=0664 size=51395 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=b199f9b6172da6b70229faa4b1a9fc7b6356f5fc5765263738c7cb05f6499198
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go type=file mode=0664 size=51399 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=f78c0195de90b72579c5d3465637111a74367eb75cd73c7a2a7514c45fa6c2eb
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go type=file mode=0664 size=52086 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=5ad282683095915c99a9e33447e18d07693a728f844f006d931c145e533b6bae
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go type=file mode=0664 size=52934 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=bc14c21964f5fd05c75bf0e9ebb0279a3fba9d66a37f41e3a5138e4437bf3d93
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go type=file mode=0664 size=52938 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=0f75dcb00c83a30131cb57e37e58b6a1f542c00a3fe6ea1e0b2b241f48fe61e8
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go type=file mode=0664 size=46874 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=b743a983a377d31a24a9ba7ef5d31354fc3527d4dce84e54cdedd03aadabefb4
|
||||
vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go type=file mode=0664 size=44728 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=f26dc211f14c4f8035b73612982dfd90834e1c09aa989de65154962758dd6ffe
|
||||
vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go type=file mode=0664 size=31891 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=af9cdf91fe2c36164de4fa3ed038f14038bec076fc54af956fa25ea77457f9bd
|
||||
vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go type=file mode=0664 size=31743 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=e8b00003195ca4250a18778ec1f0ae164656b837d962d994325f3677f7f4b9dc
|
||||
vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go type=file mode=0664 size=31896 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=fdd1119582f350c753b2e6e7540190ac30a8b54ebe10d622f25bd18ca80bbe1f
|
||||
vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go type=file mode=0664 size=33300 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.461900815 sha512256digest=af635b3c4882ea5048da47441941aa0b3242d0476d810125ec467697eff2f97b
|
||||
vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go type=file mode=0664 size=33152 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=3d9a34af2dd1e304e24a37ffa2488241c8afd94e5ca3f4e79622b87c56cd59f0
|
||||
vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go type=file mode=0664 size=33305 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=0faea38dd503903cad40a920c758c98f59f2ee4e4bb6a6beba6b1e801b454523
|
||||
vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go type=file mode=0664 size=44500 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=3be412f7aa291690f4e12b92d23d7cbfa6ef6fe5bfb036f005a084af067a9f83
|
||||
vendor/golang.org/x/sys/unix/zsysctl_openbsd_386.go type=file mode=0664 size=11986 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=a0c871c1d9e2b175b93338b07b244b2bb654e9cdb0aa387994718f254db61ff1
|
||||
vendor/golang.org/x/sys/unix/zsysctl_openbsd_amd64.go type=file mode=0664 size=11986 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=a0c871c1d9e2b175b93338b07b244b2bb654e9cdb0aa387994718f254db61ff1
|
||||
vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm.go type=file mode=0664 size=11986 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=a0c871c1d9e2b175b93338b07b244b2bb654e9cdb0aa387994718f254db61ff1
|
||||
vendor/golang.org/x/sys/unix/zsysnum_darwin_386.go type=file mode=0664 size=16506 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=8bb84085428dfac3401a645f60f1aca81a630e1bd6522f3c53f71fd431f2f829
|
||||
vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go type=file mode=0664 size=16508 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=d9e8603cbdebe2f35133bb0fb2768d0ff5ce0f1f1d178990c2057fb320f0c49c
|
||||
vendor/golang.org/x/sys/unix/zsysnum_darwin_arm.go type=file mode=0664 size=17697 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=983db0f2fe7a6f5922d2b4dd42f0cbc00a8a464e3b5ac8129d2b6089441a62e9
|
||||
vendor/golang.org/x/sys/unix/zsysnum_darwin_arm64.go type=file mode=0664 size=17699 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=72b479827976db9d5aebf663b48839db56fd41a94ffacf70376ee40059eae328
|
||||
vendor/golang.org/x/sys/unix/zsysnum_dragonfly_amd64.go type=file mode=0664 size=24161 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.462900809 sha512256digest=c4db2065890efb27ea9db44a0ceab42791790c2e9e5bb4bdd74248f3943ff1c8
|
||||
vendor/golang.org/x/sys/unix/zsysnum_freebsd_386.go type=file mode=0664 size=25623 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.463900804 sha512256digest=5befa2680b15e9981e291beee40a6c56011837762a178ccd2b84eab0cc2ac449
|
||||
vendor/golang.org/x/sys/unix/zsysnum_freebsd_amd64.go type=file mode=0664 size=25625 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.463900804 sha512256digest=3bdaa06622d436e79530094aa65b7462bcbef6897aca55776c7c18a25bd6ba07
|
||||
vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm.go type=file mode=0664 size=25623 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.463900804 sha512256digest=d04d1424746505e05e78a5155505691350370aed2ed6acab41b2b434f3709966
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_386.go type=file mode=0664 size=13047 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.463900804 sha512256digest=384144a3c9d1c9f6dd219b109de7ccde43923991861e11021daf64bb27270aeb
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go type=file mode=0664 size=11417 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.463900804 sha512256digest=4bdb0e2b3c39c24b57c48aee35723a44b46342e6c2a193b11642f831fcdbbb3c
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go type=file mode=0664 size=12117 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.463900804 sha512256digest=ab9ab2be4786115536692e1431f393151dd3da1c846f555843feec037365132f
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go type=file mode=0664 size=9522 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.463900804 sha512256digest=696ef2e52734bce58c25268c45067ed77e483f66f7397cdfc340847af3d8556e
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go type=file mode=0664 size=13009 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=fb453ef4ada8f4892c1db1f43bf01cd5406f062ad86d9aa8aa54fb92f830a47d
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go type=file mode=0664 size=11611 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=5204b369474c7ba523004c70e5724e4c197e7eb3f727362fc20bd2aa93555fea
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go type=file mode=0664 size=11613 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=32a81f707bfc9d0a53455a1725777c513e375546750e856a999a4a0271194a94
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go type=file mode=0664 size=13011 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=336125faeb15c94f43be6da4e9fedcf2d7137e033a82673caf3323d134f8842e
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go type=file mode=0664 size=12364 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=9f9fe29f18a395d5da8074653f10edc4afe5765cc481ff7a156dee4de34340b0
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go type=file mode=0664 size=12366 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=e2c59480adfee1d1721f2da613118404f90db1fdcc7f80866c4e092ac90f7b35
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go type=file mode=0664 size=11153 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=3077e58a29f985f51fe6626aa80ee9b07183cbfd253ccd26d5555aeb50bb1dc8
|
||||
vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go type=file mode=0664 size=11627 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=8e3d19f3484b31cb0b74ce3065dc8a9b764bc1236dc107be1b0b1a746868d597
|
||||
vendor/golang.org/x/sys/unix/zsysnum_netbsd_386.go type=file mode=0664 size=26249 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.464900799 sha512256digest=84a1d3577ad9efbe15b119880e06e523ef4e5c165b7b3aa8ae04c466e40f95a2
|
||||
vendor/golang.org/x/sys/unix/zsysnum_netbsd_amd64.go type=file mode=0664 size=26251 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=e7605c55181ca00e586d289d1b9da3bbc33d91020ed62409daee31cd471b50fd
|
||||
vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm.go type=file mode=0664 size=26249 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=0f2f8b61509acb907b7cc465aea6f6e08a9929977f124ff438db5b4fabb1081e
|
||||
vendor/golang.org/x/sys/unix/zsysnum_openbsd_386.go type=file mode=0664 size=14343 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=d026eadf38e09541256531e745cd1a967315820ca65a65ecb16626f4603f2279
|
||||
vendor/golang.org/x/sys/unix/zsysnum_openbsd_amd64.go type=file mode=0664 size=14345 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=f11c85cbaff35c6e1b159e4ad8bbce97b11bbfe074ca7dceab0cf83f7eabefa2
|
||||
vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm.go type=file mode=0664 size=14801 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=b898bdbeb639b162a12137983f468c9d206799ae149d3dae992e641aeaca8de4
|
||||
vendor/golang.org/x/sys/unix/zsysnum_solaris_amd64.go type=file mode=0664 size=286 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=4b85545ec2b0afb73065697d583003bf81e69ea63a33bd7740c8e0511f046e53
|
||||
vendor/golang.org/x/sys/unix/ztypes_darwin_386.go type=file mode=0664 size=7088 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=69965aa975a8e17796805a7fbea4480658e444c273c7cc857023384a810518fe
|
||||
vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go type=file mode=0664 size=7303 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=5a735e0dc5d072c148be8660cd0f35511437e52f410bc708bb66f57993a1c3a3
|
||||
vendor/golang.org/x/sys/unix/ztypes_darwin_arm.go type=file mode=0664 size=7106 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=04a311a5a756f9cbce2e5d33df4dc40ad1c8da5679ae31fb1ae3ba2a777cdfc2
|
||||
vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go type=file mode=0664 size=7255 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=d1c31d0c1156152e53536386e5d2edd89f0677dcfc9229a863203224cd1a8795
|
||||
vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go type=file mode=0664 size=6879 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=a17ace91aac6fde3b41b7926a0dbc2d01c4cff28bdc9d5bfa15259040d0aa8cd
|
||||
vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go type=file mode=0664 size=8383 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=8850b6f1b199085dcc3de01d287bacb6d3bdf5894a14ddbb0e6bed8745034ef7
|
||||
vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go type=file mode=0664 size=8468 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=d713c13a0c3e7d19262650e11d861acd2622a391b675060e192b229f110482d8
|
||||
vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go type=file mode=0664 size=8493 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.465900794 sha512256digest=322972bda83d28989ceeead12e68e8e4836cdaac34e6e86d3129877a497d5240
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_386.go type=file mode=0664 size=14896 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=94965019a6ce7f0b46f1296ec4098097ca3f1a7aee8f1ad4ef3aad3b32854949
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go type=file mode=0664 size=15241 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=d09df8d55f52d9eba83a7c567ac43e1193c262779d148b418494a78759925972
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_arm.go type=file mode=0664 size=14779 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=6815aa38db25fd7c3a1fed293155c5c4e366a846819acd66821ee93bc14dfe15
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go type=file mode=0664 size=14885 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=0934597c25d70bd1ce3a8e9e6d4106010ead862f02961b36ea2d7a2846cbeb50
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_mips.go type=file mode=0664 size=14824 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=aeff3c297f6dcc31b56500b2fa0f4d8cfce5b21c97fd0eb6e2bde5686b2c6a05
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go type=file mode=0664 size=14923 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=d25acc07a832ce49268a62b80d72cdf2d09b52443ff2146889d28b88f8972645
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go type=file mode=0664 size=14925 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=f5b23e69e3ef24e24f91cb3fafcac3352dbfe2f60c62d1ec9775b89a6ea7dae1
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go type=file mode=0664 size=14826 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=10e4781078ff4926592c06821be99cf8eacb59de85c56171912d4c3264b31d59
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go type=file mode=0664 size=15076 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=6fd0a0437c2332a23cfcd58d06cee89eb2ec66e66bf615897b1c6b249e393dfb
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go type=file mode=0664 size=15078 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=a75abe109b487b24cc01e9f1982c5671cfd3462a4875e8720bb7601a1cea1da6
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go type=file mode=0664 size=15353 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=a6d2c1f7c8ff1b70f58ba7a17c17eae4e44fc259eafab56102f8b0240d423360
|
||||
vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go type=file mode=0664 size=11442 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=9409f66d6e47bf33d1e8ddaf86f7f931f8dea3f091ab19540ab3c73dd28155e5
|
||||
vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go type=file mode=0664 size=6093 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=03965e5a6240c60f14cf6ecf63d7b1a34673ca7323aa6f0c90a0d2b2a9dccc66
|
||||
vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go type=file mode=0664 size=6279 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.466900789 sha512256digest=e5ed42e7b340581a58b038218a2bbe4c46d6ed672849205fb1bdb2116c8e1aca
|
||||
vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go type=file mode=0664 size=6238 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=ac531f076071cfe8cfa7d515fdb3bfef55358a24816a4c52021c9fcd6463fe3a
|
||||
vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go type=file mode=0664 size=6861 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=72ba3a6f0d9a00a37bb1ab4163b193d200bcc45943fdb783eeff98b6a54bd0b3
|
||||
vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go type=file mode=0664 size=7022 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=5766a6597832c64b111be726303f6a3a3261948b9d8cf67aae806a4c03c905fb
|
||||
vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go type=file mode=0664 size=6770 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=7d5aaa31bf85ff3eb5bd5d71c4ea8b31edfc361ef8d31c70d228aef328b1ece7
|
||||
vendor/golang.org/x/sys/unix/ztypes_solaris_amd64.go type=file mode=0664 size=6722 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=ffb709cf263ae56f2c67637b85c4a7c5b63d2acfc3fcfa007f98846a296835bd
|
||||
vendor/golang.org/x/sys/windows type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767
|
||||
vendor/golang.org/x/sys/windows/asm_windows_386.s type=file mode=0664 size=378 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=58d393e4c02842ed863c42538a6a2b8149b4957a0f7933e7dc0d50d98803c001
|
||||
vendor/golang.org/x/sys/windows/asm_windows_amd64.s type=file mode=0664 size=381 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=ba022694d8c21b99c77b61b21a2c25f36eed4180681d51c152e05c8fa200f852
|
||||
vendor/golang.org/x/sys/windows/dll_windows.go type=file mode=0664 size=11128 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=3520d5dad0515ac5d4a345a78fc64c24862f711c56265228046e25b5b4d455a9
|
||||
vendor/golang.org/x/sys/windows/env_unset.go type=file mode=0664 size=327 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=2973b09180689676b7ded3c4c8574fb88a3e35812f4cd1d115ec6551c690e5bb
|
||||
vendor/golang.org/x/sys/windows/env_windows.go type=file mode=0664 size=487 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=dd755498ee242619fcaa30f27afd53aa41245eddfbcc1df6c6b93c50f8f157c3
|
||||
vendor/golang.org/x/sys/windows/eventlog.go type=file mode=0664 size=824 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=a2f8a51099e77875823323aa622d46d972efc715c5652256488a5b31d6a646ac
|
||||
vendor/golang.org/x/sys/windows/exec_windows.go type=file mode=0664 size=1949 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=ed4923e4c075c4201ed62d12a915e7cad454ed431cac75d48d99daf75acbaa78
|
||||
vendor/golang.org/x/sys/windows/memory_windows.go type=file mode=0664 size=706 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=76cd0d0ffdd3978293f5fc1292ae25af7330e6ec2411c395ebe6541b66ea53cf
|
||||
vendor/golang.org/x/sys/windows/mksyscall.go type=file mode=0664 size=330 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=5c16a267d957527960bddf522ea5f5094ed02f04f3d0b3e6758f549857e59e16
|
||||
vendor/golang.org/x/sys/windows/race.go type=file mode=0664 size=588 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=f1320e01be7935bd2dd971edc95e8be8eda5fc34fdedb0719c3f9a4ed76d95b8
|
||||
vendor/golang.org/x/sys/windows/race0.go type=file mode=0664 size=451 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=809559ba2984e20400dab18202bf0405a5bc8806bb835e81af6625ad9c4fb1fc
|
||||
vendor/golang.org/x/sys/windows/registry type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778
|
||||
vendor/golang.org/x/sys/windows/registry/export_test.go type=file mode=0664 size=311 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=3ce47dd70187cde02063100515b4b23382a95cc3ba23ab925838c7c5e2bfb108
|
||||
vendor/golang.org/x/sys/windows/registry/key.go type=file mode=0664 size=6149 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=cf61d3cd1c5be251a0ff761e282b6b12582b8fcd119f3c76c56fbe43c9ea2bda
|
||||
vendor/golang.org/x/sys/windows/registry/mksyscall.go type=file mode=0664 size=279 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=fc241ec3bd3c3226e2b57b990c103c747bea998281d1150d8cb659ede8a53d43
|
||||
vendor/golang.org/x/sys/windows/registry/registry_test.go type=file mode=0664 size=21613 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.467900783 sha512256digest=ce43c79f6978a97c7e518a0b01da47077500bd82bc49ba8db93effd306476708
|
||||
vendor/golang.org/x/sys/windows/registry/syscall.go type=file mode=0664 size=1672 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=696b9d5331bb99d6bcd07a9d7901575cd7551ff17c22281b635dd7b5db764de0
|
||||
vendor/golang.org/x/sys/windows/registry/value.go type=file mode=0664 size=11659 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=cc3f5ec0613f4b19142d45df90ae671f76f37103d25b81a2e619179e992b9d9d
|
||||
vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go type=file mode=0664 size=4497 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=d83b6e21f7d505089d8dc0203f7a67e46f7b480253b0948abacafc066bfcb41c
|
||||
vendor/golang.org/x/sys/windows/security_windows.go type=file mode=0664 size=12717 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=5dbd8c3c6b49a23bcbc5c6cb9e178b8838817c89b1b2d55eee481a6636481748
|
||||
vendor/golang.org/x/sys/windows/service.go type=file mode=0664 size=6760 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=9f548a44ae665f1e2be6798cd0c08004705d9c24511d60579082725c762c4c75
|
||||
vendor/golang.org/x/sys/windows/str.go type=file mode=0664 size=503 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=6a1dd05563d1850a61182fc8abb2985a7139423cdc07bd3bb417925af804d8c1
|
||||
vendor/golang.org/x/sys/windows/svc type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773
|
||||
vendor/golang.org/x/sys/windows/svc/debug type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778
|
||||
vendor/golang.org/x/sys/windows/svc/debug/log.go type=file mode=0664 size=1434 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=36f6b32913126cce77be4bfe994f606c14aaf45aff421fe4fb8b0f5fe3e5d2dd
|
||||
vendor/golang.org/x/sys/windows/svc/debug/service.go type=file mode=0664 size=1013 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=d76bb6933e48c64b9573721547d7129e656b89221f80e07a2ef3748420bd6d3b
|
||||
vendor/golang.org/x/sys/windows/svc/event.go type=file mode=0664 size=979 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=94c5b43479f0a3201bf5ae95e326e1b96593dc0edf92f9e1100839e66f84d588
|
||||
vendor/golang.org/x/sys/windows/svc/eventlog type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778
|
||||
vendor/golang.org/x/sys/windows/svc/eventlog/install.go type=file mode=0664 size=2341 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=5cf5daa0e431195eb4350cfece36376a962b135f6ef1bae422517dc8ed6115c7
|
||||
vendor/golang.org/x/sys/windows/svc/eventlog/log.go type=file mode=0664 size=2041 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=8a504aca519496a4f5b79c5fa60fa8aac7fecf299c63716fdabd83ba213da53f
|
||||
vendor/golang.org/x/sys/windows/svc/eventlog/log_test.go type=file mode=0664 size=1061 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=c393876238b289f5d349ab32c307ba716ffc30f3e423eed5b6a15db3aa375c6d
|
||||
vendor/golang.org/x/sys/windows/svc/example type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773
|
||||
vendor/golang.org/x/sys/windows/svc/example/beep.go type=file mode=0664 size=477 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=b943a452e5d817683380ea39c7b8845d04928334ae8990ee80d2cf1d0980d5da
|
||||
vendor/golang.org/x/sys/windows/svc/example/install.go type=file mode=0664 size=1835 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=ae2446a0cfe3ac3ebcc68c7b3455f16fb4df626a98ceeae643f8102d59d16347
|
||||
vendor/golang.org/x/sys/windows/svc/example/main.go type=file mode=0664 size=1769 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.468900778 sha512256digest=3ce3c4e311f0dc882cdb1686fa88ce26263b3822379db527bd04ba058a23ddb1
|
||||
vendor/golang.org/x/sys/windows/svc/example/manage.go type=file mode=0664 size=1396 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=64659105feeb84f1ff677e29552b6bcf16e8dc1bddb30888b3cb106ce8e5042c
|
||||
vendor/golang.org/x/sys/windows/svc/example/service.go type=file mode=0664 size=2027 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=f84055875f8674a647cb8edf49f1cdc35cd0e88544fe6f7856a4a347d897c7d9
|
||||
vendor/golang.org/x/sys/windows/svc/go12.c type=file mode=0664 size=497 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=b6cd9fe0b0cb80022aeb239ef013e8ac37e9fa5330d2451b4b92c565529365cf
|
||||
vendor/golang.org/x/sys/windows/svc/go12.go type=file mode=0664 size=257 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=7ed60f81776941804a4235d3328e3c1f05ff25f535d194dd2eb3d149f4774ffb
|
||||
vendor/golang.org/x/sys/windows/svc/go13.go type=file mode=0664 size=863 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=9e4c8e9803004e778509b7a4728313b1a789f02bf8906171af2fd80d752f6626
|
||||
vendor/golang.org/x/sys/windows/svc/mgr type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773
|
||||
vendor/golang.org/x/sys/windows/svc/mgr/config.go type=file mode=0664 size=3939 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=69bf2a714f7d6f62db674c9e18ad00214b90c0ffb13116149ee9f0d43ec4b261
|
||||
vendor/golang.org/x/sys/windows/svc/mgr/mgr.go type=file mode=0664 size=4455 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=83687c4dd83faa67ef8db09a153c56845c4010a4dd05401f0a3fdc0f681c3c24
|
||||
vendor/golang.org/x/sys/windows/svc/mgr/mgr_test.go type=file mode=0664 size=3908 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=825a2145e522294fddf0d37ae4175df3b1bbb1acfa61de94cd1c9522cb54ff98
|
||||
vendor/golang.org/x/sys/windows/svc/mgr/service.go type=file mode=0664 size=1791 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=1b0977a1347a7989286d32f6358debe8c6517e5e38d11cdaf388ef3f92fb6b9b
|
||||
vendor/golang.org/x/sys/windows/svc/security.go type=file mode=0664 size=1553 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=05774567b99190dcfaa1198fc818d3a9f0586bcf40527c14c7d4019f8800e3c0
|
||||
vendor/golang.org/x/sys/windows/svc/service.go type=file mode=0664 size=10848 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=91aa0a8eec292418bb2e45609fd9536370fb31d413119663779a29a7c0fa5369
|
||||
vendor/golang.org/x/sys/windows/svc/svc_test.go type=file mode=0664 size=2638 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=cda7e9488d951a5d278c51a45d595ae597167c240aadfcc997f2c564be94c955
|
||||
vendor/golang.org/x/sys/windows/svc/sys_386.s type=file mode=0664 size=1241 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=44892d02d381297466df500895f6ba5991202947c45bb736d8e566f144912e25
|
||||
vendor/golang.org/x/sys/windows/svc/sys_amd64.s type=file mode=0664 size=1031 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=1530d9d362cf67b5abcbbae7b7e244843b0089b50ae376d86070d31ce17c4d36
|
||||
vendor/golang.org/x/sys/windows/syscall.go type=file mode=0664 size=2407 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=129c8714171a4cce55aa92caba4a590881062aa00646e048abd78f97c4e88f4c
|
||||
vendor/golang.org/x/sys/windows/syscall_test.go type=file mode=0664 size=1433 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=8efdb25b14f6699769f13f16a3703f166b2a18a6d8dbe836d1188e259674b48a
|
||||
vendor/golang.org/x/sys/windows/syscall_windows.go type=file mode=0664 size=38971 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=9f24c38c3d96ac08f3a3e6acc64fe165a544305a8acd26ead6d4a71ad45a1bdf
|
||||
vendor/golang.org/x/sys/windows/syscall_windows_test.go type=file mode=0664 size=2857 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.469900773 sha512256digest=94530256b93cfadc7561335221b443298fe5780a6c4e83386986d0cf04b6e577
|
||||
vendor/golang.org/x/sys/windows/types_windows.go type=file mode=0664 size=31549 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=3f82ee167ab35b67c63619c544e47a05e2d26af92761d799ad5d1d6d99e73294
|
||||
vendor/golang.org/x/sys/windows/types_windows_386.go type=file mode=0664 size=478 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=8bb43554de9c8339edfac4c58130193448c774660c9659cb1ae49c2710e6d547
|
||||
vendor/golang.org/x/sys/windows/types_windows_amd64.go type=file mode=0664 size=478 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=25469d12b697e94aa4eb902b8643f5fec03108885e1521d470623237962ec497
|
||||
vendor/golang.org/x/sys/windows/zsyscall_windows.go type=file mode=0664 size=81066 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=f6c866585ffde0cc4ebedd64b2ea42ef6af9624880c353409ada4d868b895db0
|
||||
version.go type=file mode=0664 size=624 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=8653b2bc3523c290f6bd998bb29865c0165ad4e85aaccd0816a34287dc0e04d1
|
||||
walk.go type=file mode=0664 size=10410 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=d398fe4bb1bb9af39442dd6f695fa2fc66302ebbcb5bc1adb6418b2f3d5f9ed3
|
||||
walk_test.go type=file mode=0664 size=1156 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1513088893.904788061 sha512256digest=227e25157b95da02017dab4d2dcff4199f356219cfc622228a6fe0535a41ab1c
|
||||
xattr type=dir mode=0775 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767
|
||||
xattr/xattr.go type=file mode=0664 size=984 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=a2700b603df30c3b0a91bdcf9045e64aea42f62647b0128ea154f51a0c48991e
|
||||
xattr/xattr_test.go type=file mode=0664 size=859 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=91294ea554801b75f6a9e33c268807c9620b531eb813ea24512dd4eeaf0592e4
|
||||
xattr/xattr_unsupported.go type=file mode=0664 size=509 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=81ced06a1cdf88c4936d10bbf8d46edc2d42dc26efeed3691ddbeeb469865f8a
|
||||
xattr/xattr_unsupported_test.go type=file mode=0664 size=877 uid=1000 gid=1000 uname=vbatts gname=vbatts time=1512774394.470900767 sha512256digest=46605a03a985c7a3a4ab1a488f81382db4865f77b90b6a2b32693af39a8e1fba
|
293
unvis.c
293
unvis.c
|
@ -1,293 +0,0 @@
|
|||
/*-
|
||||
* Copyright (c) 1989, 1993
|
||||
* The Regents of the University of California. 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.
|
||||
* 4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
|
||||
*/
|
||||
|
||||
#if defined(LIBC_SCCS) && !defined(lint)
|
||||
static char sccsid[] = "@(#)unvis.c 8.1 (Berkeley) 6/4/93";
|
||||
#endif /* LIBC_SCCS and not lint */
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <ctype.h>
|
||||
#include "vis.h"
|
||||
|
||||
/*
|
||||
* decode driven by state machine
|
||||
*/
|
||||
#define S_GROUND 0 /* haven't seen escape char */
|
||||
#define S_START 1 /* start decoding special sequence */
|
||||
#define S_META 2 /* metachar started (M) */
|
||||
#define S_META1 3 /* metachar more, regular char (-) */
|
||||
#define S_CTRL 4 /* control char started (^) */
|
||||
#define S_OCTAL2 5 /* octal digit 2 */
|
||||
#define S_OCTAL3 6 /* octal digit 3 */
|
||||
#define S_HEX2 7 /* hex digit 2 */
|
||||
|
||||
#define S_HTTP 0x080 /* %HEXHEX escape */
|
||||
|
||||
#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
|
||||
#define ishex(c) ((((u_char)(c)) >= '0' && ((u_char)(c)) <= '9') || (((u_char)(c)) >= 'a' && ((u_char)(c)) <= 'f'))
|
||||
|
||||
/*
|
||||
* unvis - decode characters previously encoded by vis
|
||||
*/
|
||||
int
|
||||
unvis(char *cp, int c, int *astate, int flag)
|
||||
{
|
||||
|
||||
if (flag & UNVIS_END) {
|
||||
if (*astate == S_OCTAL2 || *astate == S_OCTAL3) {
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
}
|
||||
return (*astate == S_GROUND ? UNVIS_NOCHAR : UNVIS_SYNBAD);
|
||||
}
|
||||
|
||||
switch (*astate & ~S_HTTP) {
|
||||
|
||||
case S_GROUND:
|
||||
*cp = 0;
|
||||
if (c == '\\') {
|
||||
*astate = S_START;
|
||||
return (0);
|
||||
}
|
||||
if (flag & VIS_HTTPSTYLE && c == '%') {
|
||||
*astate = S_START | S_HTTP;
|
||||
return (0);
|
||||
}
|
||||
*cp = c;
|
||||
return (UNVIS_VALID);
|
||||
|
||||
case S_START:
|
||||
if (*astate & S_HTTP) {
|
||||
if (ishex(tolower(c))) {
|
||||
*cp = isdigit(c) ? (c - '0') : (tolower(c) - 'a');
|
||||
*astate = S_HEX2;
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
switch(c) {
|
||||
case '\\':
|
||||
*cp = c;
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case '0': case '1': case '2': case '3':
|
||||
case '4': case '5': case '6': case '7':
|
||||
*cp = (c - '0');
|
||||
*astate = S_OCTAL2;
|
||||
return (0);
|
||||
case 'M':
|
||||
*cp = 0200;
|
||||
*astate = S_META;
|
||||
return (0);
|
||||
case '^':
|
||||
*astate = S_CTRL;
|
||||
return (0);
|
||||
case 'n':
|
||||
*cp = '\n';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case 'r':
|
||||
*cp = '\r';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case 'b':
|
||||
*cp = '\b';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case 'a':
|
||||
*cp = '\007';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case 'v':
|
||||
*cp = '\v';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case 't':
|
||||
*cp = '\t';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case 'f':
|
||||
*cp = '\f';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case 's':
|
||||
*cp = ' ';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case 'E':
|
||||
*cp = '\033';
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
case '\n':
|
||||
/*
|
||||
* hidden newline
|
||||
*/
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_NOCHAR);
|
||||
case '$':
|
||||
/*
|
||||
* hidden marker
|
||||
*/
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_NOCHAR);
|
||||
}
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_SYNBAD);
|
||||
|
||||
case S_META:
|
||||
if (c == '-')
|
||||
*astate = S_META1;
|
||||
else if (c == '^')
|
||||
*astate = S_CTRL;
|
||||
else {
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_SYNBAD);
|
||||
}
|
||||
return (0);
|
||||
|
||||
case S_META1:
|
||||
*astate = S_GROUND;
|
||||
*cp |= c;
|
||||
return (UNVIS_VALID);
|
||||
|
||||
case S_CTRL:
|
||||
if (c == '?')
|
||||
*cp |= 0177;
|
||||
else
|
||||
*cp |= c & 037;
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
|
||||
case S_OCTAL2: /* second possible octal digit */
|
||||
if (isoctal(c)) {
|
||||
/*
|
||||
* yes - and maybe a third
|
||||
*/
|
||||
*cp = (*cp << 3) + (c - '0');
|
||||
*astate = S_OCTAL3;
|
||||
return (0);
|
||||
}
|
||||
/*
|
||||
* no - done with current sequence, push back passed char
|
||||
*/
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALIDPUSH);
|
||||
|
||||
case S_OCTAL3: /* third possible octal digit */
|
||||
*astate = S_GROUND;
|
||||
if (isoctal(c)) {
|
||||
*cp = (*cp << 3) + (c - '0');
|
||||
return (UNVIS_VALID);
|
||||
}
|
||||
/*
|
||||
* we were done, push back passed char
|
||||
*/
|
||||
return (UNVIS_VALIDPUSH);
|
||||
|
||||
case S_HEX2: /* second mandatory hex digit */
|
||||
if (ishex(tolower(c))) {
|
||||
*cp = (isdigit(c) ? (*cp << 4) + (c - '0') : (*cp << 4) + (tolower(c) - 'a' + 10));
|
||||
}
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_VALID);
|
||||
|
||||
default:
|
||||
/*
|
||||
* decoder in unknown state - (probably uninitialized)
|
||||
*/
|
||||
*astate = S_GROUND;
|
||||
return (UNVIS_SYNBAD);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* strunvis - decode src into dst
|
||||
*
|
||||
* Number of chars decoded into dst is returned, -1 on error.
|
||||
* Dst is null terminated.
|
||||
*/
|
||||
|
||||
int
|
||||
strunvis(char *dst, const char *src)
|
||||
{
|
||||
char c;
|
||||
char *start = dst;
|
||||
int state = 0;
|
||||
|
||||
while ( (c = *src++) ) {
|
||||
again:
|
||||
switch (unvis(dst, c, &state, 0)) {
|
||||
case UNVIS_VALID:
|
||||
dst++;
|
||||
break;
|
||||
case UNVIS_VALIDPUSH:
|
||||
dst++;
|
||||
goto again;
|
||||
case 0:
|
||||
case UNVIS_NOCHAR:
|
||||
break;
|
||||
default:
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
if (unvis(dst, c, &state, UNVIS_END) == UNVIS_VALID)
|
||||
dst++;
|
||||
*dst = '\0';
|
||||
return (dst - start);
|
||||
}
|
||||
|
||||
int
|
||||
strunvisx(char *dst, const char *src, int flag)
|
||||
{
|
||||
char c;
|
||||
char *start = dst;
|
||||
int state = 0;
|
||||
|
||||
while ( (c = *src++) ) {
|
||||
again:
|
||||
switch (unvis(dst, c, &state, flag)) {
|
||||
case UNVIS_VALID:
|
||||
dst++;
|
||||
break;
|
||||
case UNVIS_VALIDPUSH:
|
||||
dst++;
|
||||
goto again;
|
||||
case 0:
|
||||
case UNVIS_NOCHAR:
|
||||
break;
|
||||
default:
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
if (unvis(dst, c, &state, UNVIS_END) == UNVIS_VALID)
|
||||
dst++;
|
||||
*dst = '\0';
|
||||
return (dst - start);
|
||||
}
|
22
unvis.go
22
unvis.go
|
@ -1,22 +0,0 @@
|
|||
package mtree
|
||||
|
||||
// #include "vis.h"
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Unvis is a wrapper for the C implementation of unvis, which decodes a string
|
||||
// that potentially has characters that are encoded with Vis
|
||||
func Unvis(src string) (string, error) {
|
||||
cDst, cSrc := C.CString(string(make([]byte, len(src)+1))), C.CString(src)
|
||||
defer C.free(unsafe.Pointer(cDst))
|
||||
defer C.free(unsafe.Pointer(cSrc))
|
||||
ret := C.strunvis(cDst, cSrc)
|
||||
if ret == -1 {
|
||||
return "", fmt.Errorf("failed to decode: %q", src)
|
||||
}
|
||||
return C.GoString(cDst), nil
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package mtree
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/vbatts/go-mtree/xattr"
|
||||
)
|
||||
|
||||
func init() {
|
||||
//logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
func TestXattrUpdate(t *testing.T) {
|
||||
content := []byte("I know half of you half as well as I ought to")
|
||||
// a bit dirty to create/destory a directory in cwd, but often /tmp is
|
||||
// mounted tmpfs and doesn't support xattrs
|
||||
dir, err := ioutil.TempDir(".", "test.xattr.restore.")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir) // clean up
|
||||
|
||||
tmpfn := filepath.Join(dir, "tmpfile")
|
||||
if err := ioutil.WriteFile(tmpfn, content, 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := xattr.Set(dir, "user.test", []byte("directory")); err != nil {
|
||||
t.Skip(fmt.Sprintf("skipping: %q does not support xattrs", dir))
|
||||
}
|
||||
if err := xattr.Set(tmpfn, "user.test", []byte("regular file")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Walk this tempdir
|
||||
dh, err := Walk(dir, nil, append(DefaultKeywords, []Keyword{"xattr", "sha1"}...), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now check that we're sane
|
||||
res, err := Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) != 0 {
|
||||
t.Errorf("expecting no failures, but got %q", res)
|
||||
}
|
||||
|
||||
if err := xattr.Set(tmpfn, "user.test", []byte("let it fly")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now check that we fail the check
|
||||
res, err = Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) == 0 {
|
||||
t.Error("expected failures (like xattrs), but got none")
|
||||
}
|
||||
|
||||
// restore the xattrs to original
|
||||
res, err = Update(dir, dh, append(DefaultUpdateKeywords, "xattr"), nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(res) != 0 {
|
||||
t.Errorf("expecting no failures, but got %q", res)
|
||||
}
|
||||
|
||||
// Now check that we're sane again
|
||||
res, err = Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) != 0 {
|
||||
// pretty this shit up
|
||||
buf, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
t.Errorf("expecting no failures, but got %q", res)
|
||||
} else {
|
||||
t.Errorf("expecting no failures, but got %s", string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO make a test for xattr here. Likely in the user space for privileges. Even still this may be prone to error for some tmpfs don't act right with xattrs. :-\
|
||||
// I'd hate to have to t.Skip() a test rather than fail alltogether.
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
// +build go1.7
|
||||
|
||||
package mtree
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
content := []byte("I know half of you half as well as I ought to")
|
||||
dir, err := ioutil.TempDir("", "test-check-keywords")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir) // clean up
|
||||
|
||||
tmpfn := filepath.Join(dir, "tmpfile")
|
||||
if err := ioutil.WriteFile(tmpfn, content, 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Walk this tempdir
|
||||
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Touch a file, so the mtime changes.
|
||||
now := time.Now()
|
||||
if err := os.Chtimes(tmpfn, now, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chmod(tmpfn, os.FileMode(0600)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Changing user is a little tough, but the group can be changed by a limited user to any group that the user is a member of. So just choose one that is not the current main group.
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ugroups, err := u.GroupIds()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, ugroup := range ugroups {
|
||||
if ugroup == u.Gid {
|
||||
continue
|
||||
}
|
||||
gid, err := strconv.Atoi(ugroup)
|
||||
if err != nil {
|
||||
t.Fatal(ugroup)
|
||||
}
|
||||
if err := os.Lchown(tmpfn, -1, gid); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for sanity. This ought to have failures
|
||||
res, err := Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res) == 0 {
|
||||
t.Error("expected failures (like mtimes), but got none")
|
||||
}
|
||||
//dh.WriteTo(os.Stdout)
|
||||
|
||||
res, err = Update(dir, dh, DefaultUpdateKeywords, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(res) > 0 {
|
||||
// pretty this shit up
|
||||
buf, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
t.Errorf("%#v", res)
|
||||
}
|
||||
t.Error(string(buf))
|
||||
}
|
||||
|
||||
// Now check that we're sane again
|
||||
res, err = Check(dir, dh, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// should have no failures now
|
||||
if len(res) > 0 {
|
||||
// pretty this shit up
|
||||
buf, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
t.Errorf("%#v", res)
|
||||
} else {
|
||||
t.Error(string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPathUpdateHeap(t *testing.T) {
|
||||
h := &pathUpdateHeap{
|
||||
pathUpdate{Path: "not/the/longest"},
|
||||
pathUpdate{Path: "almost/the/longest"},
|
||||
pathUpdate{Path: "."},
|
||||
pathUpdate{Path: "short"},
|
||||
}
|
||||
heap.Init(h)
|
||||
v := "this/is/one/is/def/the/longest"
|
||||
heap.Push(h, pathUpdate{Path: v})
|
||||
|
||||
longest := len(v)
|
||||
var p string
|
||||
for h.Len() > 0 {
|
||||
p = heap.Pop(h).(pathUpdate).Path
|
||||
if len(p) > longest {
|
||||
t.Errorf("expected next path to be shorter, but it was not %q is longer than %d", p, longest)
|
||||
}
|
||||
}
|
||||
if p != "." {
|
||||
t.Errorf("expected \".\" to be the last, but got %q", p)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// +build !linux
|
||||
|
||||
package mtree
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func xattrUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||
return os.Lstat(path)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
type flag uintptr
|
||||
|
||||
var (
|
||||
// flagRO indicates whether the value field of a reflect.Value
|
||||
// is read-only.
|
||||
flagRO flag
|
||||
|
||||
// flagAddr indicates whether the address of the reflect.Value's
|
||||
// value may be taken.
|
||||
flagAddr flag
|
||||
)
|
||||
|
||||
// flagKindMask holds the bits that make up the kind
|
||||
// part of the flags field. In all the supported versions,
|
||||
// it is in the lower 5 bits.
|
||||
const flagKindMask = flag(0x1f)
|
||||
|
||||
// Different versions of Go have used different
|
||||
// bit layouts for the flags type. This table
|
||||
// records the known combinations.
|
||||
var okFlags = []struct {
|
||||
ro, addr flag
|
||||
}{{
|
||||
// From Go 1.4 to 1.5
|
||||
ro: 1 << 5,
|
||||
addr: 1 << 7,
|
||||
}, {
|
||||
// Up to Go tip.
|
||||
ro: 1<<5 | 1<<6,
|
||||
addr: 1 << 8,
|
||||
}}
|
||||
|
||||
var flagValOffset = func() uintptr {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
return field.Offset
|
||||
}()
|
||||
|
||||
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||
func flagField(v *reflect.Value) *flag {
|
||||
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||
return v
|
||||
}
|
||||
flagFieldPtr := flagField(&v)
|
||||
*flagFieldPtr &^= flagRO
|
||||
*flagFieldPtr |= flagAddr
|
||||
return v
|
||||
}
|
||||
|
||||
// Sanity checks against future reflect package changes
|
||||
// to the type or semantics of the Value.flag field.
|
||||
func init() {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||
panic("reflect.Value flag field has changed kind")
|
||||
}
|
||||
type t0 int
|
||||
var t struct {
|
||||
A t0
|
||||
// t0 will have flagEmbedRO set.
|
||||
t0
|
||||
// a will have flagStickyRO set
|
||||
a t0
|
||||
}
|
||||
vA := reflect.ValueOf(t).FieldByName("A")
|
||||
va := reflect.ValueOf(t).FieldByName("a")
|
||||
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||
|
||||
// Infer flagRO from the difference between the flags
|
||||
// for the (otherwise identical) fields in t.
|
||||
flagPublic := *flagField(&vA)
|
||||
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||
flagRO = flagPublic ^ flagWithRO
|
||||
|
||||
// Infer flagAddr from the difference between a value
|
||||
// taken from a pointer and not.
|
||||
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||
flagNoPtr := *flagField(&vA)
|
||||
flagPtr := *flagField(&vPtrA)
|
||||
flagAddr = flagNoPtr ^ flagPtr
|
||||
|
||||
// Check that the inferred flags tally with one of the known versions.
|
||||
for _, f := range okFlags {
|
||||
if flagRO == f.ro && flagAddr == f.addr {
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("reflect.Value read-only flag has changed semantics")
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe !go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
panicBytes = []byte("(PANIC=")
|
||||
plusBytes = []byte("+")
|
||||
iBytes = []byte("i")
|
||||
trueBytes = []byte("true")
|
||||
falseBytes = []byte("false")
|
||||
interfaceBytes = []byte("(interface {})")
|
||||
commaNewlineBytes = []byte(",\n")
|
||||
newlineBytes = []byte("\n")
|
||||
openBraceBytes = []byte("{")
|
||||
openBraceNewlineBytes = []byte("{\n")
|
||||
closeBraceBytes = []byte("}")
|
||||
asteriskBytes = []byte("*")
|
||||
colonBytes = []byte(":")
|
||||
colonSpaceBytes = []byte(": ")
|
||||
openParenBytes = []byte("(")
|
||||
closeParenBytes = []byte(")")
|
||||
spaceBytes = []byte(" ")
|
||||
pointerChainBytes = []byte("->")
|
||||
nilAngleBytes = []byte("<nil>")
|
||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
percentBytes = []byte("%")
|
||||
precisionBytes = []byte(".")
|
||||
openAngleBytes = []byte("<")
|
||||
closeAngleBytes = []byte(">")
|
||||
openMapBytes = []byte("map[")
|
||||
closeMapBytes = []byte("]")
|
||||
lenEqualsBytes = []byte("len=")
|
||||
capEqualsBytes = []byte("cap=")
|
||||
)
|
||||
|
||||
// hexDigits is used to map a decimal value to a hex digit.
|
||||
var hexDigits = "0123456789abcdef"
|
||||
|
||||
// catchPanic handles any panics that might occur during the handleMethods
|
||||
// calls.
|
||||
func catchPanic(w io.Writer, v reflect.Value) {
|
||||
if err := recover(); err != nil {
|
||||
w.Write(panicBytes)
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMethods attempts to call the Error and String methods on the underlying
|
||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||
//
|
||||
// It handles panics in any called methods by catching and displaying the error
|
||||
// as the formatted value.
|
||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
// Choose whether or not to do error and Stringer interface lookups against
|
||||
// the base type or a pointer to the base type depending on settings.
|
||||
// Technically calling one of these methods with a pointer receiver can
|
||||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.Error()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
|
||||
w.Write([]byte(iface.Error()))
|
||||
return true
|
||||
|
||||
case fmt.Stringer:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.String()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
w.Write([]byte(iface.String()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printBool outputs a boolean value as true or false to Writer w.
|
||||
func printBool(w io.Writer, val bool) {
|
||||
if val {
|
||||
w.Write(trueBytes)
|
||||
} else {
|
||||
w.Write(falseBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// printInt outputs a signed integer value to Writer w.
|
||||
func printInt(w io.Writer, val int64, base int) {
|
||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||
}
|
||||
|
||||
// printUint outputs an unsigned integer value to Writer w.
|
||||
func printUint(w io.Writer, val uint64, base int) {
|
||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||
}
|
||||
|
||||
// printFloat outputs a floating point value using the specified precision,
|
||||
// which is expected to be 32 or 64bit, to Writer w.
|
||||
func printFloat(w io.Writer, val float64, precision int) {
|
||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||
}
|
||||
|
||||
// printComplex outputs a complex value using the specified float precision
|
||||
// for the real and imaginary parts to Writer w.
|
||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
r := real(c)
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||
i := imag(c)
|
||||
if i >= 0 {
|
||||
w.Write(plusBytes)
|
||||
}
|
||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||
w.Write(iBytes)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
num := uint64(p)
|
||||
if num == 0 {
|
||||
w.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||
buf := make([]byte, 18)
|
||||
|
||||
// It's simpler to construct the hex string right to left.
|
||||
base := uint64(16)
|
||||
i := len(buf) - 1
|
||||
for num >= base {
|
||||
buf[i] = hexDigits[num%base]
|
||||
num /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[num]
|
||||
|
||||
// Add '0x' prefix.
|
||||
i--
|
||||
buf[i] = 'x'
|
||||
i--
|
||||
buf[i] = '0'
|
||||
|
||||
// Strip unused leading bytes.
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
values []reflect.Value
|
||||
strings []string // either nil or same len and values
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||
// surrogate keys on which the data should be sorted. It uses flags in
|
||||
// ConfigState to decide if and how to populate those surrogate keys.
|
||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||
vs := &valuesSorter{values: values, cs: cs}
|
||||
if canSortSimply(vs.values[0].Kind()) {
|
||||
return vs
|
||||
}
|
||||
if !cs.DisableMethods {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
b := bytes.Buffer{}
|
||||
if !handleMethods(cs, &b, vs.values[i]) {
|
||||
vs.strings = nil
|
||||
break
|
||||
}
|
||||
vs.strings[i] = b.String()
|
||||
}
|
||||
}
|
||||
if vs.strings == nil && cs.SpewKeys {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||
// directly, or whether it should be considered for sorting by surrogate keys
|
||||
// (if the ConfigState allows it).
|
||||
func canSortSimply(kind reflect.Kind) bool {
|
||||
// This switch parallels valueSortLess, except for the default case.
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Uintptr:
|
||||
return true
|
||||
case reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
// Swap swaps the values at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
if s.strings != nil {
|
||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
// valueSortLess returns whether the first value should sort before the second
|
||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||
// implementation.
|
||||
func valueSortLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.String:
|
||||
return a.String() < b.String()
|
||||
case reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Array:
|
||||
// Compare the contents of both arrays.
|
||||
l := a.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
av := a.Index(i)
|
||||
bv := b.Index(i)
|
||||
if av.Interface() == bv.Interface() {
|
||||
continue
|
||||
}
|
||||
return valueSortLess(av, bv)
|
||||
}
|
||||
}
|
||||
return a.String() < b.String()
|
||||
}
|
||||
|
||||
// Less returns whether the value at index i should sort before the
|
||||
// value at index j. It is part of the sort.Interface implementation.
|
||||
func (s *valuesSorter) Less(i, j int) bool {
|
||||
if s.strings == nil {
|
||||
return valueSortLess(s.values[i], s.values[j])
|
||||
}
|
||||
return s.strings[i] < s.strings[j]
|
||||
}
|
||||
|
||||
// sortValues is a sort function that handles both native types and any type that
|
||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||
// their Value.String() value to ensure display stability.
|
||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Sort(newValuesSorter(values, cs))
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigState houses the configuration options used by spew to format and
|
||||
// display values. There is a global instance, Config, that is used to control
|
||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||
// provides methods equivalent to the top-level functions.
|
||||
//
|
||||
// The zero value for ConfigState provides no indentation. You would typically
|
||||
// want to set it to a space or a tab.
|
||||
//
|
||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||
// with default settings. See the documentation of NewDefaultConfig for default
|
||||
// values.
|
||||
type ConfigState struct {
|
||||
// Indent specifies the string to use for each indentation level. The
|
||||
// global config instance that all top-level functions use set this to a
|
||||
// single space by default. If you would like more indentation, you might
|
||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||
Indent string
|
||||
|
||||
// MaxDepth controls the maximum number of levels to descend into nested
|
||||
// data structures. The default, 0, means there is no limit.
|
||||
//
|
||||
// NOTE: Circular data structures are properly detected, so it is not
|
||||
// necessary to set this value unless you specifically want to limit deeply
|
||||
// nested data structures.
|
||||
MaxDepth int
|
||||
|
||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||
// invoked for types that implement them.
|
||||
DisableMethods bool
|
||||
|
||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||
// error and Stringer interfaces on types which only accept a pointer
|
||||
// receiver when the current type is not a pointer.
|
||||
//
|
||||
// NOTE: This might be an unsafe action since calling one of these methods
|
||||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "safe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// DisablePointerAddresses specifies whether to disable the printing of
|
||||
// pointer addresses. This is useful when diffing data structures in tests.
|
||||
DisablePointerAddresses bool
|
||||
|
||||
// DisableCapacities specifies whether to disable the printing of capacities
|
||||
// for arrays, slices, maps and channels. This is useful when diffing
|
||||
// data structures in tests.
|
||||
DisableCapacities bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
// a custom error or Stringer interface is invoked. The default, false,
|
||||
// means it will print the results of invoking the custom error or Stringer
|
||||
// interface and return immediately instead of continuing to recurse into
|
||||
// the internals of the data type.
|
||||
//
|
||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||
// via the DisableMethods or DisablePointerMethods options.
|
||||
ContinueOnMethod bool
|
||||
|
||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||
// this to have a more deterministic, diffable output. Note that only
|
||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||
// that support the error or Stringer interfaces (if methods are
|
||||
// enabled) are supported, with other types sorted according to the
|
||||
// reflect.Value.String() output which guarantees display stability.
|
||||
SortKeys bool
|
||||
|
||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
// The configuration can be changed by modifying the contents of spew.Config.
|
||||
var Config = ConfigState{Indent: " "}
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the formatted string as a value that satisfies error. See NewFormatter
|
||||
// for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
c.Printf, c.Println, or c.Printf.
|
||||
*/
|
||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(c, v)
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(c, w, a...)
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by modifying the public members
|
||||
of c. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func (c *ConfigState) Dump(a ...interface{}) {
|
||||
fdump(c, os.Stdout, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(c, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a spew Formatter interface using
|
||||
// the ConfigState associated with s.
|
||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = newFormatter(c, arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||
//
|
||||
// Indent: " "
|
||||
// MaxDepth: 0
|
||||
// DisableMethods: false
|
||||
// DisablePointerMethods: false
|
||||
// ContinueOnMethod: false
|
||||
// SortKeys: false
|
||||
func NewDefaultConfig() *ConfigState {
|
||||
return &ConfigState{Indent: " "}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging.
|
||||
|
||||
A quick overview of the additional features spew provides over the built-in
|
||||
printing facilities for Go data types are as follows:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output (only when using
|
||||
Dump style)
|
||||
|
||||
There are two different approaches spew allows for dumping Go data structures:
|
||||
|
||||
* Dump style which prints with newlines, customizable indentation,
|
||||
and additional debug information such as types and all pointer addresses
|
||||
used to indirect to the final value
|
||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||
similar to the default %v while providing the additional functionality
|
||||
outlined above and passing unsupported format verbs such as %x and %q
|
||||
along to fmt
|
||||
|
||||
Quick Start
|
||||
|
||||
This section demonstrates how to quickly get started with spew. See the
|
||||
sections below for further details on formatting and configuration options.
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||
%#+v (adds types and pointer addresses):
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available
|
||||
via the spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
The following configuration options are available:
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of
|
||||
capacities for arrays, slices, maps and channels. This is useful when
|
||||
diffing data structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are
|
||||
supported with other types sorted according to the
|
||||
reflect.Value.String() output which guarantees display
|
||||
stability. Natural map order is used by default.
|
||||
|
||||
* SpewKeys
|
||||
Specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only
|
||||
considered if SortKeys is true.
|
||||
|
||||
Dump Usage
|
||||
|
||||
Simply call spew.Dump with a list of variables you want to dump:
|
||||
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
|
||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||
io.Writer. For example, to dump to standard error:
|
||||
|
||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||
|
||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Sample Dump Output
|
||||
|
||||
See the Dump example for details on the setup of the types and variables being
|
||||
shown here.
|
||||
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
(string) (len=3) "one": (bool) true
|
||||
}
|
||||
}
|
||||
|
||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||
command as shown.
|
||||
([]uint8) (len=32 cap=32) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
|
||||
Custom Formatter
|
||||
|
||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||
so that it integrates cleanly with standard fmt package printing functions. The
|
||||
formatter is useful for inline printing of smaller data types similar to the
|
||||
standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Custom Formatter Usage
|
||||
|
||||
The simplest way to make use of the spew custom formatter is to call one of the
|
||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||
functions have syntax you are most likely already familiar with:
|
||||
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Println(myVar, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
See the Index for the full list convenience functions.
|
||||
|
||||
Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
|
||||
See the Printf example for details on the setup of variables being shown
|
||||
here.
|
||||
|
||||
Errors
|
||||
|
||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||
detects them and handles them internally by printing the panic information
|
||||
inline with the output. Since spew is intended to provide deep pretty printing
|
||||
capabilities on structures, it intentionally does not return any errors.
|
||||
*/
|
||||
package spew
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||
// convert cgo types to uint8 slices for hexdumping.
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
type dumpState struct {
|
||||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
// option.
|
||||
func (d *dumpState) indent() {
|
||||
if d.ignoreNextIndent {
|
||||
d.ignoreNextIndent = false
|
||||
return
|
||||
}
|
||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range d.pointers {
|
||||
if depth >= d.depth {
|
||||
delete(d.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by dereferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type information.
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
d.w.Write([]byte(ve.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
|
||||
// Display pointer information.
|
||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||
// Determine whether this type should be hex dumped or not. Also,
|
||||
// for types which should be hexdumped, try to use the underlying data
|
||||
// first, then fall back to trying to convert them to a uint8 slice.
|
||||
var buf []uint8
|
||||
doConvert := false
|
||||
doHexDump := false
|
||||
numEntries := v.Len()
|
||||
if numEntries > 0 {
|
||||
vt := v.Index(0).Type()
|
||||
vts := vt.String()
|
||||
switch {
|
||||
// C types that need to be converted.
|
||||
case cCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUnsignedCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUint8tCharRE.MatchString(vts):
|
||||
doConvert = true
|
||||
|
||||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
// be type asserted to a uint8 slice.
|
||||
doConvert = true
|
||||
}
|
||||
|
||||
// Copy and convert the underlying type if needed.
|
||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||
// Convert and copy each element into a uint8 byte
|
||||
// slice.
|
||||
buf = make([]uint8, numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
vv := v.Index(i)
|
||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||
}
|
||||
doHexDump = true
|
||||
}
|
||||
}
|
||||
|
||||
// Hexdump the entire slice as needed.
|
||||
if doHexDump {
|
||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||
str := indent + hex.Dump(buf)
|
||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||
str = strings.TrimRight(str, d.cs.Indent)
|
||||
d.w.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively call dump for each item.
|
||||
for i := 0; i < numEntries; i++ {
|
||||
d.dump(d.unpackValue(v.Index(i)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||
// value to figure out what kind of object we are dealing with and formats it
|
||||
// appropriately. It is a recursive function, however circular data structures
|
||||
// are detected and handled properly.
|
||||
func (d *dumpState) dump(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
d.w.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
d.indent()
|
||||
d.dumpPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !d.ignoreNextType {
|
||||
d.indent()
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write([]byte(v.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.ignoreNextType = false
|
||||
|
||||
// Display length and capacity if the built-in len and cap functions
|
||||
// work with the value's kind and the len/cap itself is non-zero.
|
||||
valueLen, valueCap := 0, 0
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||
valueLen, valueCap = v.Len(), v.Cap()
|
||||
case reflect.Map, reflect.String:
|
||||
valueLen = v.Len()
|
||||
}
|
||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
if valueLen != 0 {
|
||||
d.w.Write(lenEqualsBytes)
|
||||
printInt(d.w, int64(valueLen), 10)
|
||||
}
|
||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||
if valueLen != 0 {
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.w.Write(capEqualsBytes)
|
||||
printInt(d.w, int64(valueCap), 10)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||
// is enabled
|
||||
if !d.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(d.w, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(d.w, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(d.w, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(d.w, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(d.w, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(d.w, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(d.w, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
d.dumpSlice(v)
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.String:
|
||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
keys := v.MapKeys()
|
||||
if d.cs.SortKeys {
|
||||
sortValues(keys, d.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
d.dump(d.unpackValue(key))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
numFields := v.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
d.indent()
|
||||
vtf := vt.Field(i)
|
||||
d.w.Write([]byte(vtf.Name))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.Field(i)))
|
||||
if i < (numFields - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(d.w, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(d.w, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it in case any new
|
||||
// types are added.
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(d.w, "%v", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fdump is a helper function to consolidate the logic from the various public
|
||||
// methods which take varying writers and config states.
|
||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if arg == nil {
|
||||
w.Write(interfaceBytes)
|
||||
w.Write(spaceBytes)
|
||||
w.Write(nilAngleBytes)
|
||||
w.Write(newlineBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(&Config, w, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(&Config, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by an exported package global,
|
||||
spew.Config. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func Dump(a ...interface{}) {
|
||||
fdump(&Config, os.Stdout, a...)
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||
const supportedFlags = "0-+# "
|
||||
|
||||
// formatState implements the fmt.Formatter interface and contains information
|
||||
// about the state of a formatting operation. The NewFormatter function can
|
||||
// be used to get a new Formatter which can be used directly as arguments
|
||||
// in standard fmt package printing calls.
|
||||
type formatState struct {
|
||||
value interface{}
|
||||
fs fmt.State
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// buildDefaultFormat recreates the original format string without precision
|
||||
// and width information to pass in to fmt.Sprintf in the case of an
|
||||
// unrecognized type. Unless new types are added to the language, this
|
||||
// function won't ever be called.
|
||||
func (f *formatState) buildDefaultFormat() (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteRune('v')
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// constructOrigFormat recreates the original format string including precision
|
||||
// and width information to pass along to the standard fmt package. This allows
|
||||
// automatic deferral of all format strings this package doesn't support.
|
||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if width, ok := f.fs.Width(); ok {
|
||||
buf.WriteString(strconv.Itoa(width))
|
||||
}
|
||||
|
||||
if precision, ok := f.fs.Precision(); ok {
|
||||
buf.Write(precisionBytes)
|
||||
buf.WriteString(strconv.Itoa(precision))
|
||||
}
|
||||
|
||||
buf.WriteRune(verb)
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||
// ensures that types for values which have been unpacked from an interface
|
||||
// are displayed when the show types flag is also set.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface {
|
||||
f.ignoreNextType = false
|
||||
if !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (f *formatState) formatPtr(v reflect.Value) {
|
||||
// Display nil if top level pointer is nil.
|
||||
showTypes := f.fs.Flag('#')
|
||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range f.pointers {
|
||||
if depth >= f.depth {
|
||||
delete(f.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to possibly show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by derferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
f.pointers[addr] = f.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type or indirection level depending on flags.
|
||||
if showTypes && !f.ignoreNextType {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
f.fs.Write([]byte(ve.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
} else {
|
||||
if nilFound || cycleFound {
|
||||
indirects += strings.Count(ve.Type().String(), "*")
|
||||
}
|
||||
f.fs.Write(openAngleBytes)
|
||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||
f.fs.Write(closeAngleBytes)
|
||||
}
|
||||
|
||||
// Display pointer information depending on flags.
|
||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||
f.fs.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
f.fs.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(f.fs, addr)
|
||||
}
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
switch {
|
||||
case nilFound:
|
||||
f.fs.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
f.fs.Write(circularShortBytes)
|
||||
|
||||
default:
|
||||
f.ignoreNextType = true
|
||||
f.format(ve)
|
||||
}
|
||||
}
|
||||
|
||||
// format is the main workhorse for providing the Formatter interface. It
|
||||
// uses the passed reflect value to figure out what kind of object we are
|
||||
// dealing with and formats it appropriately. It is a recursive function,
|
||||
// however circular data structures are detected and handled properly.
|
||||
func (f *formatState) format(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
f.fs.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
f.formatPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write([]byte(v.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
f.ignoreNextType = false
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods
|
||||
// flag is enabled.
|
||||
if !f.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(f.fs, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(f.fs, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(f.fs, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(f.fs, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(f.fs, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(f.fs, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(f.fs, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
f.fs.Write(openBracketBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.Index(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBracketBytes)
|
||||
|
||||
case reflect.String:
|
||||
f.fs.Write([]byte(v.String()))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
f.fs.Write(openMapBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
keys := v.MapKeys()
|
||||
if f.cs.SortKeys {
|
||||
sortValues(keys, f.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(key))
|
||||
f.fs.Write(colonBytes)
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.MapIndex(key)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeMapBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
numFields := v.NumField()
|
||||
f.fs.Write(openBraceBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
for i := 0; i < numFields; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
vtf := vt.Field(i)
|
||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||
f.fs.Write([]byte(vtf.Name))
|
||||
f.fs.Write(colonBytes)
|
||||
}
|
||||
f.format(f.unpackValue(v.Field(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(f.fs, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it if any get added.
|
||||
default:
|
||||
format := f.buildDefaultFormat()
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(f.fs, format, v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(f.fs, format, v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||
// details.
|
||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||
f.fs = fs
|
||||
|
||||
// Use standard formatting for verbs that are not v.
|
||||
if verb != 'v' {
|
||||
format := f.constructOrigFormat(verb)
|
||||
fmt.Fprintf(fs, format, f.value)
|
||||
return
|
||||
}
|
||||
|
||||
if f.value == nil {
|
||||
if fs.Flag('#') {
|
||||
fs.Write(interfaceBytes)
|
||||
}
|
||||
fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
f.format(reflect.ValueOf(f.value))
|
||||
}
|
||||
|
||||
// newFormatter is a helper function to consolidate the logic from the various
|
||||
// public methods which take varying config states.
|
||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||
fs := &formatState{value: v, cs: cs}
|
||||
fs.pointers = make(map[uintptr]int)
|
||||
return fs
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
Printf, Println, or Fprintf.
|
||||
*/
|
||||
func NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(&Config, v)
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the formatted string as a value that satisfies error. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a default Formatter interface returned by NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a default spew Formatter interface.
|
||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = NewFormatter(arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
logrus
|
||||
vendor
|
||||
|
||||
.idea/
|
|
@ -0,0 +1,40 @@
|
|||
run:
|
||||
# do not run on test files yet
|
||||
tests: false
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: false
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
|
||||
lll:
|
||||
line-length: 100
|
||||
tab-width: 4
|
||||
|
||||
prealloc:
|
||||
simple: false
|
||||
range-loops: false
|
||||
for-loops: false
|
||||
|
||||
whitespace:
|
||||
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
||||
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- govet
|
||||
disable:
|
||||
- maligned
|
||||
- prealloc
|
||||
disable-all: false
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
fast: false
|
|
@ -0,0 +1,17 @@
|
|||
language: go
|
||||
go_import_path: github.com/sirupsen/logrus
|
||||
git:
|
||||
depth: 1
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
go: [1.13.x, 1.14.x]
|
||||
os: [linux, osx]
|
||||
install:
|
||||
- ./travis/install.sh
|
||||
script:
|
||||
- ./travis/cross_build.sh
|
||||
- ./travis/lint.sh
|
||||
- export GOMAXPROCS=4
|
||||
- export GORACE=halt_on_error=1
|
||||
- go test -race -v ./...
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then go test -race -v -tags appengine ./... ; fi
|
|
@ -0,0 +1,223 @@
|
|||
# 1.6.0
|
||||
Fixes:
|
||||
* end of line cleanup
|
||||
* revert the entry concurrency bug fix whic leads to deadlock under some circumstances
|
||||
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
||||
|
||||
Features:
|
||||
* add an option to the `TextFormatter` to completely disable fields quoting
|
||||
|
||||
# 1.5.0
|
||||
Code quality:
|
||||
* add golangci linter run on travis
|
||||
|
||||
Fixes:
|
||||
* add mutex for hooks concurrent access on `Entry` data
|
||||
* caller function field for go1.14
|
||||
* fix build issue for gopherjs target
|
||||
|
||||
Feature:
|
||||
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
||||
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
||||
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
||||
|
||||
# 1.4.2
|
||||
* Fixes build break for plan9, nacl, solaris
|
||||
# 1.4.1
|
||||
This new release introduces:
|
||||
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
||||
* Remove dependency on golang.org/x/crypto (#932, #943)
|
||||
|
||||
Fixes:
|
||||
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
||||
|
||||
# 1.4.0
|
||||
This new release introduces:
|
||||
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
||||
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
||||
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
||||
|
||||
Fixes:
|
||||
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
||||
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
||||
* Fix infinite recursion on unknown `Level.String()` (#907)
|
||||
* Fix race condition in `getCaller` (#916).
|
||||
|
||||
|
||||
# 1.3.0
|
||||
This new release introduces:
|
||||
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
||||
|
||||
Fixes:
|
||||
* Building prometheus node_exporter on AIX (#840)
|
||||
* Race condition in TextFormatter (#468)
|
||||
* Travis CI import path (#868)
|
||||
* Remove coloured output on Windows (#862)
|
||||
* Pointer to func as field in JSONFormatter (#870)
|
||||
* Properly marshal Levels (#873)
|
||||
|
||||
# 1.2.0
|
||||
This new release introduces:
|
||||
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
||||
* A new trace level named `Trace` whose level is below `Debug`
|
||||
* A configurable exit function to be called upon a Fatal trace
|
||||
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
||||
|
||||
# 1.1.1
|
||||
This is a bug fix release.
|
||||
* fix the build break on Solaris
|
||||
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
||||
|
||||
# 1.1.0
|
||||
This new release introduces:
|
||||
* several fixes:
|
||||
* a fix for a race condition on entry formatting
|
||||
* proper cleanup of previously used entries before putting them back in the pool
|
||||
* the extra new line at the end of message in text formatter has been removed
|
||||
* a new global public API to check if a level is activated: IsLevelEnabled
|
||||
* the following methods have been added to the Logger object
|
||||
* IsLevelEnabled
|
||||
* SetFormatter
|
||||
* SetOutput
|
||||
* ReplaceHooks
|
||||
* introduction of go module
|
||||
* an indent configuration for the json formatter
|
||||
* output colour support for windows
|
||||
* the field sort function is now configurable for text formatter
|
||||
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
||||
|
||||
# 1.0.6
|
||||
|
||||
This new release introduces:
|
||||
* a new api WithTime which allows to easily force the time of the log entry
|
||||
which is mostly useful for logger wrapper
|
||||
* a fix reverting the immutability of the entry given as parameter to the hooks
|
||||
a new configuration field of the json formatter in order to put all the fields
|
||||
in a nested dictionnary
|
||||
* a new SetOutput method in the Logger
|
||||
* a new configuration of the textformatter to configure the name of the default keys
|
||||
* a new configuration of the text formatter to disable the level truncation
|
||||
|
||||
# 1.0.5
|
||||
|
||||
* Fix hooks race (#707)
|
||||
* Fix panic deadlock (#695)
|
||||
|
||||
# 1.0.4
|
||||
|
||||
* Fix race when adding hooks (#612)
|
||||
* Fix terminal check in AppEngine (#635)
|
||||
|
||||
# 1.0.3
|
||||
|
||||
* Replace example files with testable examples
|
||||
|
||||
# 1.0.2
|
||||
|
||||
* bug: quote non-string values in text formatter (#583)
|
||||
* Make (*Logger) SetLevel a public method
|
||||
|
||||
# 1.0.1
|
||||
|
||||
* bug: fix escaping in text formatter (#575)
|
||||
|
||||
# 1.0.0
|
||||
|
||||
* Officially changed name to lower-case
|
||||
* bug: colors on Windows 10 (#541)
|
||||
* bug: fix race in accessing level (#512)
|
||||
|
||||
# 0.11.5
|
||||
|
||||
* feature: add writer and writerlevel to entry (#372)
|
||||
|
||||
# 0.11.4
|
||||
|
||||
* bug: fix undefined variable on solaris (#493)
|
||||
|
||||
# 0.11.3
|
||||
|
||||
* formatter: configure quoting of empty values (#484)
|
||||
* formatter: configure quoting character (default is `"`) (#484)
|
||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||
|
||||
# 0.11.2
|
||||
|
||||
* bug: fix windows terminal detection (#476)
|
||||
|
||||
# 0.11.1
|
||||
|
||||
* bug: fix tty detection with custom out (#471)
|
||||
|
||||
# 0.11.0
|
||||
|
||||
* performance: Use bufferpool to allocate (#370)
|
||||
* terminal: terminal detection for app-engine (#343)
|
||||
* feature: exit handler (#375)
|
||||
|
||||
# 0.10.0
|
||||
|
||||
* feature: Add a test hook (#180)
|
||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||
* performance: avoid re-allocations on `WithFields` (#335)
|
||||
|
||||
# 0.9.0
|
||||
|
||||
* logrus/text_formatter: don't emit empty msg
|
||||
* logrus/hooks/airbrake: move out of main repository
|
||||
* logrus/hooks/sentry: move out of main repository
|
||||
* logrus/hooks/papertrail: move out of main repository
|
||||
* logrus/hooks/bugsnag: move out of main repository
|
||||
* logrus/core: run tests with `-race`
|
||||
* logrus/core: detect TTY based on `stderr`
|
||||
* logrus/core: support `WithError` on logger
|
||||
* logrus/core: Solaris support
|
||||
|
||||
# 0.8.7
|
||||
|
||||
* logrus/core: fix possible race (#216)
|
||||
* logrus/doc: small typo fixes and doc improvements
|
||||
|
||||
|
||||
# 0.8.6
|
||||
|
||||
* hooks/raven: allow passing an initialized client
|
||||
|
||||
# 0.8.5
|
||||
|
||||
* logrus/core: revert #208
|
||||
|
||||
# 0.8.4
|
||||
|
||||
* formatter/text: fix data race (#218)
|
||||
|
||||
# 0.8.3
|
||||
|
||||
* logrus/core: fix entry log level (#208)
|
||||
* logrus/core: improve performance of text formatter by 40%
|
||||
* logrus/core: expose `LevelHooks` type
|
||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||
* formatter/text: print structs more verbosely
|
||||
|
||||
# 0.8.2
|
||||
|
||||
* logrus: fix more Fatal family functions
|
||||
|
||||
# 0.8.1
|
||||
|
||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||
|
||||
# 0.8.0
|
||||
|
||||
* logrus: defaults to stderr instead of stdout
|
||||
* hooks/sentry: add special field for `*http.Request`
|
||||
* formatter/text: ignore Windows for colors
|
||||
|
||||
# 0.7.3
|
||||
|
||||
* formatter/\*: allow configuration of timestamp layout
|
||||
|
||||
# 0.7.2
|
||||
|
||||
* formatter/text: Add configuration option for time format (#158)
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,513 @@
|
|||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger.
|
||||
|
||||
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
||||
simply too hard to do in a way that won't break many people's projects, which is
|
||||
the last thing you want from your Logging library (again...).
|
||||
|
||||
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
||||
security, (backwards compatible) bug fixes, and performance (where we are
|
||||
limited by the interface).
|
||||
|
||||
I believe Logrus' biggest contribution is to have played a part in today's
|
||||
widespread use of structured logging in Golang. There doesn't seem to be a
|
||||
reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
|
||||
community has built those independently. Many fantastic alternatives have sprung
|
||||
up. Logrus would look like those, had it been re-designed with what we know
|
||||
about structured logging in Go today. Check out, for example,
|
||||
[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
|
||||
|
||||
[zerolog]: https://github.com/rs/zerolog
|
||||
[zap]: https://github.com/uber-go/zap
|
||||
[apex]: https://github.com/apex/log
|
||||
|
||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||
this caused issues in the community and we needed a standard. Some environments
|
||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||
Everything using `logrus` will need to use the lower-case:
|
||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||
|
||||
To fix Glide, see [these
|
||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||
For an in-depth explanation of the casing issue, see [this
|
||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||
![Colored](http://i.imgur.com/PY7qMwd.png)
|
||||
|
||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```json
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
```
|
||||
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
||||
|
||||
```go
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableColors: true,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
```
|
||||
|
||||
#### Logging Method Name
|
||||
|
||||
If you wish to add the calling method as a field, instruct the logger via:
|
||||
```go
|
||||
log.SetReportCaller(true)
|
||||
```
|
||||
This adds the caller as 'method' like so:
|
||||
|
||||
```json
|
||||
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
||||
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
||||
```
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
||||
```
|
||||
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
||||
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
||||
environment via benchmarks:
|
||||
```
|
||||
go test -bench=.*CallerTracing
|
||||
```
|
||||
|
||||
|
||||
#### Case-sensitivity
|
||||
|
||||
The organization's name was changed to lower-case--and this will not be changed
|
||||
back. If you are getting import conflicts due to case sensitivity, please use
|
||||
the lower-case import: `github.com/sirupsen/logrus`.
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Log as JSON instead of the default ASCII formatter.
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// Output to stdout instead of the default stderr
|
||||
// Can be any io.Writer, see below for File example
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
// Only log the warning severity or above.
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
|
||||
// A common pattern is to re-use fields between logging statements by re-using
|
||||
// the logrus.Entry returned from WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stdout
|
||||
|
||||
// You could set this to any `io.Writer` such as a file
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
// log.Info("Failed to log to file, using default stderr")
|
||||
// }
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging through logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Default Fields
|
||||
|
||||
Often it's helpful to have fields _always_ attached to log statements in an
|
||||
application or parts of one. For example, you may want to always log the
|
||||
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||
every line, you can create a `logrus.Entry` to pass around instead:
|
||||
|
||||
```go
|
||||
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||
requestLogger.Warn("something not great happened")
|
||||
```
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||
`init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
log.Error("Unable to connect to local syslog daemon")
|
||||
} else {
|
||||
log.AddHook(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||
|
||||
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
||||
|
||||
|
||||
#### Level logging
|
||||
|
||||
Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Trace("Something very low level.")
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
log.Error("Something failed but I'm not quitting.")
|
||||
// Calls os.Exit(1) after logging
|
||||
log.Fatal("Bye.")
|
||||
// Calls panic() after logging
|
||||
log.Panic("I'm bailing.")
|
||||
```
|
||||
|
||||
You can set the logging level on a `Logger`, then it will only log entries with
|
||||
that severity or anything above it:
|
||||
|
||||
```go
|
||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||
log.SetLevel(log.InfoLevel)
|
||||
```
|
||||
|
||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||
environment if your application has that.
|
||||
|
||||
#### Entries
|
||||
|
||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||
automatically added to all logging events:
|
||||
|
||||
1. `time`. The timestamp when the entry was created.
|
||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||
the `AddFields` call. E.g. `Failed to send event.`
|
||||
3. `level`. The logging level. E.g. `info`.
|
||||
|
||||
#### Environments
|
||||
|
||||
Logrus has no notion of environment.
|
||||
|
||||
If you wish for hooks and formatters to only be used in specific environments,
|
||||
you should handle that yourself. For example, if your application has a global
|
||||
variable `Environment`, which is a string representation of the environment you
|
||||
could do:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
init() {
|
||||
// do something here to set environment depending on an environment variable
|
||||
// or command-line flag
|
||||
if Environment == "production" {
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
} else {
|
||||
// The TextFormatter is default, you don't actually have to do this.
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration is how `logrus` was intended to be used, but JSON in
|
||||
production is mostly only useful if you do log aggregation with tools like
|
||||
Splunk or Logstash.
|
||||
|
||||
#### Formatters
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`. For Windows, see
|
||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
||||
truncation set the `DisableLevelTruncation` field to `true`.
|
||||
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
||||
|
||||
Third party logging formatters:
|
||||
|
||||
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
||||
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
|
||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
|
||||
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
||||
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files.
|
||||
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
type MyJSONFormatter struct {
|
||||
}
|
||||
|
||||
log.SetFormatter(new(MyJSONFormatter))
|
||||
|
||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
// Note this doesn't include Time, Level and Message which are available on
|
||||
// the Entry. Consult `godoc` on information about those fields or read the
|
||||
// source of the official loggers.
|
||||
serialized, err := json.Marshal(entry.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Logger as an `io.Writer`
|
||||
|
||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||
|
||||
```go
|
||||
w := logger.Writer()
|
||||
defer w.Close()
|
||||
|
||||
srv := http.Server{
|
||||
// create a stdlib log.Logger that writes to
|
||||
// logrus.Logger.
|
||||
ErrorLog: log.New(w, "", 0),
|
||||
}
|
||||
```
|
||||
|
||||
Each line written to that writer will be printed the usual way, using formatters
|
||||
and hooks. The level for those entries is `info`.
|
||||
|
||||
This means that we can override the standard library logger easily:
|
||||
|
||||
```go
|
||||
logger := logrus.New()
|
||||
logger.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
// Use logrus for standard log output
|
||||
// Note that `log` here references stdlib's log
|
||||
// Not logrus imported under the name `log`.
|
||||
log.SetOutput(logger.Writer())
|
||||
```
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotate(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
#### Tools
|
||||
|
||||
| Tool | Description |
|
||||
| ---- | ----------- |
|
||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|
||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||
|
||||
#### Testing
|
||||
|
||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||
|
||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
|
||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||
|
||||
```go
|
||||
import(
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSomething(t*testing.T){
|
||||
logger, hook := test.NewNullLogger()
|
||||
logger.Error("Helloerror")
|
||||
|
||||
assert.Equal(t, 1, len(hook.Entries))
|
||||
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
||||
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
||||
|
||||
hook.Reset()
|
||||
assert.Nil(t, hook.LastEntry())
|
||||
}
|
||||
```
|
||||
|
||||
#### Fatal handlers
|
||||
|
||||
Logrus can register one or more functions that will be called when any `fatal`
|
||||
level message is logged. The registered handlers will be executed before
|
||||
logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
|
||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
||||
|
||||
```
|
||||
...
|
||||
handler := func() {
|
||||
// gracefully shutdown something...
|
||||
}
|
||||
logrus.RegisterExitHandler(handler)
|
||||
...
|
||||
```
|
||||
|
||||
#### Thread safety
|
||||
|
||||
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
|
||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
||||
|
||||
Situation when locking is not needed includes:
|
||||
|
||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
||||
|
||||
* Writing to logger.Out is already thread-safe, for example:
|
||||
|
||||
1) logger.Out is protected by locks.
|
||||
|
||||
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
||||
|
||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
|
@ -0,0 +1,76 @@
|
|||
package logrus
|
||||
|
||||
// The following code was sourced and modified from the
|
||||
// https://github.com/tebeka/atexit package governed by the following license:
|
||||
//
|
||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var handlers = []func(){}
|
||||
|
||||
func runHandler(handler func()) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
handler()
|
||||
}
|
||||
|
||||
func runHandlers() {
|
||||
for _, handler := range handlers {
|
||||
runHandler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
||||
func Exit(code int) {
|
||||
runHandlers()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
|
||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||
// any Fatal log entry is made.
|
||||
//
|
||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||
// message but also needs to gracefully shutdown. An example usecase could be
|
||||
// closing database connections, or sending a alert that the application is
|
||||
// closing.
|
||||
func RegisterExitHandler(handler func()) {
|
||||
handlers = append(handlers, handler)
|
||||
}
|
||||
|
||||
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
|
||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||
// any Fatal log entry is made.
|
||||
//
|
||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||
// message but also needs to gracefully shutdown. An example usecase could be
|
||||
// closing database connections, or sending a alert that the application is
|
||||
// closing.
|
||||
func DeferExitHandler(handler func()) {
|
||||
handlers = append([]func(){handler}, handlers...)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
version: "{build}"
|
||||
platform: x64
|
||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
build_script:
|
||||
- go get -t
|
||||
- go test
|
|
@ -0,0 +1,52 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
bufferPool BufferPool
|
||||
)
|
||||
|
||||
type BufferPool interface {
|
||||
Put(*bytes.Buffer)
|
||||
Get() *bytes.Buffer
|
||||
}
|
||||
|
||||
type defaultPool struct {
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func (p *defaultPool) Put(buf *bytes.Buffer) {
|
||||
p.pool.Put(buf)
|
||||
}
|
||||
|
||||
func (p *defaultPool) Get() *bytes.Buffer {
|
||||
return p.pool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufferPool.Get()
|
||||
}
|
||||
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
buf.Reset()
|
||||
bufferPool.Put(buf)
|
||||
}
|
||||
|
||||
// SetBufferPool allows to replace the default logrus buffer pool
|
||||
// to better meets the specific needs of an application.
|
||||
func SetBufferPool(bp BufferPool) {
|
||||
bufferPool = bp
|
||||
}
|
||||
|
||||
func init() {
|
||||
SetBufferPool(&defaultPool{
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 1,
|
||||
"size": 10,
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
|
||||
Output:
|
||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||
|
||||
For a full guide visit https://github.com/sirupsen/logrus
|
||||
*/
|
||||
package logrus
|
|
@ -0,0 +1,422 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// qualified package name, cached at first use
|
||||
logrusPackage string
|
||||
|
||||
// Positions in the call stack when tracing to report the calling method
|
||||
minimumCallerDepth int
|
||||
|
||||
// Used for caller information initialisation
|
||||
callerInitOnce sync.Once
|
||||
)
|
||||
|
||||
const (
|
||||
maximumCallerDepth int = 25
|
||||
knownLogrusFrames int = 4
|
||||
)
|
||||
|
||||
func init() {
|
||||
// start at the bottom of the stack before the package-name cache is primed
|
||||
minimumCallerDepth = 1
|
||||
}
|
||||
|
||||
// Defines the key when adding errors using WithError.
|
||||
var ErrorKey = "error"
|
||||
|
||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
||||
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
||||
// reused and passed around as much as you wish to avoid field duplication.
|
||||
type Entry struct {
|
||||
Logger *Logger
|
||||
|
||||
// Contains all the fields set by the user.
|
||||
Data Fields
|
||||
|
||||
// Time at which the log entry was created
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||
Level Level
|
||||
|
||||
// Calling method, with package name
|
||||
Caller *runtime.Frame
|
||||
|
||||
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||
Message string
|
||||
|
||||
// When formatter is called in entry.log(), a Buffer may be set to entry
|
||||
Buffer *bytes.Buffer
|
||||
|
||||
// Contains the context set by the user. Useful for hook processing etc.
|
||||
Context context.Context
|
||||
|
||||
// err may contain a field formatting error
|
||||
err string
|
||||
}
|
||||
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is three fields, plus one optional. Give a little extra room.
|
||||
Data: make(Fields, 6),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the bytes representation of this entry from the formatter.
|
||||
func (entry *Entry) Bytes() ([]byte, error) {
|
||||
return entry.Logger.Formatter.Format(entry)
|
||||
}
|
||||
|
||||
// Returns the string representation from the reader and ultimately the
|
||||
// formatter.
|
||||
func (entry *Entry) String() (string, error) {
|
||||
serialized, err := entry.Bytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := string(serialized)
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
||||
func (entry *Entry) WithError(err error) *Entry {
|
||||
return entry.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// Add a context to the Entry.
|
||||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||||
dataCopy := make(Fields, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
dataCopy[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
|
||||
}
|
||||
|
||||
// Add a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
}
|
||||
|
||||
// Add a map of fields to the Entry.
|
||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||
data := make(Fields, len(entry.Data)+len(fields))
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
fieldErr := entry.err
|
||||
for k, v := range fields {
|
||||
isErrField := false
|
||||
if t := reflect.TypeOf(v); t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Func:
|
||||
isErrField = true
|
||||
case reflect.Ptr:
|
||||
isErrField = t.Elem().Kind() == reflect.Func
|
||||
}
|
||||
}
|
||||
if isErrField {
|
||||
tmp := fmt.Sprintf("can not add field %q", k)
|
||||
if fieldErr != "" {
|
||||
fieldErr = entry.err + ", " + tmp
|
||||
} else {
|
||||
fieldErr = tmp
|
||||
}
|
||||
} else {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
||||
}
|
||||
|
||||
// Overrides the time of the Entry.
|
||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||
dataCopy := make(Fields, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
dataCopy[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
|
||||
}
|
||||
|
||||
// getPackageName reduces a fully qualified function name to the package name
|
||||
// There really ought to be to be a better way...
|
||||
func getPackageName(f string) string {
|
||||
for {
|
||||
lastPeriod := strings.LastIndex(f, ".")
|
||||
lastSlash := strings.LastIndex(f, "/")
|
||||
if lastPeriod > lastSlash {
|
||||
f = f[:lastPeriod]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// getCaller retrieves the name of the first non-logrus calling function
|
||||
func getCaller() *runtime.Frame {
|
||||
// cache this package's fully-qualified name
|
||||
callerInitOnce.Do(func() {
|
||||
pcs := make([]uintptr, maximumCallerDepth)
|
||||
_ = runtime.Callers(0, pcs)
|
||||
|
||||
// dynamic get the package name and the minimum caller depth
|
||||
for i := 0; i < maximumCallerDepth; i++ {
|
||||
funcName := runtime.FuncForPC(pcs[i]).Name()
|
||||
if strings.Contains(funcName, "getCaller") {
|
||||
logrusPackage = getPackageName(funcName)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
minimumCallerDepth = knownLogrusFrames
|
||||
})
|
||||
|
||||
// Restrict the lookback frames to avoid runaway lookups
|
||||
pcs := make([]uintptr, maximumCallerDepth)
|
||||
depth := runtime.Callers(minimumCallerDepth, pcs)
|
||||
frames := runtime.CallersFrames(pcs[:depth])
|
||||
|
||||
for f, again := frames.Next(); again; f, again = frames.Next() {
|
||||
pkg := getPackageName(f.Function)
|
||||
|
||||
// If the caller isn't part of this package, we're done
|
||||
if pkg != logrusPackage {
|
||||
return &f //nolint:scopelint
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, we failed to find the caller's context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (entry Entry) HasCaller() (has bool) {
|
||||
return entry.Logger != nil &&
|
||||
entry.Logger.ReportCaller &&
|
||||
entry.Caller != nil
|
||||
}
|
||||
|
||||
// This function is not declared with a pointer value because otherwise
|
||||
// race conditions will occur when using multiple goroutines
|
||||
func (entry Entry) log(level Level, msg string) {
|
||||
var buffer *bytes.Buffer
|
||||
|
||||
// Default to now, but allow users to override if they want.
|
||||
//
|
||||
// We don't have to worry about polluting future calls to Entry#log()
|
||||
// with this assignment because this function is declared with a
|
||||
// non-pointer receiver.
|
||||
if entry.Time.IsZero() {
|
||||
entry.Time = time.Now()
|
||||
}
|
||||
|
||||
entry.Level = level
|
||||
entry.Message = msg
|
||||
entry.Logger.mu.Lock()
|
||||
if entry.Logger.ReportCaller {
|
||||
entry.Caller = getCaller()
|
||||
}
|
||||
entry.Logger.mu.Unlock()
|
||||
|
||||
entry.fireHooks()
|
||||
|
||||
buffer = getBuffer()
|
||||
defer func() {
|
||||
entry.Buffer = nil
|
||||
putBuffer(buffer)
|
||||
}()
|
||||
buffer.Reset()
|
||||
entry.Buffer = buffer
|
||||
|
||||
entry.write()
|
||||
|
||||
entry.Buffer = nil
|
||||
|
||||
// To avoid Entry#log() returning a value that only would make sense for
|
||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||
// directly here.
|
||||
if level <= PanicLevel {
|
||||
panic(&entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) fireHooks() {
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
err := entry.Logger.Hooks.Fire(entry.Level, entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) write() {
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||
return
|
||||
}
|
||||
if _, err = entry.Logger.Out.Write(serialized); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Log(level Level, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.log(level, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Trace(args ...interface{}) {
|
||||
entry.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
entry.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
entry.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
entry.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warning(args ...interface{}) {
|
||||
entry.Warn(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
entry.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
entry.Log(FatalLevel, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
entry.Log(PanicLevel, args...)
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.Log(level, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
||||
entry.Logf(TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
entry.Logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
entry.Logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
entry.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
entry.Logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
entry.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
entry.Logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
entry.Logf(FatalLevel, format, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
entry.Logf(PanicLevel, format, args...)
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.Log(level, entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Traceln(args ...interface{}) {
|
||||
entry.Logln(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
entry.Logln(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
entry.Logln(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
entry.Infoln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
entry.Logln(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
entry.Warnln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
entry.Logln(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
entry.Logln(FatalLevel, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
entry.Logln(PanicLevel, args...)
|
||||
}
|
||||
|
||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||
// string allocation, we do the simplest thing.
|
||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||
msg := fmt.Sprintln(args...)
|
||||
return msg[:len(msg)-1]
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// std is the name of the standard logger in stdlib `log`
|
||||
std = New()
|
||||
)
|
||||
|
||||
func StandardLogger() *Logger {
|
||||
return std
|
||||
}
|
||||
|
||||
// SetOutput sets the standard logger output.
|
||||
func SetOutput(out io.Writer) {
|
||||
std.SetOutput(out)
|
||||
}
|
||||
|
||||
// SetFormatter sets the standard logger formatter.
|
||||
func SetFormatter(formatter Formatter) {
|
||||
std.SetFormatter(formatter)
|
||||
}
|
||||
|
||||
// SetReportCaller sets whether the standard logger will include the calling
|
||||
// method as a field.
|
||||
func SetReportCaller(include bool) {
|
||||
std.SetReportCaller(include)
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level Level) {
|
||||
std.SetLevel(level)
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func GetLevel() Level {
|
||||
return std.GetLevel()
|
||||
}
|
||||
|
||||
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
||||
func IsLevelEnabled(level Level) bool {
|
||||
return std.IsLevelEnabled(level)
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the standard logger hooks.
|
||||
func AddHook(hook Hook) {
|
||||
std.AddHook(hook)
|
||||
}
|
||||
|
||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||
func WithError(err error) *Entry {
|
||||
return std.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// WithContext creates an entry from the standard logger and adds a context to it.
|
||||
func WithContext(ctx context.Context) *Entry {
|
||||
return std.WithContext(ctx)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *Entry {
|
||||
return std.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithFields(fields Fields) *Entry {
|
||||
return std.WithFields(fields)
|
||||
}
|
||||
|
||||
// WithTime creates an entry from the standard logger and overrides the time of
|
||||
// logs generated with it.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithTime(t time.Time) *Entry {
|
||||
return std.WithTime(t)
|
||||
}
|
||||
|
||||
// Trace logs a message at level Trace on the standard logger.
|
||||
func Trace(args ...interface{}) {
|
||||
std.Trace(args...)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
std.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
std.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
std.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
std.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
std.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
std.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
std.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatal(args ...interface{}) {
|
||||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// TraceFn logs a message from a func at level Trace on the standard logger.
|
||||
func TraceFn(fn LogFunction) {
|
||||
std.TraceFn(fn)
|
||||
}
|
||||
|
||||
// DebugFn logs a message from a func at level Debug on the standard logger.
|
||||
func DebugFn(fn LogFunction) {
|
||||
std.DebugFn(fn)
|
||||
}
|
||||
|
||||
// PrintFn logs a message from a func at level Info on the standard logger.
|
||||
func PrintFn(fn LogFunction) {
|
||||
std.PrintFn(fn)
|
||||
}
|
||||
|
||||
// InfoFn logs a message from a func at level Info on the standard logger.
|
||||
func InfoFn(fn LogFunction) {
|
||||
std.InfoFn(fn)
|
||||
}
|
||||
|
||||
// WarnFn logs a message from a func at level Warn on the standard logger.
|
||||
func WarnFn(fn LogFunction) {
|
||||
std.WarnFn(fn)
|
||||
}
|
||||
|
||||
// WarningFn logs a message from a func at level Warn on the standard logger.
|
||||
func WarningFn(fn LogFunction) {
|
||||
std.WarningFn(fn)
|
||||
}
|
||||
|
||||
// ErrorFn logs a message from a func at level Error on the standard logger.
|
||||
func ErrorFn(fn LogFunction) {
|
||||
std.ErrorFn(fn)
|
||||
}
|
||||
|
||||
// PanicFn logs a message from a func at level Panic on the standard logger.
|
||||
func PanicFn(fn LogFunction) {
|
||||
std.PanicFn(fn)
|
||||
}
|
||||
|
||||
// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func FatalFn(fn LogFunction) {
|
||||
std.FatalFn(fn)
|
||||
}
|
||||
|
||||
// Tracef logs a message at level Trace on the standard logger.
|
||||
func Tracef(format string, args ...interface{}) {
|
||||
std.Tracef(format, args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
std.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
std.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
std.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
std.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
std.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
std.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
std.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Traceln logs a message at level Trace on the standard logger.
|
||||
func Traceln(args ...interface{}) {
|
||||
std.Traceln(args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
std.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
std.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
std.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
std.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
std.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
std.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
std.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatalln(args ...interface{}) {
|
||||
std.Fatalln(args...)
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package logrus
|
||||
|
||||
import "time"
|
||||
|
||||
// Default key names for the default fields
|
||||
const (
|
||||
defaultTimestampFormat = time.RFC3339
|
||||
FieldKeyMsg = "msg"
|
||||
FieldKeyLevel = "level"
|
||||
FieldKeyTime = "time"
|
||||
FieldKeyLogrusError = "logrus_error"
|
||||
FieldKeyFunc = "func"
|
||||
FieldKeyFile = "file"
|
||||
)
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) {
|
||||
timeKey := fieldMap.resolve(FieldKeyTime)
|
||||
if t, ok := data[timeKey]; ok {
|
||||
data["fields."+timeKey] = t
|
||||
delete(data, timeKey)
|
||||
}
|
||||
|
||||
msgKey := fieldMap.resolve(FieldKeyMsg)
|
||||
if m, ok := data[msgKey]; ok {
|
||||
data["fields."+msgKey] = m
|
||||
delete(data, msgKey)
|
||||
}
|
||||
|
||||
levelKey := fieldMap.resolve(FieldKeyLevel)
|
||||
if l, ok := data[levelKey]; ok {
|
||||
data["fields."+levelKey] = l
|
||||
delete(data, levelKey)
|
||||
}
|
||||
|
||||
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
|
||||
if l, ok := data[logrusErrKey]; ok {
|
||||
data["fields."+logrusErrKey] = l
|
||||
delete(data, logrusErrKey)
|
||||
}
|
||||
|
||||
// If reportCaller is not set, 'func' will not conflict.
|
||||
if reportCaller {
|
||||
funcKey := fieldMap.resolve(FieldKeyFunc)
|
||||
if l, ok := data[funcKey]; ok {
|
||||
data["fields."+funcKey] = l
|
||||
}
|
||||
fileKey := fieldMap.resolve(FieldKeyFile)
|
||||
if l, ok := data[fileKey]; ok {
|
||||
data["fields."+fileKey] = l
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
module github.com/sirupsen/logrus
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
|
||||
)
|
||||
|
||||
go 1.13
|
|
@ -0,0 +1,10 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
@ -0,0 +1,34 @@
|
|||
package logrus
|
||||
|
||||
// A hook to be fired when logging on the logging levels returned from
|
||||
// `Levels()` on your implementation of the interface. Note that this is not
|
||||
// fired in a goroutine or a channel with workers, you should handle such
|
||||
// functionality yourself if your call is non-blocking and you don't wish for
|
||||
// the logging calls for levels returned from `Levels()` to block.
|
||||
type Hook interface {
|
||||
Levels() []Level
|
||||
Fire(*Entry) error
|
||||
}
|
||||
|
||||
// Internal type for storing the hooks on a logger instance.
|
||||
type LevelHooks map[Level][]Hook
|
||||
|
||||
// Add a hook to an instance of logger. This is called with
|
||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||
func (hooks LevelHooks) Add(hook Hook) {
|
||||
for _, level := range hook.Levels() {
|
||||
hooks[level] = append(hooks[level], hook)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||
// appropriate hooks for a log entry.
|
||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
||||
for _, hook := range hooks[level] {
|
||||
if err := hook.Fire(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type fieldKey string
|
||||
|
||||
// FieldMap allows customization of the key names for default fields.
|
||||
type FieldMap map[fieldKey]string
|
||||
|
||||
func (f FieldMap) resolve(key fieldKey) string {
|
||||
if k, ok := f[key]; ok {
|
||||
return k
|
||||
}
|
||||
|
||||
return string(key)
|
||||
}
|
||||
|
||||
// JSONFormatter formats logs into parsable json
|
||||
type JSONFormatter struct {
|
||||
// TimestampFormat sets the format used for marshaling timestamps.
|
||||
TimestampFormat string
|
||||
|
||||
// DisableTimestamp allows disabling automatic timestamps in output
|
||||
DisableTimestamp bool
|
||||
|
||||
// DisableHTMLEscape allows disabling html escaping in output
|
||||
DisableHTMLEscape bool
|
||||
|
||||
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
|
||||
DataKey string
|
||||
|
||||
// FieldMap allows users to customize the names of keys for default fields.
|
||||
// As an example:
|
||||
// formatter := &JSONFormatter{
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message",
|
||||
// FieldKeyFunc: "@caller",
|
||||
// },
|
||||
// }
|
||||
FieldMap FieldMap
|
||||
|
||||
// CallerPrettyfier can be set by the user to modify the content
|
||||
// of the function and file keys in the json data when ReportCaller is
|
||||
// activated. If any of the returned value is the empty string the
|
||||
// corresponding key will be removed from json fields.
|
||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||
|
||||
// PrettyPrint will indent all json logs
|
||||
PrettyPrint bool
|
||||
}
|
||||
|
||||
// Format renders a single log entry
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+4)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
// Otherwise errors are ignored by `encoding/json`
|
||||
// https://github.com/sirupsen/logrus/issues/137
|
||||
data[k] = v.Error()
|
||||
default:
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if f.DataKey != "" {
|
||||
newData := make(Fields, 4)
|
||||
newData[f.DataKey] = data
|
||||
data = newData
|
||||
}
|
||||
|
||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
|
||||
if entry.err != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
|
||||
}
|
||||
if !f.DisableTimestamp {
|
||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
||||
}
|
||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||
if entry.HasCaller() {
|
||||
funcVal := entry.Caller.Function
|
||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
}
|
||||
if funcVal != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
||||
}
|
||||
if fileVal != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
|
||||
}
|
||||
}
|
||||
|
||||
var b *bytes.Buffer
|
||||
if entry.Buffer != nil {
|
||||
b = entry.Buffer
|
||||
} else {
|
||||
b = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(b)
|
||||
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
|
||||
if f.PrettyPrint {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
|
@ -0,0 +1,404 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogFunction For big messages, it can be more efficient to pass a function
|
||||
// and only call it if the log level is actually enables rather than
|
||||
// generating the log message and then checking if the level is enabled
|
||||
type LogFunction func()[]interface{}
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||
// something more adventurous, such as logging to Kafka.
|
||||
Out io.Writer
|
||||
// Hooks for the logger instance. These allow firing events based on logging
|
||||
// levels and log entries. For example, to send errors to an error tracking
|
||||
// service, log to StatsD or dump the core on fatal errors.
|
||||
Hooks LevelHooks
|
||||
// All log entries pass through the formatter before logged to Out. The
|
||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||
// own that implements the `Formatter` interface, see the `README` or included
|
||||
// formatters for examples.
|
||||
Formatter Formatter
|
||||
|
||||
// Flag for whether to log caller info (off by default)
|
||||
ReportCaller bool
|
||||
|
||||
// The logging level the logger should log at. This is typically (and defaults
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged.
|
||||
Level Level
|
||||
// Used to sync writing to the log. Locking is enabled by Default
|
||||
mu MutexWrap
|
||||
// Reusable empty entry
|
||||
entryPool sync.Pool
|
||||
// Function to exit the application, defaults to `os.Exit()`
|
||||
ExitFunc exitFunc
|
||||
}
|
||||
|
||||
type exitFunc func(int)
|
||||
|
||||
type MutexWrap struct {
|
||||
lock sync.Mutex
|
||||
disabled bool
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Lock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Unlock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Disable() {
|
||||
mw.disabled = true
|
||||
}
|
||||
|
||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||
// instantiate your own:
|
||||
//
|
||||
// var log = &logrus.Logger{
|
||||
// Out: os.Stderr,
|
||||
// Formatter: new(logrus.TextFormatter),
|
||||
// Hooks: make(logrus.LevelHooks),
|
||||
// Level: logrus.DebugLevel,
|
||||
// }
|
||||
//
|
||||
// It's recommended to make this a global instance called `log`.
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(LevelHooks),
|
||||
Level: InfoLevel,
|
||||
ExitFunc: os.Exit,
|
||||
ReportCaller: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) newEntry() *Entry {
|
||||
entry, ok := logger.entryPool.Get().(*Entry)
|
||||
if ok {
|
||||
return entry
|
||||
}
|
||||
return NewEntry(logger)
|
||||
}
|
||||
|
||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||
entry.Data = map[string]interface{}{}
|
||||
logger.entryPool.Put(entry)
|
||||
}
|
||||
|
||||
// WithField allocates a new entry and adds a field to it.
|
||||
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
||||
// this new returned entry.
|
||||
// If you want multiple fields, use `WithFields`.
|
||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithField(key, value)
|
||||
}
|
||||
|
||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||
// each `Field`.
|
||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithFields(fields)
|
||||
}
|
||||
|
||||
// Add an error as single field to the log entry. All it does is call
|
||||
// `WithError` for the given `error`.
|
||||
func (logger *Logger) WithError(err error) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithError(err)
|
||||
}
|
||||
|
||||
// Add a context to the log entry.
|
||||
func (logger *Logger) WithContext(ctx context.Context) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithContext(ctx)
|
||||
}
|
||||
|
||||
// Overrides the time of the log entry.
|
||||
func (logger *Logger) WithTime(t time.Time) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithTime(t)
|
||||
}
|
||||
|
||||
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Logf(level, format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
||||
logger.Logf(TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
logger.Logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
logger.Logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Printf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
logger.Logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
logger.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
logger.Logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
logger.Logf(FatalLevel, format, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
logger.Logf(PanicLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Log(level Level, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Log(level, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Log(level, fn()...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Trace(args ...interface{}) {
|
||||
logger.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
logger.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
logger.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Print(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
logger.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
logger.Warn(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
logger.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
logger.Log(FatalLevel, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
logger.Log(PanicLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) TraceFn(fn LogFunction) {
|
||||
logger.LogFn(TraceLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) DebugFn(fn LogFunction) {
|
||||
logger.LogFn(DebugLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) InfoFn(fn LogFunction) {
|
||||
logger.LogFn(InfoLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) PrintFn(fn LogFunction) {
|
||||
entry := logger.newEntry()
|
||||
entry.Print(fn()...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) WarnFn(fn LogFunction) {
|
||||
logger.LogFn(WarnLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) WarningFn(fn LogFunction) {
|
||||
logger.WarnFn(fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) ErrorFn(fn LogFunction) {
|
||||
logger.LogFn(ErrorLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) FatalFn(fn LogFunction) {
|
||||
logger.LogFn(FatalLevel, fn)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) PanicFn(fn LogFunction) {
|
||||
logger.LogFn(PanicLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Logln(level, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Traceln(args ...interface{}) {
|
||||
logger.Logln(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
logger.Logln(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
logger.Logln(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Println(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
logger.Logln(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
logger.Warnln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
logger.Logln(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
logger.Logln(FatalLevel, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
logger.Logln(PanicLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Exit(code int) {
|
||||
runHandlers()
|
||||
if logger.ExitFunc == nil {
|
||||
logger.ExitFunc = os.Exit
|
||||
}
|
||||
logger.ExitFunc(code)
|
||||
}
|
||||
|
||||
//When file is opened with appending mode, it's safe to
|
||||
//write concurrently to a file (within 4k message on Linux).
|
||||
//In these cases user can choose to disable the lock.
|
||||
func (logger *Logger) SetNoLock() {
|
||||
logger.mu.Disable()
|
||||
}
|
||||
|
||||
func (logger *Logger) level() Level {
|
||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||
}
|
||||
|
||||
// SetLevel sets the logger level.
|
||||
func (logger *Logger) SetLevel(level Level) {
|
||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||
}
|
||||
|
||||
// GetLevel returns the logger level.
|
||||
func (logger *Logger) GetLevel() Level {
|
||||
return logger.level()
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the logger hooks.
|
||||
func (logger *Logger) AddHook(hook Hook) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Hooks.Add(hook)
|
||||
}
|
||||
|
||||
// IsLevelEnabled checks if the log level of the logger is greater than the level param
|
||||
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
||||
return logger.level() >= level
|
||||
}
|
||||
|
||||
// SetFormatter sets the logger formatter.
|
||||
func (logger *Logger) SetFormatter(formatter Formatter) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Formatter = formatter
|
||||
}
|
||||
|
||||
// SetOutput sets the logger output.
|
||||
func (logger *Logger) SetOutput(output io.Writer) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Out = output
|
||||
}
|
||||
|
||||
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.ReportCaller = reportCaller
|
||||
}
|
||||
|
||||
// ReplaceHooks replaces the logger hooks and returns the old ones
|
||||
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
||||
logger.mu.Lock()
|
||||
oldHooks := logger.Hooks
|
||||
logger.Hooks = hooks
|
||||
logger.mu.Unlock()
|
||||
return oldHooks
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to `WithFields`.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
// Level type
|
||||
type Level uint32
|
||||
|
||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||
func (level Level) String() string {
|
||||
if b, err := level.MarshalText(); err == nil {
|
||||
return string(b)
|
||||
} else {
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "panic":
|
||||
return PanicLevel, nil
|
||||
case "fatal":
|
||||
return FatalLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warn", "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
case "trace":
|
||||
return TraceLevel, nil
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (level *Level) UnmarshalText(text []byte) error {
|
||||
l, err := ParseLevel(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*level = l
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (level Level) MarshalText() ([]byte, error) {
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
return []byte("trace"), nil
|
||||
case DebugLevel:
|
||||
return []byte("debug"), nil
|
||||
case InfoLevel:
|
||||
return []byte("info"), nil
|
||||
case WarnLevel:
|
||||
return []byte("warning"), nil
|
||||
case ErrorLevel:
|
||||
return []byte("error"), nil
|
||||
case FatalLevel:
|
||||
return []byte("fatal"), nil
|
||||
case PanicLevel:
|
||||
return []byte("panic"), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
||||
}
|
||||
|
||||
// A constant exposing all logging levels
|
||||
var AllLevels = []Level{
|
||||
PanicLevel,
|
||||
FatalLevel,
|
||||
ErrorLevel,
|
||||
WarnLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
TraceLevel,
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
// Commonly used for hooks to send errors to an error tracking service.
|
||||
ErrorLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// InfoLevel level. General operational entries about what's going on inside the
|
||||
// application.
|
||||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
||||
TraceLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
var (
|
||||
_ StdLogger = &log.Logger{}
|
||||
_ StdLogger = &Entry{}
|
||||
_ StdLogger = &Logger{}
|
||||
)
|
||||
|
||||
// StdLogger is what your logrus-enabled library should take, that way
|
||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||
// interface, this is the closest we get, unfortunately.
|
||||
type StdLogger interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
Fatalln(...interface{})
|
||||
|
||||
Panic(...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Panicln(...interface{})
|
||||
}
|
||||
|
||||
// The FieldLogger interface generalizes the Entry and Logger types
|
||||
type FieldLogger interface {
|
||||
WithField(key string, value interface{}) *Entry
|
||||
WithFields(fields Fields) *Entry
|
||||
WithError(err error) *Entry
|
||||
|
||||
Debugf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warningf(format string, args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
|
||||
Debug(args ...interface{})
|
||||
Info(args ...interface{})
|
||||
Print(args ...interface{})
|
||||
Warn(args ...interface{})
|
||||
Warning(args ...interface{})
|
||||
Error(args ...interface{})
|
||||
Fatal(args ...interface{})
|
||||
Panic(args ...interface{})
|
||||
|
||||
Debugln(args ...interface{})
|
||||
Infoln(args ...interface{})
|
||||
Println(args ...interface{})
|
||||
Warnln(args ...interface{})
|
||||
Warningln(args ...interface{})
|
||||
Errorln(args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
Panicln(args ...interface{})
|
||||
|
||||
// IsDebugEnabled() bool
|
||||
// IsInfoEnabled() bool
|
||||
// IsWarnEnabled() bool
|
||||
// IsErrorEnabled() bool
|
||||
// IsFatalEnabled() bool
|
||||
// IsPanicEnabled() bool
|
||||
}
|
||||
|
||||
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
|
||||
// here for consistancy. Do not use. Use Logger or Entry instead.
|
||||
type Ext1FieldLogger interface {
|
||||
FieldLogger
|
||||
Tracef(format string, args ...interface{})
|
||||
Trace(args ...interface{})
|
||||
Traceln(args ...interface{})
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// +build appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
// +build !js
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TIOCGETA
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
return err == nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue