mirror of
https://github.com/openfaas/faas.git
synced 2025-06-09 08:46:48 +00:00
Vendoring with Glide and delete function handler
This commit is contained in:
parent
1eaf13c6c8
commit
78af82021f
@ -1,20 +1,21 @@
|
|||||||
FROM golang:1.7.5
|
FROM golang:1.7.5
|
||||||
|
|
||||||
RUN go get -d github.com/docker/docker/api/types \
|
#RUN go get -d github.com/docker/docker/api/types \
|
||||||
&& go get -d github.com/docker/docker/api/types/filters \
|
# && go get -d github.com/docker/docker/api/types/filters \
|
||||||
&& go get -d github.com/docker/docker/api/types/swarm \
|
# && go get -d github.com/docker/docker/api/types/swarm \
|
||||||
&& go get -d github.com/docker/docker/client \
|
# && go get -d github.com/docker/docker/client \
|
||||||
&& go get github.com/gorilla/mux \
|
# && go get github.com/gorilla/mux \
|
||||||
&& go get github.com/prometheus/client_golang/prometheus
|
# && go get github.com/prometheus/client_golang/prometheus
|
||||||
RUN go get -d github.com/Sirupsen/logrus
|
#RUN go get -d github.com/Sirupsen/logrus
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/alexellis/faas/gateway
|
WORKDIR /go/src/github.com/alexellis/faas/gateway
|
||||||
|
|
||||||
COPY metrics metrics
|
COPY vendor vendor
|
||||||
COPY requests requests
|
|
||||||
COPY tests tests
|
|
||||||
COPY handlers handlers
|
|
||||||
|
|
||||||
COPY server.go .
|
COPY handlers handlers
|
||||||
|
COPY metrics metrics
|
||||||
|
COPY requests requests
|
||||||
|
COPY tests tests
|
||||||
|
COPY server.go .
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
|
||||||
|
14
gateway/Dockerfile.newbuild
Normal file
14
gateway/Dockerfile.newbuild
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
FROM golang:1.7.5
|
||||||
|
# RUN mkdir -p /go/src/github.com/alexellis/faas/gateway
|
||||||
|
WORKDIR /go/src/github.com/alexellis/faas/gateway
|
||||||
|
|
||||||
|
COPY vendor vendor
|
||||||
|
|
||||||
|
COPY handlers handlers
|
||||||
|
COPY metrics metrics
|
||||||
|
COPY requests requests
|
||||||
|
COPY tests tests
|
||||||
|
COPY server.go .
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gateway .
|
||||||
|
|
25
gateway/Dockerfile.runtime
Normal file
25
gateway/Dockerfile.runtime
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
FROM golang:1.7.5
|
||||||
|
WORKDIR /go/src/github.com/alexellis/faas/gateway
|
||||||
|
|
||||||
|
COPY vendor vendor
|
||||||
|
|
||||||
|
COPY handlers handlers
|
||||||
|
COPY metrics metrics
|
||||||
|
COPY requests requests
|
||||||
|
COPY tests tests
|
||||||
|
COPY server.go .
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gateway .
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
WORKDIR /root/
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
ENV http_proxy ""
|
||||||
|
ENV https_proxy ""
|
||||||
|
|
||||||
|
COPY --from 0 gateway .
|
||||||
|
|
||||||
|
COPY --from 0 assets assets
|
||||||
|
|
||||||
|
CMD ["./gateway"]
|
@ -1,10 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo Building functions/gateway:build
|
echo Building functions/gateway:build
|
||||||
|
|
||||||
docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
|
docker build --no-cache --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
|
||||||
-t functions/gateway:build . -f Dockerfile.build && \
|
-t functions/gateway:build . -f Dockerfile.build && \
|
||||||
docker create --name gateway_extract functions/gateway:build && \
|
docker create --name gateway_extract functions/gateway:build && \
|
||||||
docker cp gateway_extract:/go/src/github.com/alexellis/faas/gateway/app ./gateway && \
|
docker cp gateway_extract:/go/src/github.com/alexellis/faas/gateway/app ./gateway && \
|
||||||
docker rm -f gateway_extract && \
|
docker rm -f gateway_extract && \
|
||||||
echo Building functions/gateway:latest && \
|
echo Building functions/gateway:latest && \
|
||||||
docker build --no-cache -t functions/gateway:latest-dev .
|
docker build --no-cache -t functions/gateway:latest-dev . && \
|
||||||
|
rm ./gateway
|
||||||
|
95
gateway/glide.lock
generated
Normal file
95
gateway/glide.lock
generated
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
hash: 339150b194ecf3311e4cf3262b0cdb830f4d26065d91486108ae75c129d447a2
|
||||||
|
updated: 2017-04-26T18:58:24.877329489+01:00
|
||||||
|
imports:
|
||||||
|
- name: github.com/beorn7/perks
|
||||||
|
version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
|
||||||
|
subpackages:
|
||||||
|
- quantile
|
||||||
|
- name: github.com/docker/distribution
|
||||||
|
version: e85ef3c019a2809b3397771d385581ee09fc7649
|
||||||
|
subpackages:
|
||||||
|
- digestset
|
||||||
|
- reference
|
||||||
|
- name: github.com/docker/docker
|
||||||
|
version: 4480e0417eb01caf29ae285880ec51a0180faf7f
|
||||||
|
subpackages:
|
||||||
|
- api
|
||||||
|
- api/types
|
||||||
|
- api/types/blkiodev
|
||||||
|
- api/types/container
|
||||||
|
- api/types/events
|
||||||
|
- api/types/filters
|
||||||
|
- api/types/image
|
||||||
|
- api/types/mount
|
||||||
|
- api/types/network
|
||||||
|
- api/types/registry
|
||||||
|
- api/types/strslice
|
||||||
|
- api/types/swarm
|
||||||
|
- api/types/time
|
||||||
|
- api/types/versions
|
||||||
|
- api/types/volume
|
||||||
|
- client
|
||||||
|
- pkg/ioutils
|
||||||
|
- pkg/longpath
|
||||||
|
- pkg/system
|
||||||
|
- pkg/tlsconfig
|
||||||
|
- name: github.com/docker/go-connections
|
||||||
|
version: e15c02316c12de00874640cd76311849de2aeed5
|
||||||
|
subpackages:
|
||||||
|
- nat
|
||||||
|
- sockets
|
||||||
|
- tlsconfig
|
||||||
|
- name: github.com/docker/go-units
|
||||||
|
version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
|
||||||
|
- name: github.com/docker/libtrust
|
||||||
|
version: aabc10ec26b754e797f9028f4589c5b7bd90dc20
|
||||||
|
- name: github.com/golang/protobuf
|
||||||
|
version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef
|
||||||
|
subpackages:
|
||||||
|
- proto
|
||||||
|
- name: github.com/gorilla/context
|
||||||
|
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||||
|
- name: github.com/gorilla/mux
|
||||||
|
version: 599cba5e7b6137d46ddf58fb1765f5d928e69604
|
||||||
|
- name: github.com/matttproud/golang_protobuf_extensions
|
||||||
|
version: c12348ce28de40eed0136aa2b644d0ee0650e56c
|
||||||
|
subpackages:
|
||||||
|
- pbutil
|
||||||
|
- name: github.com/Microsoft/go-winio
|
||||||
|
version: f3b1913901892ada21d52d0cffe1d4c7080d36b7
|
||||||
|
- name: github.com/opencontainers/go-digest
|
||||||
|
version: aa2ec055abd10d26d539eb630a92241b781ce4bc
|
||||||
|
- name: github.com/pkg/errors
|
||||||
|
version: ff09b135c25aae272398c51a07235b90a75aa4f0
|
||||||
|
- name: github.com/prometheus/client_golang
|
||||||
|
version: 7d9484283ebefa862b5b7727d4344cfdf9a0d138
|
||||||
|
subpackages:
|
||||||
|
- prometheus
|
||||||
|
- name: github.com/prometheus/client_model
|
||||||
|
version: 6f3806018612930941127f2a7c6c453ba2c527d2
|
||||||
|
subpackages:
|
||||||
|
- go
|
||||||
|
- name: github.com/prometheus/common
|
||||||
|
version: 9e0844febd9e2856f839c9cb974fbd676d1755a8
|
||||||
|
subpackages:
|
||||||
|
- expfmt
|
||||||
|
- internal/bitbucket.org/ww/goautoneg
|
||||||
|
- model
|
||||||
|
- name: github.com/prometheus/procfs
|
||||||
|
version: 6ac8c5d890d415025dd5aae7595bcb2a6e7e2fad
|
||||||
|
subpackages:
|
||||||
|
- xfs
|
||||||
|
- name: github.com/Sirupsen/logrus
|
||||||
|
version: 10f801ebc38b33738c9d17d50860f484a0988ff5
|
||||||
|
- name: golang.org/x/net
|
||||||
|
version: da118f7b8e5954f39d0d2130ab35d4bf0e3cb344
|
||||||
|
subpackages:
|
||||||
|
- context
|
||||||
|
- context/ctxhttp
|
||||||
|
- proxy
|
||||||
|
- name: golang.org/x/sys
|
||||||
|
version: 9f30dcbe5be197894515a338a9bda9253567ea8f
|
||||||
|
subpackages:
|
||||||
|
- unix
|
||||||
|
- windows
|
||||||
|
testImports: []
|
16
gateway/glide.yaml
Normal file
16
gateway/glide.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package: github.com/alexellis/faas/gateway
|
||||||
|
import:
|
||||||
|
- package: github.com/Sirupsen/logrus
|
||||||
|
- package: github.com/docker/docker
|
||||||
|
subpackages:
|
||||||
|
- api/types
|
||||||
|
- api/types/filters
|
||||||
|
- api/types/swarm
|
||||||
|
- client
|
||||||
|
- package: github.com/gorilla/mux
|
||||||
|
- package: github.com/prometheus/client_golang
|
||||||
|
subpackages:
|
||||||
|
- prometheus
|
||||||
|
- package: github.com/prometheus/client_model
|
||||||
|
subpackages:
|
||||||
|
- go
|
@ -76,6 +76,68 @@ func MakeFunctionReader(metricsOptions metrics.MetricOptions, c *client.Client)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeDeleteFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Client) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
req := requests.DeleteFunctionRequest{}
|
||||||
|
defer r.Body.Close()
|
||||||
|
reqData, _ := ioutil.ReadAll(r.Body)
|
||||||
|
unmarshalErr := json.Unmarshal(reqData, &req)
|
||||||
|
|
||||||
|
if (len(req.FunctionName) == 0) || unmarshalErr != nil {
|
||||||
|
log.Printf("Error parsing request to remove service: %s\n", unmarshalErr)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Attempting to remove service %s\n", req.FunctionName)
|
||||||
|
|
||||||
|
serviceFilter := filters.NewArgs()
|
||||||
|
options := types.ServiceListOptions{
|
||||||
|
Filters: serviceFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
services, err := c.ServiceList(context.Background(), options)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Filter only "faas" functions (via metadata?)
|
||||||
|
var serviceIDs []string
|
||||||
|
for _, service := range services {
|
||||||
|
isFunction := len(service.Spec.TaskTemplate.ContainerSpec.Labels["function"]) > 0
|
||||||
|
|
||||||
|
if isFunction && req.FunctionName == service.Spec.Name {
|
||||||
|
serviceIDs = append(serviceIDs, service.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(len(serviceIDs))
|
||||||
|
if len(serviceIDs) == 0 {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(fmt.Sprintf("No such service found: %s.", req.FunctionName)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceRemoveErrors []error
|
||||||
|
for _, serviceID := range serviceIDs {
|
||||||
|
err := c.ServiceRemove(context.Background(), serviceID)
|
||||||
|
if err != nil {
|
||||||
|
serviceRemoveErrors = append(serviceRemoveErrors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(serviceRemoveErrors) > 0 {
|
||||||
|
log.Printf("Error(s) removing service: %s\n", req.FunctionName)
|
||||||
|
log.Println(serviceRemoveErrors)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MakeNewFunctionHandler creates a new function (service) inside the swarm network.
|
// MakeNewFunctionHandler creates a new function (service) inside the swarm network.
|
||||||
func MakeNewFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Client) http.HandlerFunc {
|
func MakeNewFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Client) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -8,6 +8,10 @@ type CreateFunctionRequest struct {
|
|||||||
EnvProcess string `json:"envProcess"`
|
EnvProcess string `json:"envProcess"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteFunctionRequest struct {
|
||||||
|
FunctionName string `json:"functionName"`
|
||||||
|
}
|
||||||
|
|
||||||
type AlexaSessionApplication struct {
|
type AlexaSessionApplication struct {
|
||||||
ApplicationId string `json:"applicationId"`
|
ApplicationId string `json:"applicationId"`
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ func main() {
|
|||||||
r.HandleFunc("/system/alert", faasHandlers.MakeAlertHandler(dockerClient))
|
r.HandleFunc("/system/alert", faasHandlers.MakeAlertHandler(dockerClient))
|
||||||
r.HandleFunc("/system/functions", faasHandlers.MakeFunctionReader(metricsOptions, dockerClient)).Methods("GET")
|
r.HandleFunc("/system/functions", faasHandlers.MakeFunctionReader(metricsOptions, dockerClient)).Methods("GET")
|
||||||
r.HandleFunc("/system/functions", faasHandlers.MakeNewFunctionHandler(metricsOptions, dockerClient)).Methods("POST")
|
r.HandleFunc("/system/functions", faasHandlers.MakeNewFunctionHandler(metricsOptions, dockerClient)).Methods("POST")
|
||||||
|
r.HandleFunc("/system/functions", faasHandlers.MakeDeleteFunctionHandler(metricsOptions, dockerClient)).Methods("DELETE")
|
||||||
|
|
||||||
r.HandleFunc("/", faasHandlers.MakeProxy(metricsOptions, false, dockerClient, &logger)).Methods("POST")
|
r.HandleFunc("/", faasHandlers.MakeProxy(metricsOptions, false, dockerClient, &logger)).Methods("POST")
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestScale1to5(t *testing.T) {
|
func TestScale1to5(t *testing.T) {
|
||||||
newReplicas := handlers.CalculateReplicas("firing", 1)
|
newReplicas := handlers.CalculateReplicas("firing", 1, 20)
|
||||||
if newReplicas != 5 {
|
if newReplicas != 5 {
|
||||||
t.Log("Expected increment in blocks of 5 from 1 to 5")
|
t.Log("Expected increment in blocks of 5 from 1 to 5")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -15,7 +15,7 @@ func TestScale1to5(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestScale5to10(t *testing.T) {
|
func TestScale5to10(t *testing.T) {
|
||||||
newReplicas := handlers.CalculateReplicas("firing", 5)
|
newReplicas := handlers.CalculateReplicas("firing", 5, 20)
|
||||||
if newReplicas != 10 {
|
if newReplicas != 10 {
|
||||||
t.Log("Expected increment in blocks of 5 from 5 to 10")
|
t.Log("Expected increment in blocks of 5 from 5 to 10")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -23,7 +23,7 @@ func TestScale5to10(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestScaleCeilingOf20Replicas_Noaction(t *testing.T) {
|
func TestScaleCeilingOf20Replicas_Noaction(t *testing.T) {
|
||||||
newReplicas := handlers.CalculateReplicas("firing", 20)
|
newReplicas := handlers.CalculateReplicas("firing", 20, 20)
|
||||||
if newReplicas != 20 {
|
if newReplicas != 20 {
|
||||||
t.Log("Expected ceiling of 20 replicas")
|
t.Log("Expected ceiling of 20 replicas")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -31,7 +31,7 @@ func TestScaleCeilingOf20Replicas_Noaction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestScaleCeilingOf20Replicas(t *testing.T) {
|
func TestScaleCeilingOf20Replicas(t *testing.T) {
|
||||||
newReplicas := handlers.CalculateReplicas("firing", 19)
|
newReplicas := handlers.CalculateReplicas("firing", 19, 20)
|
||||||
if newReplicas != 20 {
|
if newReplicas != 20 {
|
||||||
t.Log("Expected ceiling of 20 replicas")
|
t.Log("Expected ceiling of 20 replicas")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -39,7 +39,7 @@ func TestScaleCeilingOf20Replicas(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBackingOff10to1(t *testing.T) {
|
func TestBackingOff10to1(t *testing.T) {
|
||||||
newReplicas := handlers.CalculateReplicas("resolved", 10)
|
newReplicas := handlers.CalculateReplicas("resolved", 10, 20)
|
||||||
if newReplicas != 1 {
|
if newReplicas != 1 {
|
||||||
t.Log("Expected backing off to 1 replica")
|
t.Log("Expected backing off to 1 replica")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
34
gateway/tests/integration/deletefunction_test.go
Normal file
34
gateway/tests/integration/deletefunction_test.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package inttests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDelete_EmptyFunctionGivenFails(t *testing.T) {
|
||||||
|
reqBody := `{"functionName":""}`
|
||||||
|
_, code, err := fireRequest("http://localhost:8080/system/functions", http.MethodDelete, reqBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if code != http.StatusBadRequest {
|
||||||
|
t.Errorf("Got HTTP code: %d, want %d\n", code, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete_NonExistingFunctionGives404(t *testing.T) {
|
||||||
|
reqBody := `{"functionName":"does_not_exist"}`
|
||||||
|
_, code, err := fireRequest("http://localhost:8080/system/functions", http.MethodDelete, reqBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if code != http.StatusNotFound {
|
||||||
|
t.Errorf("Got HTTP code: %d, want %d\n", code, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
1
gateway/vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
1
gateway/vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.exe
|
22
gateway/vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
22
gateway/vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Microsoft
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
22
gateway/vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
22
gateway/vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# go-winio
|
||||||
|
|
||||||
|
This repository contains utilities for efficiently performing Win32 IO operations in
|
||||||
|
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
||||||
|
for using named pipes as a net transport.
|
||||||
|
|
||||||
|
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
|
||||||
|
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
|
||||||
|
newer operating systems. This is similar to the implementation of network sockets in Go's net
|
||||||
|
package.
|
||||||
|
|
||||||
|
Please see the LICENSE file for licensing information.
|
||||||
|
|
||||||
|
This project has adopted the [Microsoft Open Source Code of
|
||||||
|
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||||
|
see the [Code of Conduct
|
||||||
|
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||||
|
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||||
|
questions or comments.
|
||||||
|
|
||||||
|
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
|
||||||
|
for another named pipe implementation.
|
27
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/LICENSE
generated
vendored
Normal file
27
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* 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.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER 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.
|
344
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/common.go
generated
vendored
Normal file
344
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/common.go
generated
vendored
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package tar implements access to tar archives.
|
||||||
|
// It aims to cover most of the variations, including those produced
|
||||||
|
// by GNU and BSD tars.
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
|
||||||
|
// http://www.gnu.org/software/tar/manual/html_node/Standard.html
|
||||||
|
// http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
blockSize = 512
|
||||||
|
|
||||||
|
// Types
|
||||||
|
TypeReg = '0' // regular file
|
||||||
|
TypeRegA = '\x00' // regular file
|
||||||
|
TypeLink = '1' // hard link
|
||||||
|
TypeSymlink = '2' // symbolic link
|
||||||
|
TypeChar = '3' // character device node
|
||||||
|
TypeBlock = '4' // block device node
|
||||||
|
TypeDir = '5' // directory
|
||||||
|
TypeFifo = '6' // fifo node
|
||||||
|
TypeCont = '7' // reserved
|
||||||
|
TypeXHeader = 'x' // extended header
|
||||||
|
TypeXGlobalHeader = 'g' // global extended header
|
||||||
|
TypeGNULongName = 'L' // Next file has a long name
|
||||||
|
TypeGNULongLink = 'K' // Next file symlinks to a file w/ a long name
|
||||||
|
TypeGNUSparse = 'S' // sparse file
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Header represents a single header in a tar archive.
|
||||||
|
// Some fields may not be populated.
|
||||||
|
type Header struct {
|
||||||
|
Name string // name of header file entry
|
||||||
|
Mode int64 // permission and mode bits
|
||||||
|
Uid int // user id of owner
|
||||||
|
Gid int // group id of owner
|
||||||
|
Size int64 // length in bytes
|
||||||
|
ModTime time.Time // modified time
|
||||||
|
Typeflag byte // type of header entry
|
||||||
|
Linkname string // target name of link
|
||||||
|
Uname string // user name of owner
|
||||||
|
Gname string // group name of owner
|
||||||
|
Devmajor int64 // major number of character or block device
|
||||||
|
Devminor int64 // minor number of character or block device
|
||||||
|
AccessTime time.Time // access time
|
||||||
|
ChangeTime time.Time // status change time
|
||||||
|
CreationTime time.Time // creation time
|
||||||
|
Xattrs map[string]string
|
||||||
|
Winheaders map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// File name constants from the tar spec.
|
||||||
|
const (
|
||||||
|
fileNameSize = 100 // Maximum number of bytes in a standard tar name.
|
||||||
|
fileNamePrefixSize = 155 // Maximum number of ustar extension bytes.
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileInfo returns an os.FileInfo for the Header.
|
||||||
|
func (h *Header) FileInfo() os.FileInfo {
|
||||||
|
return headerFileInfo{h}
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerFileInfo implements os.FileInfo.
|
||||||
|
type headerFileInfo struct {
|
||||||
|
h *Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi headerFileInfo) Size() int64 { return fi.h.Size }
|
||||||
|
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
||||||
|
func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
|
||||||
|
func (fi headerFileInfo) Sys() interface{} { return fi.h }
|
||||||
|
|
||||||
|
// Name returns the base name of the file.
|
||||||
|
func (fi headerFileInfo) Name() string {
|
||||||
|
if fi.IsDir() {
|
||||||
|
return path.Base(path.Clean(fi.h.Name))
|
||||||
|
}
|
||||||
|
return path.Base(fi.h.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns the permission and mode bits for the headerFileInfo.
|
||||||
|
func (fi headerFileInfo) Mode() (mode os.FileMode) {
|
||||||
|
// Set file permission bits.
|
||||||
|
mode = os.FileMode(fi.h.Mode).Perm()
|
||||||
|
|
||||||
|
// Set setuid, setgid and sticky bits.
|
||||||
|
if fi.h.Mode&c_ISUID != 0 {
|
||||||
|
// setuid
|
||||||
|
mode |= os.ModeSetuid
|
||||||
|
}
|
||||||
|
if fi.h.Mode&c_ISGID != 0 {
|
||||||
|
// setgid
|
||||||
|
mode |= os.ModeSetgid
|
||||||
|
}
|
||||||
|
if fi.h.Mode&c_ISVTX != 0 {
|
||||||
|
// sticky
|
||||||
|
mode |= os.ModeSticky
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set file mode bits.
|
||||||
|
// clear perm, setuid, setgid and sticky bits.
|
||||||
|
m := os.FileMode(fi.h.Mode) &^ 07777
|
||||||
|
if m == c_ISDIR {
|
||||||
|
// directory
|
||||||
|
mode |= os.ModeDir
|
||||||
|
}
|
||||||
|
if m == c_ISFIFO {
|
||||||
|
// named pipe (FIFO)
|
||||||
|
mode |= os.ModeNamedPipe
|
||||||
|
}
|
||||||
|
if m == c_ISLNK {
|
||||||
|
// symbolic link
|
||||||
|
mode |= os.ModeSymlink
|
||||||
|
}
|
||||||
|
if m == c_ISBLK {
|
||||||
|
// device file
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
}
|
||||||
|
if m == c_ISCHR {
|
||||||
|
// Unix character device
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
mode |= os.ModeCharDevice
|
||||||
|
}
|
||||||
|
if m == c_ISSOCK {
|
||||||
|
// Unix domain socket
|
||||||
|
mode |= os.ModeSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fi.h.Typeflag {
|
||||||
|
case TypeSymlink:
|
||||||
|
// symbolic link
|
||||||
|
mode |= os.ModeSymlink
|
||||||
|
case TypeChar:
|
||||||
|
// character device node
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
mode |= os.ModeCharDevice
|
||||||
|
case TypeBlock:
|
||||||
|
// block device node
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
case TypeDir:
|
||||||
|
// directory
|
||||||
|
mode |= os.ModeDir
|
||||||
|
case TypeFifo:
|
||||||
|
// fifo node
|
||||||
|
mode |= os.ModeNamedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysStat, if non-nil, populates h from system-dependent fields of fi.
|
||||||
|
var sysStat func(fi os.FileInfo, h *Header) error
|
||||||
|
|
||||||
|
// Mode constants from the tar spec.
|
||||||
|
const (
|
||||||
|
c_ISUID = 04000 // Set uid
|
||||||
|
c_ISGID = 02000 // Set gid
|
||||||
|
c_ISVTX = 01000 // Save text (sticky bit)
|
||||||
|
c_ISDIR = 040000 // Directory
|
||||||
|
c_ISFIFO = 010000 // FIFO
|
||||||
|
c_ISREG = 0100000 // Regular file
|
||||||
|
c_ISLNK = 0120000 // Symbolic link
|
||||||
|
c_ISBLK = 060000 // Block special file
|
||||||
|
c_ISCHR = 020000 // Character special file
|
||||||
|
c_ISSOCK = 0140000 // Socket
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keywords for the PAX Extended Header
|
||||||
|
const (
|
||||||
|
paxAtime = "atime"
|
||||||
|
paxCharset = "charset"
|
||||||
|
paxComment = "comment"
|
||||||
|
paxCtime = "ctime" // please note that ctime is not a valid pax header.
|
||||||
|
paxCreationTime = "LIBARCHIVE.creationtime"
|
||||||
|
paxGid = "gid"
|
||||||
|
paxGname = "gname"
|
||||||
|
paxLinkpath = "linkpath"
|
||||||
|
paxMtime = "mtime"
|
||||||
|
paxPath = "path"
|
||||||
|
paxSize = "size"
|
||||||
|
paxUid = "uid"
|
||||||
|
paxUname = "uname"
|
||||||
|
paxXattr = "SCHILY.xattr."
|
||||||
|
paxWindows = "MSWINDOWS."
|
||||||
|
paxNone = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileInfoHeader creates a partially-populated Header from fi.
|
||||||
|
// If fi describes a symlink, FileInfoHeader records link as the link target.
|
||||||
|
// If fi describes a directory, a slash is appended to the name.
|
||||||
|
// Because os.FileInfo's Name method returns only the base name of
|
||||||
|
// the file it describes, it may be necessary to modify the Name field
|
||||||
|
// of the returned header to provide the full path name of the file.
|
||||||
|
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
||||||
|
if fi == nil {
|
||||||
|
return nil, errors.New("tar: FileInfo is nil")
|
||||||
|
}
|
||||||
|
fm := fi.Mode()
|
||||||
|
h := &Header{
|
||||||
|
Name: fi.Name(),
|
||||||
|
ModTime: fi.ModTime(),
|
||||||
|
Mode: int64(fm.Perm()), // or'd with c_IS* constants later
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case fm.IsRegular():
|
||||||
|
h.Mode |= c_ISREG
|
||||||
|
h.Typeflag = TypeReg
|
||||||
|
h.Size = fi.Size()
|
||||||
|
case fi.IsDir():
|
||||||
|
h.Typeflag = TypeDir
|
||||||
|
h.Mode |= c_ISDIR
|
||||||
|
h.Name += "/"
|
||||||
|
case fm&os.ModeSymlink != 0:
|
||||||
|
h.Typeflag = TypeSymlink
|
||||||
|
h.Mode |= c_ISLNK
|
||||||
|
h.Linkname = link
|
||||||
|
case fm&os.ModeDevice != 0:
|
||||||
|
if fm&os.ModeCharDevice != 0 {
|
||||||
|
h.Mode |= c_ISCHR
|
||||||
|
h.Typeflag = TypeChar
|
||||||
|
} else {
|
||||||
|
h.Mode |= c_ISBLK
|
||||||
|
h.Typeflag = TypeBlock
|
||||||
|
}
|
||||||
|
case fm&os.ModeNamedPipe != 0:
|
||||||
|
h.Typeflag = TypeFifo
|
||||||
|
h.Mode |= c_ISFIFO
|
||||||
|
case fm&os.ModeSocket != 0:
|
||||||
|
h.Mode |= c_ISSOCK
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
|
||||||
|
}
|
||||||
|
if fm&os.ModeSetuid != 0 {
|
||||||
|
h.Mode |= c_ISUID
|
||||||
|
}
|
||||||
|
if fm&os.ModeSetgid != 0 {
|
||||||
|
h.Mode |= c_ISGID
|
||||||
|
}
|
||||||
|
if fm&os.ModeSticky != 0 {
|
||||||
|
h.Mode |= c_ISVTX
|
||||||
|
}
|
||||||
|
// If possible, populate additional fields from OS-specific
|
||||||
|
// FileInfo fields.
|
||||||
|
if sys, ok := fi.Sys().(*Header); ok {
|
||||||
|
// This FileInfo came from a Header (not the OS). Use the
|
||||||
|
// original Header to populate all remaining fields.
|
||||||
|
h.Uid = sys.Uid
|
||||||
|
h.Gid = sys.Gid
|
||||||
|
h.Uname = sys.Uname
|
||||||
|
h.Gname = sys.Gname
|
||||||
|
h.AccessTime = sys.AccessTime
|
||||||
|
h.ChangeTime = sys.ChangeTime
|
||||||
|
if sys.Xattrs != nil {
|
||||||
|
h.Xattrs = make(map[string]string)
|
||||||
|
for k, v := range sys.Xattrs {
|
||||||
|
h.Xattrs[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sys.Typeflag == TypeLink {
|
||||||
|
// hard link
|
||||||
|
h.Typeflag = TypeLink
|
||||||
|
h.Size = 0
|
||||||
|
h.Linkname = sys.Linkname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sysStat != nil {
|
||||||
|
return h, sysStat(fi, h)
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroBlock = make([]byte, blockSize)
|
||||||
|
|
||||||
|
// POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values.
|
||||||
|
// We compute and return both.
|
||||||
|
func checksum(header []byte) (unsigned int64, signed int64) {
|
||||||
|
for i := 0; i < len(header); i++ {
|
||||||
|
if i == 148 {
|
||||||
|
// The chksum field (header[148:156]) is special: it should be treated as space bytes.
|
||||||
|
unsigned += ' ' * 8
|
||||||
|
signed += ' ' * 8
|
||||||
|
i += 7
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unsigned += int64(header[i])
|
||||||
|
signed += int64(int8(header[i]))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type slicer []byte
|
||||||
|
|
||||||
|
func (sp *slicer) next(n int) (b []byte) {
|
||||||
|
s := *sp
|
||||||
|
b, *sp = s[0:n], s[n:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isASCII(s string) bool {
|
||||||
|
for _, c := range s {
|
||||||
|
if c >= 0x80 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func toASCII(s string) string {
|
||||||
|
if isASCII(s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, c := range s {
|
||||||
|
if c < 0x80 {
|
||||||
|
buf.WriteByte(byte(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHeaderOnlyType checks if the given type flag is of the type that has no
|
||||||
|
// data section even if a size is specified.
|
||||||
|
func isHeaderOnlyType(flag byte) bool {
|
||||||
|
switch flag {
|
||||||
|
case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
80
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/example_test.go
generated
vendored
Normal file
80
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/example_test.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tar_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Create a buffer to write our archive to.
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// Create a new tar archive.
|
||||||
|
tw := tar.NewWriter(buf)
|
||||||
|
|
||||||
|
// Add some files to the archive.
|
||||||
|
var files = []struct {
|
||||||
|
Name, Body string
|
||||||
|
}{
|
||||||
|
{"readme.txt", "This archive contains some text files."},
|
||||||
|
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
|
||||||
|
{"todo.txt", "Get animal handling license."},
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
hdr := &tar.Header{
|
||||||
|
Name: file.Name,
|
||||||
|
Mode: 0600,
|
||||||
|
Size: int64(len(file.Body)),
|
||||||
|
}
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make sure to check the error on Close.
|
||||||
|
if err := tw.Close(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the tar archive for reading.
|
||||||
|
r := bytes.NewReader(buf.Bytes())
|
||||||
|
tr := tar.NewReader(r)
|
||||||
|
|
||||||
|
// Iterate through the files in the archive.
|
||||||
|
for {
|
||||||
|
hdr, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
// end of tar archive
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Contents of %s:\n", hdr.Name)
|
||||||
|
if _, err := io.Copy(os.Stdout, tr); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Contents of readme.txt:
|
||||||
|
// This archive contains some text files.
|
||||||
|
// Contents of gopher.txt:
|
||||||
|
// Gopher names:
|
||||||
|
// George
|
||||||
|
// Geoffrey
|
||||||
|
// Gonzo
|
||||||
|
// Contents of todo.txt:
|
||||||
|
// Get animal handling license.
|
||||||
|
}
|
1002
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/reader.go
generated
vendored
Normal file
1002
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/reader.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1125
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/reader_test.go
generated
vendored
Normal file
1125
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/reader_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
20
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/stat_atim.go
generated
vendored
Normal file
20
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/stat_atim.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux dragonfly openbsd solaris
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func statAtime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(st.Atim.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func statCtime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(st.Ctim.Unix())
|
||||||
|
}
|
20
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/stat_atimespec.go
generated
vendored
Normal file
20
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/stat_atimespec.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin freebsd netbsd
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func statAtime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(st.Atimespec.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func statCtime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(st.Ctimespec.Unix())
|
||||||
|
}
|
32
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/stat_unix.go
generated
vendored
Normal file
32
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/stat_unix.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux darwin dragonfly freebsd openbsd netbsd solaris
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sysStat = statUnix
|
||||||
|
}
|
||||||
|
|
||||||
|
func statUnix(fi os.FileInfo, h *Header) error {
|
||||||
|
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
h.Uid = int(sys.Uid)
|
||||||
|
h.Gid = int(sys.Gid)
|
||||||
|
// TODO(bradfitz): populate username & group. os/user
|
||||||
|
// doesn't cache LookupId lookups, and lacks group
|
||||||
|
// lookup functions.
|
||||||
|
h.AccessTime = statAtime(sys)
|
||||||
|
h.ChangeTime = statCtime(sys)
|
||||||
|
// TODO(bradfitz): major/minor device numbers?
|
||||||
|
return nil
|
||||||
|
}
|
325
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/tar_test.go
generated
vendored
Normal file
325
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/tar_test.go
generated
vendored
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileInfoHeader(t *testing.T) {
|
||||||
|
fi, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h, err := FileInfoHeader(fi, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FileInfoHeader: %v", err)
|
||||||
|
}
|
||||||
|
if g, e := h.Name, "small.txt"; g != e {
|
||||||
|
t.Errorf("Name = %q; want %q", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISREG; g != e {
|
||||||
|
t.Errorf("Mode = %#o; want %#o", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.Size, int64(5); g != e {
|
||||||
|
t.Errorf("Size = %v; want %v", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
|
||||||
|
t.Errorf("ModTime = %v; want %v", g, e)
|
||||||
|
}
|
||||||
|
// FileInfoHeader should error when passing nil FileInfo
|
||||||
|
if _, err := FileInfoHeader(nil, ""); err == nil {
|
||||||
|
t.Fatalf("Expected error when passing nil to FileInfoHeader")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileInfoHeaderDir(t *testing.T) {
|
||||||
|
fi, err := os.Stat("testdata")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h, err := FileInfoHeader(fi, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FileInfoHeader: %v", err)
|
||||||
|
}
|
||||||
|
if g, e := h.Name, "testdata/"; g != e {
|
||||||
|
t.Errorf("Name = %q; want %q", g, e)
|
||||||
|
}
|
||||||
|
// Ignoring c_ISGID for golang.org/issue/4867
|
||||||
|
if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm())|c_ISDIR; g != e {
|
||||||
|
t.Errorf("Mode = %#o; want %#o", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.Size, int64(0); g != e {
|
||||||
|
t.Errorf("Size = %v; want %v", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
|
||||||
|
t.Errorf("ModTime = %v; want %v", g, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileInfoHeaderSymlink(t *testing.T) {
|
||||||
|
h, err := FileInfoHeader(symlink{}, "some-target")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if g, e := h.Name, "some-symlink"; g != e {
|
||||||
|
t.Errorf("Name = %q; want %q", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.Linkname, "some-target"; g != e {
|
||||||
|
t.Errorf("Linkname = %q; want %q", g, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type symlink struct{}
|
||||||
|
|
||||||
|
func (symlink) Name() string { return "some-symlink" }
|
||||||
|
func (symlink) Size() int64 { return 0 }
|
||||||
|
func (symlink) Mode() os.FileMode { return os.ModeSymlink }
|
||||||
|
func (symlink) ModTime() time.Time { return time.Time{} }
|
||||||
|
func (symlink) IsDir() bool { return false }
|
||||||
|
func (symlink) Sys() interface{} { return nil }
|
||||||
|
|
||||||
|
func TestRoundTrip(t *testing.T) {
|
||||||
|
data := []byte("some file contents")
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
tw := NewWriter(&b)
|
||||||
|
hdr := &Header{
|
||||||
|
Name: "file.txt",
|
||||||
|
Uid: 1 << 21, // too big for 8 octal digits
|
||||||
|
Size: int64(len(data)),
|
||||||
|
ModTime: time.Now(),
|
||||||
|
}
|
||||||
|
// tar only supports second precision.
|
||||||
|
hdr.ModTime = hdr.ModTime.Add(-time.Duration(hdr.ModTime.Nanosecond()) * time.Nanosecond)
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatalf("tw.WriteHeader: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := tw.Write(data); err != nil {
|
||||||
|
t.Fatalf("tw.Write: %v", err)
|
||||||
|
}
|
||||||
|
if err := tw.Close(); err != nil {
|
||||||
|
t.Fatalf("tw.Close: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read it back.
|
||||||
|
tr := NewReader(&b)
|
||||||
|
rHdr, err := tr.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("tr.Next: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(rHdr, hdr) {
|
||||||
|
t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
|
||||||
|
}
|
||||||
|
rData, err := ioutil.ReadAll(tr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Read: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(rData, data) {
|
||||||
|
t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerRoundTripTest struct {
|
||||||
|
h *Header
|
||||||
|
fm os.FileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderRoundTrip(t *testing.T) {
|
||||||
|
golden := []headerRoundTripTest{
|
||||||
|
// regular file.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "test.txt",
|
||||||
|
Mode: 0644 | c_ISREG,
|
||||||
|
Size: 12,
|
||||||
|
ModTime: time.Unix(1360600916, 0),
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0644,
|
||||||
|
},
|
||||||
|
// symbolic link.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "link.txt",
|
||||||
|
Mode: 0777 | c_ISLNK,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360600852, 0),
|
||||||
|
Typeflag: TypeSymlink,
|
||||||
|
},
|
||||||
|
fm: 0777 | os.ModeSymlink,
|
||||||
|
},
|
||||||
|
// character device node.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "dev/null",
|
||||||
|
Mode: 0666 | c_ISCHR,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360578951, 0),
|
||||||
|
Typeflag: TypeChar,
|
||||||
|
},
|
||||||
|
fm: 0666 | os.ModeDevice | os.ModeCharDevice,
|
||||||
|
},
|
||||||
|
// block device node.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "dev/sda",
|
||||||
|
Mode: 0660 | c_ISBLK,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360578954, 0),
|
||||||
|
Typeflag: TypeBlock,
|
||||||
|
},
|
||||||
|
fm: 0660 | os.ModeDevice,
|
||||||
|
},
|
||||||
|
// directory.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "dir/",
|
||||||
|
Mode: 0755 | c_ISDIR,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360601116, 0),
|
||||||
|
Typeflag: TypeDir,
|
||||||
|
},
|
||||||
|
fm: 0755 | os.ModeDir,
|
||||||
|
},
|
||||||
|
// fifo node.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "dev/initctl",
|
||||||
|
Mode: 0600 | c_ISFIFO,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360578949, 0),
|
||||||
|
Typeflag: TypeFifo,
|
||||||
|
},
|
||||||
|
fm: 0600 | os.ModeNamedPipe,
|
||||||
|
},
|
||||||
|
// setuid.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "bin/su",
|
||||||
|
Mode: 0755 | c_ISREG | c_ISUID,
|
||||||
|
Size: 23232,
|
||||||
|
ModTime: time.Unix(1355405093, 0),
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0755 | os.ModeSetuid,
|
||||||
|
},
|
||||||
|
// setguid.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "group.txt",
|
||||||
|
Mode: 0750 | c_ISREG | c_ISGID,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360602346, 0),
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0750 | os.ModeSetgid,
|
||||||
|
},
|
||||||
|
// sticky.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "sticky.txt",
|
||||||
|
Mode: 0600 | c_ISREG | c_ISVTX,
|
||||||
|
Size: 7,
|
||||||
|
ModTime: time.Unix(1360602540, 0),
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0600 | os.ModeSticky,
|
||||||
|
},
|
||||||
|
// hard link.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "hard.txt",
|
||||||
|
Mode: 0644 | c_ISREG,
|
||||||
|
Size: 0,
|
||||||
|
Linkname: "file.txt",
|
||||||
|
ModTime: time.Unix(1360600916, 0),
|
||||||
|
Typeflag: TypeLink,
|
||||||
|
},
|
||||||
|
fm: 0644,
|
||||||
|
},
|
||||||
|
// More information.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "info.txt",
|
||||||
|
Mode: 0600 | c_ISREG,
|
||||||
|
Size: 0,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 1000,
|
||||||
|
ModTime: time.Unix(1360602540, 0),
|
||||||
|
Uname: "slartibartfast",
|
||||||
|
Gname: "users",
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, g := range golden {
|
||||||
|
fi := g.h.FileInfo()
|
||||||
|
h2, err := FileInfoHeader(fi, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(fi.Name(), "/") {
|
||||||
|
t.Errorf("FileInfo of %q contains slash: %q", g.h.Name, fi.Name())
|
||||||
|
}
|
||||||
|
name := path.Base(g.h.Name)
|
||||||
|
if fi.IsDir() {
|
||||||
|
name += "/"
|
||||||
|
}
|
||||||
|
if got, want := h2.Name, name; got != want {
|
||||||
|
t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Size, g.h.Size; got != want {
|
||||||
|
t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Uid, g.h.Uid; got != want {
|
||||||
|
t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Gid, g.h.Gid; got != want {
|
||||||
|
t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Uname, g.h.Uname; got != want {
|
||||||
|
t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Gname, g.h.Gname; got != want {
|
||||||
|
t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Linkname, g.h.Linkname; got != want {
|
||||||
|
t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Typeflag, g.h.Typeflag; got != want {
|
||||||
|
t.Logf("%#v %#v", g.h, fi.Sys())
|
||||||
|
t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Mode, g.h.Mode; got != want {
|
||||||
|
t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := fi.Mode(), g.fm; got != want {
|
||||||
|
t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.AccessTime, g.h.AccessTime; got != want {
|
||||||
|
t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.ChangeTime, g.h.ChangeTime; got != want {
|
||||||
|
t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.ModTime, g.h.ModTime; got != want {
|
||||||
|
t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if sysh, ok := fi.Sys().(*Header); !ok || sysh != g.h {
|
||||||
|
t.Errorf("i=%d: Sys didn't return original *Header", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/gnu-multi-hdrs.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/gnu-multi-hdrs.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/gnu.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/gnu.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/hardlink.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/hardlink.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/hdr-only.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/hdr-only.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/issue10968.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/issue10968.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/issue11169.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/issue11169.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/issue12435.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/issue12435.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/neg-size.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/neg-size.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/nil-uid.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/nil-uid.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/pax-multi-hdrs.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/pax-multi-hdrs.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/pax-path-hdr.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/pax-path-hdr.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/pax.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/pax.tar
generated
vendored
Normal file
Binary file not shown.
1
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/small.txt
generated
vendored
Normal file
1
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/small.txt
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Kilts
|
1
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/small2.txt
generated
vendored
Normal file
1
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/small2.txt
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Google.com
|
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/sparse-formats.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/sparse-formats.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/star.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/star.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/ustar-file-reg.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/ustar-file-reg.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/ustar.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/ustar.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/v7.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/v7.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/writer-big-long.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/writer-big-long.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/writer-big.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/writer-big.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/writer.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/writer.tar
generated
vendored
Normal file
Binary file not shown.
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/xattrs.tar
generated
vendored
Normal file
BIN
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/testdata/xattrs.tar
generated
vendored
Normal file
Binary file not shown.
444
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/writer.go
generated
vendored
Normal file
444
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/writer.go
generated
vendored
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
// TODO(dsymonds):
|
||||||
|
// - catch more errors (no first header, etc.)
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWriteTooLong = errors.New("archive/tar: write too long")
|
||||||
|
ErrFieldTooLong = errors.New("archive/tar: header field too long")
|
||||||
|
ErrWriteAfterClose = errors.New("archive/tar: write after close")
|
||||||
|
errInvalidHeader = errors.New("archive/tar: header field too long or contains invalid values")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Writer provides sequential writing of a tar archive in POSIX.1 format.
|
||||||
|
// A tar archive consists of a sequence of files.
|
||||||
|
// Call WriteHeader to begin a new file, and then call Write to supply that file's data,
|
||||||
|
// writing at most hdr.Size bytes in total.
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
nb int64 // number of unwritten bytes for current file entry
|
||||||
|
pad int64 // amount of padding to write after current file entry
|
||||||
|
closed bool
|
||||||
|
usedBinary bool // whether the binary numeric field extension was used
|
||||||
|
preferPax bool // use pax header instead of binary numeric header
|
||||||
|
hdrBuff [blockSize]byte // buffer to use in writeHeader when writing a regular header
|
||||||
|
paxHdrBuff [blockSize]byte // buffer to use in writeHeader when writing a pax header
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatter struct {
|
||||||
|
err error // Last error seen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter creates a new Writer writing to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer { return &Writer{w: w, preferPax: true} }
|
||||||
|
|
||||||
|
// Flush finishes writing the current file (optional).
|
||||||
|
func (tw *Writer) Flush() error {
|
||||||
|
if tw.nb > 0 {
|
||||||
|
tw.err = fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb)
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := tw.nb + tw.pad
|
||||||
|
for n > 0 && tw.err == nil {
|
||||||
|
nr := n
|
||||||
|
if nr > blockSize {
|
||||||
|
nr = blockSize
|
||||||
|
}
|
||||||
|
var nw int
|
||||||
|
nw, tw.err = tw.w.Write(zeroBlock[0:nr])
|
||||||
|
n -= int64(nw)
|
||||||
|
}
|
||||||
|
tw.nb = 0
|
||||||
|
tw.pad = 0
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write s into b, terminating it with a NUL if there is room.
|
||||||
|
func (f *formatter) formatString(b []byte, s string) {
|
||||||
|
if len(s) > len(b) {
|
||||||
|
f.err = ErrFieldTooLong
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ascii := toASCII(s)
|
||||||
|
copy(b, ascii)
|
||||||
|
if len(ascii) < len(b) {
|
||||||
|
b[len(ascii)] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode x as an octal ASCII string and write it into b with leading zeros.
|
||||||
|
func (f *formatter) formatOctal(b []byte, x int64) {
|
||||||
|
s := strconv.FormatInt(x, 8)
|
||||||
|
// leading zeros, but leave room for a NUL.
|
||||||
|
for len(s)+1 < len(b) {
|
||||||
|
s = "0" + s
|
||||||
|
}
|
||||||
|
f.formatString(b, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fitsInBase256 reports whether x can be encoded into n bytes using base-256
|
||||||
|
// encoding. Unlike octal encoding, base-256 encoding does not require that the
|
||||||
|
// string ends with a NUL character. Thus, all n bytes are available for output.
|
||||||
|
//
|
||||||
|
// If operating in binary mode, this assumes strict GNU binary mode; which means
|
||||||
|
// that the first byte can only be either 0x80 or 0xff. Thus, the first byte is
|
||||||
|
// equivalent to the sign bit in two's complement form.
|
||||||
|
func fitsInBase256(n int, x int64) bool {
|
||||||
|
var binBits = uint(n-1) * 8
|
||||||
|
return n >= 9 || (x >= -1<<binBits && x < 1<<binBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write x into b, as binary (GNUtar/star extension).
|
||||||
|
func (f *formatter) formatNumeric(b []byte, x int64) {
|
||||||
|
if fitsInBase256(len(b), x) {
|
||||||
|
for i := len(b) - 1; i >= 0; i-- {
|
||||||
|
b[i] = byte(x)
|
||||||
|
x >>= 8
|
||||||
|
}
|
||||||
|
b[0] |= 0x80 // Highest bit indicates binary format
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.formatOctal(b, 0) // Last resort, just write zero
|
||||||
|
f.err = ErrFieldTooLong
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
minTime = time.Unix(0, 0)
|
||||||
|
// There is room for 11 octal digits (33 bits) of mtime.
|
||||||
|
maxTime = minTime.Add((1<<33 - 1) * time.Second)
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteHeader writes hdr and prepares to accept the file's contents.
|
||||||
|
// WriteHeader calls Flush if it is not the first header.
|
||||||
|
// Calling after a Close will return ErrWriteAfterClose.
|
||||||
|
func (tw *Writer) WriteHeader(hdr *Header) error {
|
||||||
|
return tw.writeHeader(hdr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes hdr and prepares to accept the file's contents.
|
||||||
|
// WriteHeader calls Flush if it is not the first header.
|
||||||
|
// Calling after a Close will return ErrWriteAfterClose.
|
||||||
|
// As this method is called internally by writePax header to allow it to
|
||||||
|
// suppress writing the pax header.
|
||||||
|
func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
|
||||||
|
if tw.closed {
|
||||||
|
return ErrWriteAfterClose
|
||||||
|
}
|
||||||
|
if tw.err == nil {
|
||||||
|
tw.Flush()
|
||||||
|
}
|
||||||
|
if tw.err != nil {
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// a map to hold pax header records, if any are needed
|
||||||
|
paxHeaders := make(map[string]string)
|
||||||
|
|
||||||
|
// TODO(shanemhansen): we might want to use PAX headers for
|
||||||
|
// subsecond time resolution, but for now let's just capture
|
||||||
|
// too long fields or non ascii characters
|
||||||
|
|
||||||
|
var f formatter
|
||||||
|
var header []byte
|
||||||
|
|
||||||
|
// We need to select which scratch buffer to use carefully,
|
||||||
|
// since this method is called recursively to write PAX headers.
|
||||||
|
// If allowPax is true, this is the non-recursive call, and we will use hdrBuff.
|
||||||
|
// If allowPax is false, we are being called by writePAXHeader, and hdrBuff is
|
||||||
|
// already being used by the non-recursive call, so we must use paxHdrBuff.
|
||||||
|
header = tw.hdrBuff[:]
|
||||||
|
if !allowPax {
|
||||||
|
header = tw.paxHdrBuff[:]
|
||||||
|
}
|
||||||
|
copy(header, zeroBlock)
|
||||||
|
s := slicer(header)
|
||||||
|
|
||||||
|
// Wrappers around formatter that automatically sets paxHeaders if the
|
||||||
|
// argument extends beyond the capacity of the input byte slice.
|
||||||
|
var formatString = func(b []byte, s string, paxKeyword string) {
|
||||||
|
needsPaxHeader := paxKeyword != paxNone && len(s) > len(b) || !isASCII(s)
|
||||||
|
if needsPaxHeader {
|
||||||
|
paxHeaders[paxKeyword] = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.formatString(b, s)
|
||||||
|
}
|
||||||
|
var formatNumeric = func(b []byte, x int64, paxKeyword string) {
|
||||||
|
// Try octal first.
|
||||||
|
s := strconv.FormatInt(x, 8)
|
||||||
|
if len(s) < len(b) {
|
||||||
|
f.formatOctal(b, x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is too long for octal, and PAX is preferred, use a PAX header.
|
||||||
|
if paxKeyword != paxNone && tw.preferPax {
|
||||||
|
f.formatOctal(b, 0)
|
||||||
|
s := strconv.FormatInt(x, 10)
|
||||||
|
paxHeaders[paxKeyword] = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tw.usedBinary = true
|
||||||
|
f.formatNumeric(b, x)
|
||||||
|
}
|
||||||
|
var formatTime = func(b []byte, t time.Time, paxKeyword string) {
|
||||||
|
var unixTime int64
|
||||||
|
if !t.Before(minTime) && !t.After(maxTime) {
|
||||||
|
unixTime = t.Unix()
|
||||||
|
}
|
||||||
|
formatNumeric(b, unixTime, paxNone)
|
||||||
|
|
||||||
|
// Write a PAX header if the time didn't fit precisely.
|
||||||
|
if paxKeyword != "" && tw.preferPax && allowPax && (t.Nanosecond() != 0 || !t.Before(minTime) || !t.After(maxTime)) {
|
||||||
|
paxHeaders[paxKeyword] = formatPAXTime(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep a reference to the filename to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
|
||||||
|
pathHeaderBytes := s.next(fileNameSize)
|
||||||
|
|
||||||
|
formatString(pathHeaderBytes, hdr.Name, paxPath)
|
||||||
|
|
||||||
|
f.formatOctal(s.next(8), hdr.Mode) // 100:108
|
||||||
|
formatNumeric(s.next(8), int64(hdr.Uid), paxUid) // 108:116
|
||||||
|
formatNumeric(s.next(8), int64(hdr.Gid), paxGid) // 116:124
|
||||||
|
formatNumeric(s.next(12), hdr.Size, paxSize) // 124:136
|
||||||
|
formatTime(s.next(12), hdr.ModTime, paxMtime) // 136:148
|
||||||
|
s.next(8) // chksum (148:156)
|
||||||
|
s.next(1)[0] = hdr.Typeflag // 156:157
|
||||||
|
|
||||||
|
formatString(s.next(100), hdr.Linkname, paxLinkpath)
|
||||||
|
|
||||||
|
copy(s.next(8), []byte("ustar\x0000")) // 257:265
|
||||||
|
formatString(s.next(32), hdr.Uname, paxUname) // 265:297
|
||||||
|
formatString(s.next(32), hdr.Gname, paxGname) // 297:329
|
||||||
|
formatNumeric(s.next(8), hdr.Devmajor, paxNone) // 329:337
|
||||||
|
formatNumeric(s.next(8), hdr.Devminor, paxNone) // 337:345
|
||||||
|
|
||||||
|
// keep a reference to the prefix to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
|
||||||
|
prefixHeaderBytes := s.next(155)
|
||||||
|
formatString(prefixHeaderBytes, "", paxNone) // 345:500 prefix
|
||||||
|
|
||||||
|
// Use the GNU magic instead of POSIX magic if we used any GNU extensions.
|
||||||
|
if tw.usedBinary {
|
||||||
|
copy(header[257:265], []byte("ustar \x00"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, paxPathUsed := paxHeaders[paxPath]
|
||||||
|
// try to use a ustar header when only the name is too long
|
||||||
|
if !tw.preferPax && len(paxHeaders) == 1 && paxPathUsed {
|
||||||
|
prefix, suffix, ok := splitUSTARPath(hdr.Name)
|
||||||
|
if ok {
|
||||||
|
// Since we can encode in USTAR format, disable PAX header.
|
||||||
|
delete(paxHeaders, paxPath)
|
||||||
|
|
||||||
|
// Update the path fields
|
||||||
|
formatString(pathHeaderBytes, suffix, paxNone)
|
||||||
|
formatString(prefixHeaderBytes, prefix, paxNone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The chksum field is terminated by a NUL and a space.
|
||||||
|
// This is different from the other octal fields.
|
||||||
|
chksum, _ := checksum(header)
|
||||||
|
f.formatOctal(header[148:155], chksum) // Never fails
|
||||||
|
header[155] = ' '
|
||||||
|
|
||||||
|
// Check if there were any formatting errors.
|
||||||
|
if f.err != nil {
|
||||||
|
tw.err = f.err
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if allowPax {
|
||||||
|
if !hdr.AccessTime.IsZero() {
|
||||||
|
paxHeaders[paxAtime] = formatPAXTime(hdr.AccessTime)
|
||||||
|
}
|
||||||
|
if !hdr.ChangeTime.IsZero() {
|
||||||
|
paxHeaders[paxCtime] = formatPAXTime(hdr.ChangeTime)
|
||||||
|
}
|
||||||
|
if !hdr.CreationTime.IsZero() {
|
||||||
|
paxHeaders[paxCreationTime] = formatPAXTime(hdr.CreationTime)
|
||||||
|
}
|
||||||
|
for k, v := range hdr.Xattrs {
|
||||||
|
paxHeaders[paxXattr+k] = v
|
||||||
|
}
|
||||||
|
for k, v := range hdr.Winheaders {
|
||||||
|
paxHeaders[paxWindows+k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(paxHeaders) > 0 {
|
||||||
|
if !allowPax {
|
||||||
|
return errInvalidHeader
|
||||||
|
}
|
||||||
|
if err := tw.writePAXHeader(hdr, paxHeaders); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tw.nb = int64(hdr.Size)
|
||||||
|
tw.pad = (blockSize - (tw.nb % blockSize)) % blockSize
|
||||||
|
|
||||||
|
_, tw.err = tw.w.Write(header)
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatPAXTime(t time.Time) string {
|
||||||
|
sec := t.Unix()
|
||||||
|
usec := t.Nanosecond()
|
||||||
|
s := strconv.FormatInt(sec, 10)
|
||||||
|
if usec != 0 {
|
||||||
|
s = fmt.Sprintf("%s.%09d", s, usec)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitUSTARPath splits a path according to USTAR prefix and suffix rules.
|
||||||
|
// If the path is not splittable, then it will return ("", "", false).
|
||||||
|
func splitUSTARPath(name string) (prefix, suffix string, ok bool) {
|
||||||
|
length := len(name)
|
||||||
|
if length <= fileNameSize || !isASCII(name) {
|
||||||
|
return "", "", false
|
||||||
|
} else if length > fileNamePrefixSize+1 {
|
||||||
|
length = fileNamePrefixSize + 1
|
||||||
|
} else if name[length-1] == '/' {
|
||||||
|
length--
|
||||||
|
}
|
||||||
|
|
||||||
|
i := strings.LastIndex(name[:length], "/")
|
||||||
|
nlen := len(name) - i - 1 // nlen is length of suffix
|
||||||
|
plen := i // plen is length of prefix
|
||||||
|
if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return name[:i], name[i+1:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// writePaxHeader writes an extended pax header to the
|
||||||
|
// archive.
|
||||||
|
func (tw *Writer) writePAXHeader(hdr *Header, paxHeaders map[string]string) error {
|
||||||
|
// Prepare extended header
|
||||||
|
ext := new(Header)
|
||||||
|
ext.Typeflag = TypeXHeader
|
||||||
|
// Setting ModTime is required for reader parsing to
|
||||||
|
// succeed, and seems harmless enough.
|
||||||
|
ext.ModTime = hdr.ModTime
|
||||||
|
// The spec asks that we namespace our pseudo files
|
||||||
|
// with the current pid. However, this results in differing outputs
|
||||||
|
// for identical inputs. As such, the constant 0 is now used instead.
|
||||||
|
// golang.org/issue/12358
|
||||||
|
dir, file := path.Split(hdr.Name)
|
||||||
|
fullName := path.Join(dir, "PaxHeaders.0", file)
|
||||||
|
|
||||||
|
ascii := toASCII(fullName)
|
||||||
|
if len(ascii) > 100 {
|
||||||
|
ascii = ascii[:100]
|
||||||
|
}
|
||||||
|
ext.Name = ascii
|
||||||
|
// Construct the body
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Keys are sorted before writing to body to allow deterministic output.
|
||||||
|
var keys []string
|
||||||
|
for k := range paxHeaders {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
fmt.Fprint(&buf, formatPAXRecord(k, paxHeaders[k]))
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.Size = int64(len(buf.Bytes()))
|
||||||
|
if err := tw.writeHeader(ext, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := tw.Write(buf.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPAXRecord formats a single PAX record, prefixing it with the
|
||||||
|
// appropriate length.
|
||||||
|
func formatPAXRecord(k, v string) string {
|
||||||
|
const padding = 3 // Extra padding for ' ', '=', and '\n'
|
||||||
|
size := len(k) + len(v) + padding
|
||||||
|
size += len(strconv.Itoa(size))
|
||||||
|
record := fmt.Sprintf("%d %s=%s\n", size, k, v)
|
||||||
|
|
||||||
|
// Final adjustment if adding size field increased the record size.
|
||||||
|
if len(record) != size {
|
||||||
|
size = len(record)
|
||||||
|
record = fmt.Sprintf("%d %s=%s\n", size, k, v)
|
||||||
|
}
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to the current entry in the tar archive.
|
||||||
|
// Write returns the error ErrWriteTooLong if more than
|
||||||
|
// hdr.Size bytes are written after WriteHeader.
|
||||||
|
func (tw *Writer) Write(b []byte) (n int, err error) {
|
||||||
|
if tw.closed {
|
||||||
|
err = ErrWriteAfterClose
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overwrite := false
|
||||||
|
if int64(len(b)) > tw.nb {
|
||||||
|
b = b[0:tw.nb]
|
||||||
|
overwrite = true
|
||||||
|
}
|
||||||
|
n, err = tw.w.Write(b)
|
||||||
|
tw.nb -= int64(n)
|
||||||
|
if err == nil && overwrite {
|
||||||
|
err = ErrWriteTooLong
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tw.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the tar archive, flushing any unwritten
|
||||||
|
// data to the underlying writer.
|
||||||
|
func (tw *Writer) Close() error {
|
||||||
|
if tw.err != nil || tw.closed {
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
tw.Flush()
|
||||||
|
tw.closed = true
|
||||||
|
if tw.err != nil {
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// trailer: two zero blocks
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
_, tw.err = tw.w.Write(zeroBlock)
|
||||||
|
if tw.err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tw.err
|
||||||
|
}
|
739
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/writer_test.go
generated
vendored
Normal file
739
gateway/vendor/github.com/Microsoft/go-winio/archive/tar/writer_test.go
generated
vendored
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"testing/iotest"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type writerTestEntry struct {
|
||||||
|
header *Header
|
||||||
|
contents string
|
||||||
|
}
|
||||||
|
|
||||||
|
type writerTest struct {
|
||||||
|
file string // filename of expected output
|
||||||
|
entries []*writerTestEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
var writerTests = []*writerTest{
|
||||||
|
// The writer test file was produced with this command:
|
||||||
|
// tar (GNU tar) 1.26
|
||||||
|
// ln -s small.txt link.txt
|
||||||
|
// tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
|
||||||
|
{
|
||||||
|
file: "testdata/writer.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "small.txt",
|
||||||
|
Mode: 0640,
|
||||||
|
Uid: 73025,
|
||||||
|
Gid: 5000,
|
||||||
|
Size: 5,
|
||||||
|
ModTime: time.Unix(1246508266, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "dsymonds",
|
||||||
|
Gname: "eng",
|
||||||
|
},
|
||||||
|
contents: "Kilts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "small2.txt",
|
||||||
|
Mode: 0640,
|
||||||
|
Uid: 73025,
|
||||||
|
Gid: 5000,
|
||||||
|
Size: 11,
|
||||||
|
ModTime: time.Unix(1245217492, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "dsymonds",
|
||||||
|
Gname: "eng",
|
||||||
|
},
|
||||||
|
contents: "Google.com\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "link.txt",
|
||||||
|
Mode: 0777,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 1000,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1314603082, 0),
|
||||||
|
Typeflag: '2',
|
||||||
|
Linkname: "small.txt",
|
||||||
|
Uname: "strings",
|
||||||
|
Gname: "strings",
|
||||||
|
},
|
||||||
|
// no contents
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// The truncated test file was produced using these commands:
|
||||||
|
// dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
|
||||||
|
// tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
|
||||||
|
{
|
||||||
|
file: "testdata/writer-big.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "tmp/16gig.txt",
|
||||||
|
Mode: 0640,
|
||||||
|
Uid: 73025,
|
||||||
|
Gid: 5000,
|
||||||
|
Size: 16 << 30,
|
||||||
|
ModTime: time.Unix(1254699560, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "dsymonds",
|
||||||
|
Gname: "eng",
|
||||||
|
},
|
||||||
|
// fake contents
|
||||||
|
contents: strings.Repeat("\x00", 4<<10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// The truncated test file was produced using these commands:
|
||||||
|
// dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt
|
||||||
|
// tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar
|
||||||
|
{
|
||||||
|
file: "testdata/writer-big-long.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: strings.Repeat("longname/", 15) + "16gig.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 1000,
|
||||||
|
Size: 16 << 30,
|
||||||
|
ModTime: time.Unix(1399583047, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "guillaume",
|
||||||
|
Gname: "guillaume",
|
||||||
|
},
|
||||||
|
// fake contents
|
||||||
|
contents: strings.Repeat("\x00", 4<<10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// This file was produced using gnu tar 1.17
|
||||||
|
// gnutar -b 4 --format=ustar (longname/)*15 + file.txt
|
||||||
|
{
|
||||||
|
file: "testdata/ustar.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: strings.Repeat("longname/", 15) + "file.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 0765,
|
||||||
|
Gid: 024,
|
||||||
|
Size: 06,
|
||||||
|
ModTime: time.Unix(1360135598, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "shane",
|
||||||
|
Gname: "staff",
|
||||||
|
},
|
||||||
|
contents: "hello\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// This file was produced using gnu tar 1.26
|
||||||
|
// echo "Slartibartfast" > file.txt
|
||||||
|
// ln file.txt hard.txt
|
||||||
|
// tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt
|
||||||
|
{
|
||||||
|
file: "testdata/hardlink.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "file.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 100,
|
||||||
|
Size: 15,
|
||||||
|
ModTime: time.Unix(1425484303, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "vbatts",
|
||||||
|
Gname: "users",
|
||||||
|
},
|
||||||
|
contents: "Slartibartfast\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "hard.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 100,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1425484303, 0),
|
||||||
|
Typeflag: '1',
|
||||||
|
Linkname: "file.txt",
|
||||||
|
Uname: "vbatts",
|
||||||
|
Gname: "users",
|
||||||
|
},
|
||||||
|
// no contents
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
|
||||||
|
func bytestr(offset int, b []byte) string {
|
||||||
|
const rowLen = 32
|
||||||
|
s := fmt.Sprintf("%04x ", offset)
|
||||||
|
for _, ch := range b {
|
||||||
|
switch {
|
||||||
|
case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
|
||||||
|
s += fmt.Sprintf(" %c", ch)
|
||||||
|
default:
|
||||||
|
s += fmt.Sprintf(" %02x", ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a pseudo-diff between two blocks of bytes.
|
||||||
|
func bytediff(a []byte, b []byte) string {
|
||||||
|
const rowLen = 32
|
||||||
|
s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
|
||||||
|
for offset := 0; len(a)+len(b) > 0; offset += rowLen {
|
||||||
|
na, nb := rowLen, rowLen
|
||||||
|
if na > len(a) {
|
||||||
|
na = len(a)
|
||||||
|
}
|
||||||
|
if nb > len(b) {
|
||||||
|
nb = len(b)
|
||||||
|
}
|
||||||
|
sa := bytestr(offset, a[0:na])
|
||||||
|
sb := bytestr(offset, b[0:nb])
|
||||||
|
if sa != sb {
|
||||||
|
s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
|
||||||
|
}
|
||||||
|
a = a[na:]
|
||||||
|
b = b[nb:]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriter(t *testing.T) {
|
||||||
|
testLoop:
|
||||||
|
for i, test := range writerTests {
|
||||||
|
expected, err := ioutil.ReadFile(test.file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d: Unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
|
||||||
|
big := false
|
||||||
|
for j, entry := range test.entries {
|
||||||
|
big = big || entry.header.Size > 1<<10
|
||||||
|
if err := tw.WriteHeader(entry.header); err != nil {
|
||||||
|
t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
|
||||||
|
continue testLoop
|
||||||
|
}
|
||||||
|
if _, err := io.WriteString(tw, entry.contents); err != nil {
|
||||||
|
t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
|
||||||
|
continue testLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only interested in Close failures for the small tests.
|
||||||
|
if err := tw.Close(); err != nil && !big {
|
||||||
|
t.Errorf("test %d: Failed closing archive: %v", i, err)
|
||||||
|
continue testLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := buf.Bytes()
|
||||||
|
if !bytes.Equal(expected, actual) {
|
||||||
|
t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
|
||||||
|
i, bytediff(expected, actual))
|
||||||
|
}
|
||||||
|
if testing.Short() { // The second test is expensive.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPax(t *testing.T) {
|
||||||
|
// Create an archive with a large name
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat: %v", err)
|
||||||
|
}
|
||||||
|
// Force a PAX long name to be written
|
||||||
|
longName := strings.Repeat("ab", 100)
|
||||||
|
contents := strings.Repeat(" ", int(hdr.Size))
|
||||||
|
hdr.Name = longName
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Simple test to make sure PAX extensions are in effect
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
|
||||||
|
t.Fatal("Expected at least one PAX header to be written.")
|
||||||
|
}
|
||||||
|
// Test that we can get a long name back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Name != longName {
|
||||||
|
t.Fatal("Couldn't recover long file name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaxSymlink(t *testing.T) {
|
||||||
|
// Create an archive with a large linkname
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
hdr.Typeflag = TypeSymlink
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat:1 %v", err)
|
||||||
|
}
|
||||||
|
// Force a PAX long linkname to be written
|
||||||
|
longLinkname := strings.Repeat("1234567890/1234567890", 10)
|
||||||
|
hdr.Linkname = longLinkname
|
||||||
|
|
||||||
|
hdr.Size = 0
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Simple test to make sure PAX extensions are in effect
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
|
||||||
|
t.Fatal("Expected at least one PAX header to be written.")
|
||||||
|
}
|
||||||
|
// Test that we can get a long name back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Linkname != longLinkname {
|
||||||
|
t.Fatal("Couldn't recover long link name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaxNonAscii(t *testing.T) {
|
||||||
|
// Create an archive with non ascii. These should trigger a pax header
|
||||||
|
// because pax headers have a defined utf-8 encoding.
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat:1 %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// some sample data
|
||||||
|
chineseFilename := "文件名"
|
||||||
|
chineseGroupname := "組"
|
||||||
|
chineseUsername := "用戶名"
|
||||||
|
|
||||||
|
hdr.Name = chineseFilename
|
||||||
|
hdr.Gname = chineseGroupname
|
||||||
|
hdr.Uname = chineseUsername
|
||||||
|
|
||||||
|
contents := strings.Repeat(" ", int(hdr.Size))
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Simple test to make sure PAX extensions are in effect
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
|
||||||
|
t.Fatal("Expected at least one PAX header to be written.")
|
||||||
|
}
|
||||||
|
// Test that we can get a long name back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Name != chineseFilename {
|
||||||
|
t.Fatal("Couldn't recover unicode name")
|
||||||
|
}
|
||||||
|
if hdr.Gname != chineseGroupname {
|
||||||
|
t.Fatal("Couldn't recover unicode group")
|
||||||
|
}
|
||||||
|
if hdr.Uname != chineseUsername {
|
||||||
|
t.Fatal("Couldn't recover unicode user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaxXattrs(t *testing.T) {
|
||||||
|
xattrs := map[string]string{
|
||||||
|
"user.key": "value",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an archive with an xattr
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat: %v", err)
|
||||||
|
}
|
||||||
|
contents := "Kilts"
|
||||||
|
hdr.Xattrs = xattrs
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Test that we can get the xattrs back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
|
||||||
|
t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
|
||||||
|
hdr.Xattrs, xattrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaxHeadersSorted(t *testing.T) {
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat: %v", err)
|
||||||
|
}
|
||||||
|
contents := strings.Repeat(" ", int(hdr.Size))
|
||||||
|
|
||||||
|
hdr.Xattrs = map[string]string{
|
||||||
|
"foo": "foo",
|
||||||
|
"bar": "bar",
|
||||||
|
"baz": "baz",
|
||||||
|
"qux": "qux",
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Simple test to make sure PAX extensions are in effect
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
|
||||||
|
t.Fatal("Expected at least one PAX header to be written.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// xattr bar should always appear before others
|
||||||
|
indices := []int{
|
||||||
|
bytes.Index(buf.Bytes(), []byte("bar=bar")),
|
||||||
|
bytes.Index(buf.Bytes(), []byte("baz=baz")),
|
||||||
|
bytes.Index(buf.Bytes(), []byte("foo=foo")),
|
||||||
|
bytes.Index(buf.Bytes(), []byte("qux=qux")),
|
||||||
|
}
|
||||||
|
if !sort.IntsAreSorted(indices) {
|
||||||
|
t.Fatal("PAX headers are not sorted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUSTARLongName(t *testing.T) {
|
||||||
|
// Create an archive with a path that failed to split with USTAR extension in previous versions.
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
hdr.Typeflag = TypeDir
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat:1 %v", err)
|
||||||
|
}
|
||||||
|
// Force a PAX long name to be written. The name was taken from a practical example
|
||||||
|
// that fails and replaced ever char through numbers to anonymize the sample.
|
||||||
|
longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
|
||||||
|
hdr.Name = longName
|
||||||
|
|
||||||
|
hdr.Size = 0
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Test that we can get a long name back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Name != longName {
|
||||||
|
t.Fatal("Couldn't recover long name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidTypeflagWithPAXHeader(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
tw := NewWriter(&buffer)
|
||||||
|
|
||||||
|
fileName := strings.Repeat("ab", 100)
|
||||||
|
|
||||||
|
hdr := &Header{
|
||||||
|
Name: fileName,
|
||||||
|
Size: 4,
|
||||||
|
Typeflag: 0,
|
||||||
|
}
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatalf("Failed to write header: %s", err)
|
||||||
|
}
|
||||||
|
if _, err := tw.Write([]byte("fooo")); err != nil {
|
||||||
|
t.Fatalf("Failed to write the file's data: %s", err)
|
||||||
|
}
|
||||||
|
tw.Close()
|
||||||
|
|
||||||
|
tr := NewReader(&buffer)
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read header: %s", err)
|
||||||
|
}
|
||||||
|
if header.Typeflag != 0 {
|
||||||
|
t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteAfterClose(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
tw := NewWriter(&buffer)
|
||||||
|
|
||||||
|
hdr := &Header{
|
||||||
|
Name: "small.txt",
|
||||||
|
Size: 5,
|
||||||
|
}
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatalf("Failed to write header: %s", err)
|
||||||
|
}
|
||||||
|
tw.Close()
|
||||||
|
if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
|
||||||
|
t.Fatalf("Write: got %v; want ErrWriteAfterClose", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitUSTARPath(t *testing.T) {
|
||||||
|
var sr = strings.Repeat
|
||||||
|
|
||||||
|
var vectors = []struct {
|
||||||
|
input string // Input path
|
||||||
|
prefix string // Expected output prefix
|
||||||
|
suffix string // Expected output suffix
|
||||||
|
ok bool // Split success?
|
||||||
|
}{
|
||||||
|
{"", "", "", false},
|
||||||
|
{"abc", "", "", false},
|
||||||
|
{"用戶名", "", "", false},
|
||||||
|
{sr("a", fileNameSize), "", "", false},
|
||||||
|
{sr("a", fileNameSize) + "/", "", "", false},
|
||||||
|
{sr("a", fileNameSize) + "/a", sr("a", fileNameSize), "a", true},
|
||||||
|
{sr("a", fileNamePrefixSize) + "/", "", "", false},
|
||||||
|
{sr("a", fileNamePrefixSize) + "/a", sr("a", fileNamePrefixSize), "a", true},
|
||||||
|
{sr("a", fileNameSize+1), "", "", false},
|
||||||
|
{sr("/", fileNameSize+1), sr("/", fileNameSize-1), "/", true},
|
||||||
|
{sr("a", fileNamePrefixSize) + "/" + sr("b", fileNameSize),
|
||||||
|
sr("a", fileNamePrefixSize), sr("b", fileNameSize), true},
|
||||||
|
{sr("a", fileNamePrefixSize) + "//" + sr("b", fileNameSize), "", "", false},
|
||||||
|
{sr("a/", fileNameSize), sr("a/", 77) + "a", sr("a/", 22), true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vectors {
|
||||||
|
prefix, suffix, ok := splitUSTARPath(v.input)
|
||||||
|
if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
|
||||||
|
t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)",
|
||||||
|
v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatPAXRecord(t *testing.T) {
|
||||||
|
var medName = strings.Repeat("CD", 50)
|
||||||
|
var longName = strings.Repeat("AB", 100)
|
||||||
|
|
||||||
|
var vectors = []struct {
|
||||||
|
inputKey string
|
||||||
|
inputVal string
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
{"k", "v", "6 k=v\n"},
|
||||||
|
{"path", "/etc/hosts", "19 path=/etc/hosts\n"},
|
||||||
|
{"path", longName, "210 path=" + longName + "\n"},
|
||||||
|
{"path", medName, "110 path=" + medName + "\n"},
|
||||||
|
{"foo", "ba", "9 foo=ba\n"},
|
||||||
|
{"foo", "bar", "11 foo=bar\n"},
|
||||||
|
{"foo", "b=\nar=\n==\x00", "18 foo=b=\nar=\n==\x00\n"},
|
||||||
|
{"foo", "hello9 foo=ba\nworld", "27 foo=hello9 foo=ba\nworld\n"},
|
||||||
|
{"☺☻☹", "日a本b語ç", "27 ☺☻☹=日a本b語ç\n"},
|
||||||
|
{"\x00hello", "\x00world", "17 \x00hello=\x00world\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vectors {
|
||||||
|
output := formatPAXRecord(v.inputKey, v.inputVal)
|
||||||
|
if output != v.output {
|
||||||
|
t.Errorf("formatPAXRecord(%q, %q): got %q, want %q",
|
||||||
|
v.inputKey, v.inputVal, output, v.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFitsInBase256(t *testing.T) {
|
||||||
|
var vectors = []struct {
|
||||||
|
input int64
|
||||||
|
width int
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{+1, 8, true},
|
||||||
|
{0, 8, true},
|
||||||
|
{-1, 8, true},
|
||||||
|
{1 << 56, 8, false},
|
||||||
|
{(1 << 56) - 1, 8, true},
|
||||||
|
{-1 << 56, 8, true},
|
||||||
|
{(-1 << 56) - 1, 8, false},
|
||||||
|
{121654, 8, true},
|
||||||
|
{-9849849, 8, true},
|
||||||
|
{math.MaxInt64, 9, true},
|
||||||
|
{0, 9, true},
|
||||||
|
{math.MinInt64, 9, true},
|
||||||
|
{math.MaxInt64, 12, true},
|
||||||
|
{0, 12, true},
|
||||||
|
{math.MinInt64, 12, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vectors {
|
||||||
|
ok := fitsInBase256(v.width, v.input)
|
||||||
|
if ok != v.ok {
|
||||||
|
t.Errorf("checkNumeric(%d, %d): got %v, want %v", v.input, v.width, ok, v.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatNumeric(t *testing.T) {
|
||||||
|
var vectors = []struct {
|
||||||
|
input int64
|
||||||
|
output string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
// Test base-256 (binary) encoded values.
|
||||||
|
{-1, "\xff", true},
|
||||||
|
{-1, "\xff\xff", true},
|
||||||
|
{-1, "\xff\xff\xff", true},
|
||||||
|
{(1 << 0), "0", false},
|
||||||
|
{(1 << 8) - 1, "\x80\xff", true},
|
||||||
|
{(1 << 8), "0\x00", false},
|
||||||
|
{(1 << 16) - 1, "\x80\xff\xff", true},
|
||||||
|
{(1 << 16), "00\x00", false},
|
||||||
|
{-1 * (1 << 0), "\xff", true},
|
||||||
|
{-1*(1<<0) - 1, "0", false},
|
||||||
|
{-1 * (1 << 8), "\xff\x00", true},
|
||||||
|
{-1*(1<<8) - 1, "0\x00", false},
|
||||||
|
{-1 * (1 << 16), "\xff\x00\x00", true},
|
||||||
|
{-1*(1<<16) - 1, "00\x00", false},
|
||||||
|
{537795476381659745, "0000000\x00", false},
|
||||||
|
{537795476381659745, "\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", true},
|
||||||
|
{-615126028225187231, "0000000\x00", false},
|
||||||
|
{-615126028225187231, "\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", true},
|
||||||
|
{math.MaxInt64, "0000000\x00", false},
|
||||||
|
{math.MaxInt64, "\x80\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff", true},
|
||||||
|
{math.MinInt64, "0000000\x00", false},
|
||||||
|
{math.MinInt64, "\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
|
||||||
|
{math.MaxInt64, "\x80\x7f\xff\xff\xff\xff\xff\xff\xff", true},
|
||||||
|
{math.MinInt64, "\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vectors {
|
||||||
|
var f formatter
|
||||||
|
output := make([]byte, len(v.output))
|
||||||
|
f.formatNumeric(output, v.input)
|
||||||
|
ok := (f.err == nil)
|
||||||
|
if ok != v.ok {
|
||||||
|
if v.ok {
|
||||||
|
t.Errorf("formatNumeric(%d): got formatting failure, want success", v.input)
|
||||||
|
} else {
|
||||||
|
t.Errorf("formatNumeric(%d): got formatting success, want failure", v.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if string(output) != v.output {
|
||||||
|
t.Errorf("formatNumeric(%d): got %q, want %q", v.input, output, v.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatPAXTime(t *testing.T) {
|
||||||
|
t1 := time.Date(2000, 1, 1, 11, 0, 0, 0, time.UTC)
|
||||||
|
t2 := time.Date(2000, 1, 1, 11, 0, 0, 100, time.UTC)
|
||||||
|
t3 := time.Date(1960, 1, 1, 11, 0, 0, 0, time.UTC)
|
||||||
|
t4 := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
verify := func(time time.Time, s string) {
|
||||||
|
p := formatPAXTime(time)
|
||||||
|
if p != s {
|
||||||
|
t.Errorf("for %v, expected %s, got %s", time, s, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verify(t1, "946724400")
|
||||||
|
verify(t2, "946724400.000000100")
|
||||||
|
verify(t3, "-315579600")
|
||||||
|
verify(t4, "0")
|
||||||
|
}
|
268
gateway/vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
268
gateway/vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||||
|
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
||||||
|
|
||||||
|
const (
|
||||||
|
BackupData = uint32(iota + 1)
|
||||||
|
BackupEaData
|
||||||
|
BackupSecurity
|
||||||
|
BackupAlternateData
|
||||||
|
BackupLink
|
||||||
|
BackupPropertyData
|
||||||
|
BackupObjectId
|
||||||
|
BackupReparseData
|
||||||
|
BackupSparseBlock
|
||||||
|
BackupTxfsData
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StreamSparseAttributes = uint32(8)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WRITE_DAC = 0x40000
|
||||||
|
WRITE_OWNER = 0x80000
|
||||||
|
ACCESS_SYSTEM_SECURITY = 0x1000000
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackupHeader represents a backup stream of a file.
|
||||||
|
type BackupHeader struct {
|
||||||
|
Id uint32 // The backup stream ID
|
||||||
|
Attributes uint32 // Stream attributes
|
||||||
|
Size int64 // The size of the stream in bytes
|
||||||
|
Name string // The name of the stream (for BackupAlternateData only).
|
||||||
|
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32StreamId struct {
|
||||||
|
StreamId uint32
|
||||||
|
Attributes uint32
|
||||||
|
Size uint64
|
||||||
|
NameSize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
|
||||||
|
// of BackupHeader values.
|
||||||
|
type BackupStreamReader struct {
|
||||||
|
r io.Reader
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
|
||||||
|
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
||||||
|
return &BackupStreamReader{r, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next backup stream and prepares for calls to Write(). It skips the remainder of the current stream if
|
||||||
|
// it was not completely read.
|
||||||
|
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
||||||
|
if r.bytesLeft > 0 {
|
||||||
|
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wsi win32StreamId
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr := &BackupHeader{
|
||||||
|
Id: wsi.StreamId,
|
||||||
|
Attributes: wsi.Attributes,
|
||||||
|
Size: int64(wsi.Size),
|
||||||
|
}
|
||||||
|
if wsi.NameSize != 0 {
|
||||||
|
name := make([]uint16, int(wsi.NameSize/2))
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Name = syscall.UTF16ToString(name)
|
||||||
|
}
|
||||||
|
if wsi.StreamId == BackupSparseBlock {
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Size -= 8
|
||||||
|
}
|
||||||
|
r.bytesLeft = hdr.Size
|
||||||
|
return hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from the current backup stream.
|
||||||
|
func (r *BackupStreamReader) Read(b []byte) (int, error) {
|
||||||
|
if r.bytesLeft == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if int64(len(b)) > r.bytesLeft {
|
||||||
|
b = b[:r.bytesLeft]
|
||||||
|
}
|
||||||
|
n, err := r.r.Read(b)
|
||||||
|
r.bytesLeft -= int64(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
} else if r.bytesLeft == 0 && err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
|
||||||
|
type BackupStreamWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
|
||||||
|
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
|
||||||
|
return &BackupStreamWriter{w, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes the next backup stream header and prepares for calls to Write().
|
||||||
|
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
|
||||||
|
if w.bytesLeft != 0 {
|
||||||
|
return fmt.Errorf("missing %d bytes", w.bytesLeft)
|
||||||
|
}
|
||||||
|
name := utf16.Encode([]rune(hdr.Name))
|
||||||
|
wsi := win32StreamId{
|
||||||
|
StreamId: hdr.Id,
|
||||||
|
Attributes: hdr.Attributes,
|
||||||
|
Size: uint64(hdr.Size),
|
||||||
|
NameSize: uint32(len(name) * 2),
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
// Include space for the int64 block offset
|
||||||
|
wsi.Size += 8
|
||||||
|
}
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(name) != 0 {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.bytesLeft = hdr.Size
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to the current backup stream.
|
||||||
|
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
|
||||||
|
if w.bytesLeft < int64(len(b)) {
|
||||||
|
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
|
||||||
|
}
|
||||||
|
n, err := w.w.Write(b)
|
||||||
|
w.bytesLeft -= int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
|
||||||
|
type BackupFileReader struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
|
||||||
|
// Read will attempt to read the security descriptor of the file.
|
||||||
|
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
|
||||||
|
r := &BackupFileReader{f, includeSecurity, 0}
|
||||||
|
runtime.SetFinalizer(r, func(r *BackupFileReader) { r.Close() })
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
|
||||||
|
func (r *BackupFileReader) Read(b []byte) (int, error) {
|
||||||
|
var bytesRead uint32
|
||||||
|
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{"BackupRead", r.f.Name(), err}
|
||||||
|
}
|
||||||
|
if bytesRead == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return int(bytesRead), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileReader. It does not close
|
||||||
|
// the underlying file.
|
||||||
|
func (r *BackupFileReader) Close() error {
|
||||||
|
if r.ctx != 0 {
|
||||||
|
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
||||||
|
r.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
|
||||||
|
type BackupFileWriter struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileWrtier returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
||||||
|
// Write() will attempt to restore the security descriptor from the stream.
|
||||||
|
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
||||||
|
w := &BackupFileWriter{f, includeSecurity, 0}
|
||||||
|
runtime.SetFinalizer(w, func(w *BackupFileWriter) { w.Close() })
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write restores a portion of the file using the provided backup stream.
|
||||||
|
func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
||||||
|
var bytesWritten uint32
|
||||||
|
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{"BackupWrite", w.f.Name(), err}
|
||||||
|
}
|
||||||
|
if int(bytesWritten) != len(b) {
|
||||||
|
return int(bytesWritten), errors.New("not all bytes could be written")
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileWriter. It does not
|
||||||
|
// close the underlying file.
|
||||||
|
func (w *BackupFileWriter) Close() error {
|
||||||
|
if w.ctx != 0 {
|
||||||
|
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
||||||
|
w.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
|
||||||
|
// or restore privileges have been acquired.
|
||||||
|
//
|
||||||
|
// If the file opened was a directory, it cannot be used with Readdir().
|
||||||
|
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
|
||||||
|
winPath, err := syscall.UTF16FromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
|
||||||
|
if err != nil {
|
||||||
|
err = &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return os.NewFile(uintptr(h), path), nil
|
||||||
|
}
|
255
gateway/vendor/github.com/Microsoft/go-winio/backup_test.go
generated
vendored
Normal file
255
gateway/vendor/github.com/Microsoft/go-winio/backup_test.go
generated
vendored
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testFileName string
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
f, err := ioutil.TempFile("", "tmp")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
testFileName = f.Name()
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove(testFileName)
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestFile(makeADS bool) error {
|
||||||
|
os.Remove(testFileName)
|
||||||
|
f, err := os.Create(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = f.Write([]byte("testing 1 2 3\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if makeADS {
|
||||||
|
a, err := os.Create(testFileName + ":ads.txt")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer a.Close()
|
||||||
|
_, err = a.Write([]byte("alternate data stream\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupRead(t *testing.T) {
|
||||||
|
err := makeTestFile(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewBackupFileReader(f, false)
|
||||||
|
defer r.Close()
|
||||||
|
b, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
t.Fatal("no data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupStreamRead(t *testing.T) {
|
||||||
|
err := makeTestFile(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewBackupFileReader(f, false)
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
br := NewBackupStreamReader(r)
|
||||||
|
gotData := false
|
||||||
|
gotAltData := false
|
||||||
|
for {
|
||||||
|
hdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch hdr.Id {
|
||||||
|
case BackupData:
|
||||||
|
if gotData {
|
||||||
|
t.Fatal("duplicate data")
|
||||||
|
}
|
||||||
|
if hdr.Name != "" {
|
||||||
|
t.Fatalf("unexpected name %s", hdr.Name)
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(br)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != "testing 1 2 3\n" {
|
||||||
|
t.Fatalf("incorrect data %v", b)
|
||||||
|
}
|
||||||
|
gotData = true
|
||||||
|
case BackupAlternateData:
|
||||||
|
if gotAltData {
|
||||||
|
t.Fatal("duplicate alt data")
|
||||||
|
}
|
||||||
|
if hdr.Name != ":ads.txt:$DATA" {
|
||||||
|
t.Fatalf("incorrect name %s", hdr.Name)
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(br)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != "alternate data stream\n" {
|
||||||
|
t.Fatalf("incorrect data %v", b)
|
||||||
|
}
|
||||||
|
gotAltData = true
|
||||||
|
default:
|
||||||
|
t.Fatalf("unknown stream ID %d", hdr.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !gotData || !gotAltData {
|
||||||
|
t.Fatal("missing stream")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupStreamWrite(t *testing.T) {
|
||||||
|
f, err := os.Create(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
w := NewBackupFileWriter(f, false)
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
data := "testing 1 2 3\n"
|
||||||
|
altData := "alternate stream\n"
|
||||||
|
|
||||||
|
br := NewBackupStreamWriter(w)
|
||||||
|
err = br.WriteHeader(&BackupHeader{Id: BackupData, Size: int64(len(data))})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n, err := br.Write([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
t.Fatal("short write")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = br.WriteHeader(&BackupHeader{Id: BackupAlternateData, Size: int64(len(altData)), Name: ":ads.txt:$DATA"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n, err = br.Write([]byte(altData))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != len(altData) {
|
||||||
|
t.Fatal("short write")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadFile(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != data {
|
||||||
|
t.Fatalf("wrong data %v", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = ioutil.ReadFile(testFileName + ":ads.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != altData {
|
||||||
|
t.Fatalf("wrong data %v", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSparseFile() error {
|
||||||
|
os.Remove(testFileName)
|
||||||
|
f, err := os.Create(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
const (
|
||||||
|
FSCTL_SET_SPARSE = 0x000900c4
|
||||||
|
FSCTL_SET_ZERO_DATA = 0x000980c8
|
||||||
|
)
|
||||||
|
|
||||||
|
err = syscall.DeviceIoControl(syscall.Handle(f.Fd()), FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write([]byte("testing 1 2 3\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Seek(1000000, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write([]byte("more data later\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupSparseFile(t *testing.T) {
|
||||||
|
err := makeSparseFile()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewBackupFileReader(f, false)
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
br := NewBackupStreamReader(r)
|
||||||
|
for {
|
||||||
|
hdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(hdr)
|
||||||
|
}
|
||||||
|
}
|
4
gateway/vendor/github.com/Microsoft/go-winio/backuptar/noop.go
generated
vendored
Normal file
4
gateway/vendor/github.com/Microsoft/go-winio/backuptar/noop.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// +build !windows
|
||||||
|
// This file only exists to allow go get on non-Windows platforms.
|
||||||
|
|
||||||
|
package backuptar
|
353
gateway/vendor/github.com/Microsoft/go-winio/backuptar/tar.go
generated
vendored
Normal file
353
gateway/vendor/github.com/Microsoft/go-winio/backuptar/tar.go
generated
vendored
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package backuptar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio"
|
||||||
|
"github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
c_ISUID = 04000 // Set uid
|
||||||
|
c_ISGID = 02000 // Set gid
|
||||||
|
c_ISVTX = 01000 // Save text (sticky bit)
|
||||||
|
c_ISDIR = 040000 // Directory
|
||||||
|
c_ISFIFO = 010000 // FIFO
|
||||||
|
c_ISREG = 0100000 // Regular file
|
||||||
|
c_ISLNK = 0120000 // Symbolic link
|
||||||
|
c_ISBLK = 060000 // Block special file
|
||||||
|
c_ISCHR = 020000 // Character special file
|
||||||
|
c_ISSOCK = 0140000 // Socket
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hdrFileAttributes = "fileattr"
|
||||||
|
hdrSecurityDescriptor = "sd"
|
||||||
|
hdrRawSecurityDescriptor = "rawsd"
|
||||||
|
hdrMountPoint = "mountpoint"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeZeroes(w io.Writer, count int64) error {
|
||||||
|
buf := make([]byte, 8192)
|
||||||
|
c := len(buf)
|
||||||
|
for i := int64(0); i < count; i += int64(c) {
|
||||||
|
if int64(c) > count-i {
|
||||||
|
c = int(count - i)
|
||||||
|
}
|
||||||
|
_, err := w.Write(buf[:c])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
|
||||||
|
curOffset := int64(0)
|
||||||
|
for {
|
||||||
|
bhdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bhdr.Id != winio.BackupSparseBlock {
|
||||||
|
return fmt.Errorf("unexpected stream %d", bhdr.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// archive/tar does not support writing sparse files
|
||||||
|
// so just write zeroes to catch up to the current offset.
|
||||||
|
err = writeZeroes(t, bhdr.Offset-curOffset)
|
||||||
|
if bhdr.Size == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n, err := io.Copy(t, br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
curOffset = bhdr.Offset + n
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicInfoHeader creates a tar header from basic file information.
|
||||||
|
func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
|
||||||
|
hdr := &tar.Header{
|
||||||
|
Name: filepath.ToSlash(name),
|
||||||
|
Size: size,
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
|
||||||
|
ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
|
||||||
|
AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
|
||||||
|
CreationTime: time.Unix(0, fileInfo.CreationTime.Nanoseconds()),
|
||||||
|
Winheaders: make(map[string]string),
|
||||||
|
}
|
||||||
|
hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
|
||||||
|
|
||||||
|
if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
|
||||||
|
hdr.Mode |= c_ISDIR
|
||||||
|
hdr.Size = 0
|
||||||
|
hdr.Typeflag = tar.TypeDir
|
||||||
|
}
|
||||||
|
return hdr
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
|
||||||
|
//
|
||||||
|
// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
|
||||||
|
//
|
||||||
|
// The additional Win32 metadata is:
|
||||||
|
//
|
||||||
|
// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
|
||||||
|
//
|
||||||
|
// MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
|
||||||
|
//
|
||||||
|
// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
|
||||||
|
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
|
||||||
|
name = filepath.ToSlash(name)
|
||||||
|
hdr := BasicInfoHeader(name, size, fileInfo)
|
||||||
|
br := winio.NewBackupStreamReader(r)
|
||||||
|
var dataHdr *winio.BackupHeader
|
||||||
|
for dataHdr == nil {
|
||||||
|
bhdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch bhdr.Id {
|
||||||
|
case winio.BackupData:
|
||||||
|
hdr.Mode |= c_ISREG
|
||||||
|
dataHdr = bhdr
|
||||||
|
case winio.BackupSecurity:
|
||||||
|
sd, err := ioutil.ReadAll(br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
|
||||||
|
|
||||||
|
case winio.BackupReparseData:
|
||||||
|
hdr.Mode |= c_ISLNK
|
||||||
|
hdr.Typeflag = tar.TypeSymlink
|
||||||
|
reparseBuffer, err := ioutil.ReadAll(br)
|
||||||
|
rp, err := winio.DecodeReparsePoint(reparseBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rp.IsMountPoint {
|
||||||
|
hdr.Winheaders[hdrMountPoint] = "1"
|
||||||
|
}
|
||||||
|
hdr.Linkname = rp.Target
|
||||||
|
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
||||||
|
// ignore these streams
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := t.WriteHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataHdr != nil {
|
||||||
|
// A data stream was found. Copy the data.
|
||||||
|
if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
||||||
|
if size != dataHdr.Size {
|
||||||
|
return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(t, br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = copySparse(t, br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for streams after the data stream. The only ones we handle are alternate data streams.
|
||||||
|
// Other streams may have metadata that could be serialized, but the tar header has already
|
||||||
|
// been written. In practice, this means that we don't get EA or TXF metadata.
|
||||||
|
for {
|
||||||
|
bhdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch bhdr.Id {
|
||||||
|
case winio.BackupAlternateData:
|
||||||
|
altName := bhdr.Name
|
||||||
|
if strings.HasSuffix(altName, ":$DATA") {
|
||||||
|
altName = altName[:len(altName)-len(":$DATA")]
|
||||||
|
}
|
||||||
|
if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
||||||
|
hdr = &tar.Header{
|
||||||
|
Name: name + altName,
|
||||||
|
Mode: hdr.Mode,
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
Size: bhdr.Size,
|
||||||
|
ModTime: hdr.ModTime,
|
||||||
|
AccessTime: hdr.AccessTime,
|
||||||
|
ChangeTime: hdr.ChangeTime,
|
||||||
|
}
|
||||||
|
err = t.WriteHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(t, br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Unsupported for now, since the size of the alternate stream is not present
|
||||||
|
// in the backup stream until after the data has been read.
|
||||||
|
return errors.New("tar of sparse alternate data streams is unsupported")
|
||||||
|
}
|
||||||
|
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
||||||
|
// ignore these streams
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
|
||||||
|
// WriteTarFileFromBackupStream.
|
||||||
|
func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
|
||||||
|
name = hdr.Name
|
||||||
|
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||||
|
size = hdr.Size
|
||||||
|
}
|
||||||
|
fileInfo = &winio.FileBasicInfo{
|
||||||
|
LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
|
||||||
|
LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||||
|
ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
|
||||||
|
CreationTime: syscall.NsecToFiletime(hdr.CreationTime.UnixNano()),
|
||||||
|
}
|
||||||
|
if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok {
|
||||||
|
attr, err := strconv.ParseUint(attrStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, nil, err
|
||||||
|
}
|
||||||
|
fileInfo.FileAttributes = uintptr(attr)
|
||||||
|
} else {
|
||||||
|
if hdr.Typeflag == tar.TypeDir {
|
||||||
|
fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
|
||||||
|
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
|
||||||
|
// tar file that was not processed, or io.EOF is there are no more.
|
||||||
|
func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
|
||||||
|
bw := winio.NewBackupStreamWriter(w)
|
||||||
|
var sd []byte
|
||||||
|
var err error
|
||||||
|
// Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
|
||||||
|
// by this library will have raw binary for the security descriptor.
|
||||||
|
if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok {
|
||||||
|
sd, err = winio.SddlToSecurityDescriptor(sddl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sdraw, ok := hdr.Winheaders[hdrRawSecurityDescriptor]; ok {
|
||||||
|
sd, err = base64.StdEncoding.DecodeString(sdraw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(sd) != 0 {
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupSecurity,
|
||||||
|
Size: int64(len(sd)),
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = bw.Write(sd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeSymlink {
|
||||||
|
_, isMountPoint := hdr.Winheaders[hdrMountPoint]
|
||||||
|
rp := winio.ReparsePoint{
|
||||||
|
Target: filepath.FromSlash(hdr.Linkname),
|
||||||
|
IsMountPoint: isMountPoint,
|
||||||
|
}
|
||||||
|
reparse := winio.EncodeReparsePoint(&rp)
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupReparseData,
|
||||||
|
Size: int64(len(reparse)),
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = bw.Write(reparse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupData,
|
||||||
|
Size: hdr.Size,
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(bw, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Copy all the alternate data streams and return the next non-ADS header.
|
||||||
|
for {
|
||||||
|
ahdr, err := t.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
|
||||||
|
return ahdr, nil
|
||||||
|
}
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupAlternateData,
|
||||||
|
Size: ahdr.Size,
|
||||||
|
Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
|
||||||
|
}
|
||||||
|
err = bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(bw, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
gateway/vendor/github.com/Microsoft/go-winio/backuptar/tar_test.go
generated
vendored
Normal file
84
gateway/vendor/github.com/Microsoft/go-winio/backuptar/tar_test.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package backuptar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio"
|
||||||
|
"github.com/Microsoft/go-winio/archive/tar"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ensurePresent(t *testing.T, m map[string]string, keys ...string) {
|
||||||
|
for _, k := range keys {
|
||||||
|
if _, ok := m[k]; !ok {
|
||||||
|
t.Error(k, "not present in tar header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTrip(t *testing.T) {
|
||||||
|
f, err := ioutil.TempFile("", "tst")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
if _, err = f.Write([]byte("testing 1 2 3\n")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = f.Seek(0, 0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bi, err := winio.GetFileBasicInfo(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
br := winio.NewBackupFileReader(f, true)
|
||||||
|
defer br.Close()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
tw := tar.NewWriter(&buf)
|
||||||
|
|
||||||
|
err = WriteTarFileFromBackupStream(tw, br, f.Name(), fi.Size(), bi)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := tar.NewReader(&buf)
|
||||||
|
hdr, err := tr.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, size, bi2, err := FileInfoFromHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != filepath.ToSlash(f.Name()) {
|
||||||
|
t.Errorf("got name %s, expected %s", name, filepath.ToSlash(f.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if size != fi.Size() {
|
||||||
|
t.Errorf("got size %d, expected %d", size, fi.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(*bi, *bi2) {
|
||||||
|
t.Errorf("got %#v, expected %#v", *bi, *bi2)
|
||||||
|
}
|
||||||
|
|
||||||
|
ensurePresent(t, hdr.Winheaders, "fileattr", "sd")
|
||||||
|
}
|
225
gateway/vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
225
gateway/vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
|
||||||
|
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||||
|
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||||
|
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||||
|
//sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod
|
||||||
|
|
||||||
|
const (
|
||||||
|
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
|
||||||
|
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileClosed = errors.New("file has already been closed")
|
||||||
|
ErrTimeout = &timeoutError{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeoutError struct{}
|
||||||
|
|
||||||
|
func (e *timeoutError) Error() string { return "i/o timeout" }
|
||||||
|
func (e *timeoutError) Timeout() bool { return true }
|
||||||
|
func (e *timeoutError) Temporary() bool { return true }
|
||||||
|
|
||||||
|
var ioInitOnce sync.Once
|
||||||
|
var ioCompletionPort syscall.Handle
|
||||||
|
|
||||||
|
// ioResult contains the result of an asynchronous IO operation
|
||||||
|
type ioResult struct {
|
||||||
|
bytes uint32
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioOperation represents an outstanding asynchronous Win32 IO
|
||||||
|
type ioOperation struct {
|
||||||
|
o syscall.Overlapped
|
||||||
|
ch chan ioResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func initIo() {
|
||||||
|
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ioCompletionPort = h
|
||||||
|
go ioCompletionProcessor(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
||||||
|
// It takes ownership of this handle and will close it if it is garbage collected.
|
||||||
|
type win32File struct {
|
||||||
|
handle syscall.Handle
|
||||||
|
wg sync.WaitGroup
|
||||||
|
closing bool
|
||||||
|
readDeadline time.Time
|
||||||
|
writeDeadline time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeWin32File makes a new win32File from an existing file handle
|
||||||
|
func makeWin32File(h syscall.Handle) (*win32File, error) {
|
||||||
|
f := &win32File{handle: h}
|
||||||
|
ioInitOnce.Do(initIo)
|
||||||
|
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
runtime.SetFinalizer(f, (*win32File).closeHandle)
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||||
|
return makeWin32File(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeHandle closes the resources associated with a Win32 handle
|
||||||
|
func (f *win32File) closeHandle() {
|
||||||
|
if !f.closing {
|
||||||
|
// cancel all IO and wait for it to complete
|
||||||
|
f.closing = true
|
||||||
|
cancelIoEx(f.handle, nil)
|
||||||
|
f.wg.Wait()
|
||||||
|
// at this point, no new IO can start
|
||||||
|
syscall.Close(f.handle)
|
||||||
|
f.handle = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a win32File.
|
||||||
|
func (f *win32File) Close() error {
|
||||||
|
f.closeHandle()
|
||||||
|
runtime.SetFinalizer(f, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareIo prepares for a new IO operation
|
||||||
|
func (f *win32File) prepareIo() (*ioOperation, error) {
|
||||||
|
f.wg.Add(1)
|
||||||
|
if f.closing {
|
||||||
|
return nil, ErrFileClosed
|
||||||
|
}
|
||||||
|
c := &ioOperation{}
|
||||||
|
c.ch = make(chan ioResult)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioCompletionProcessor processes completed async IOs forever
|
||||||
|
func ioCompletionProcessor(h syscall.Handle) {
|
||||||
|
// Set the timer resolution to 1. This fixes a performance regression in golang 1.6.
|
||||||
|
timeBeginPeriod(1)
|
||||||
|
for {
|
||||||
|
var bytes uint32
|
||||||
|
var key uintptr
|
||||||
|
var op *ioOperation
|
||||||
|
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
|
||||||
|
if op == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
op.ch <- ioResult{bytes, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
|
||||||
|
// the operation has actually completed.
|
||||||
|
func (f *win32File) asyncIo(c *ioOperation, deadline time.Time, bytes uint32, err error) (int, error) {
|
||||||
|
if err != syscall.ERROR_IO_PENDING {
|
||||||
|
f.wg.Done()
|
||||||
|
return int(bytes), err
|
||||||
|
} else {
|
||||||
|
var r ioResult
|
||||||
|
wait := true
|
||||||
|
timedout := false
|
||||||
|
if f.closing {
|
||||||
|
cancelIoEx(f.handle, &c.o)
|
||||||
|
} else if !deadline.IsZero() {
|
||||||
|
now := time.Now()
|
||||||
|
if !deadline.After(now) {
|
||||||
|
timedout = true
|
||||||
|
} else {
|
||||||
|
timeout := time.After(deadline.Sub(now))
|
||||||
|
select {
|
||||||
|
case r = <-c.ch:
|
||||||
|
wait = false
|
||||||
|
case <-timeout:
|
||||||
|
timedout = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if timedout {
|
||||||
|
cancelIoEx(f.handle, &c.o)
|
||||||
|
}
|
||||||
|
if wait {
|
||||||
|
r = <-c.ch
|
||||||
|
}
|
||||||
|
err = r.err
|
||||||
|
if err == syscall.ERROR_OPERATION_ABORTED {
|
||||||
|
if f.closing {
|
||||||
|
err = ErrFileClosed
|
||||||
|
} else if timedout {
|
||||||
|
err = ErrTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.wg.Done()
|
||||||
|
return int(r.bytes), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from a file handle.
|
||||||
|
func (f *win32File) Read(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIo(c, f.readDeadline, bytes, err)
|
||||||
|
|
||||||
|
// Handle EOF conditions.
|
||||||
|
if err == nil && n == 0 && len(b) != 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else if err == syscall.ERROR_BROKEN_PIPE {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to a file handle.
|
||||||
|
func (f *win32File) Write(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
|
||||||
|
return f.asyncIo(c, f.writeDeadline, bytes, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetReadDeadline(t time.Time) error {
|
||||||
|
f.readDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetWriteDeadline(t time.Time) error {
|
||||||
|
f.writeDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) Flush() error {
|
||||||
|
return syscall.FlushFileBuffers(f.handle)
|
||||||
|
}
|
56
gateway/vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
56
gateway/vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
|
||||||
|
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
|
||||||
|
|
||||||
|
const (
|
||||||
|
fileBasicInfo = 0
|
||||||
|
fileIDInfo = 0x12
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileBasicInfo contains file access time and file attributes information.
|
||||||
|
type FileBasicInfo struct {
|
||||||
|
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
|
||||||
|
FileAttributes uintptr // includes padding
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||||
|
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||||
|
bi := &FileBasicInfo{}
|
||||||
|
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
return bi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFileBasicInfo sets times and attributes for a file.
|
||||||
|
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||||
|
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||||
|
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
|
||||||
|
// unique on a system.
|
||||||
|
type FileIDInfo struct {
|
||||||
|
VolumeSerialNumber uint64
|
||||||
|
FileID [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileID retrieves the unique (volume, file ID) pair for a file.
|
||||||
|
func GetFileID(f *os.File) (*FileIDInfo, error) {
|
||||||
|
fileID := &FileIDInfo{}
|
||||||
|
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
return fileID, nil
|
||||||
|
}
|
404
gateway/vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
404
gateway/vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
|
||||||
|
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
|
||||||
|
//sys createFile(name string, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
|
||||||
|
//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW
|
||||||
|
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||||
|
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||||
|
|
||||||
|
type securityAttributes struct {
|
||||||
|
Length uint32
|
||||||
|
SecurityDescriptor *byte
|
||||||
|
InheritHandle uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cERROR_PIPE_BUSY = syscall.Errno(231)
|
||||||
|
cERROR_PIPE_CONNECTED = syscall.Errno(535)
|
||||||
|
cERROR_SEM_TIMEOUT = syscall.Errno(121)
|
||||||
|
|
||||||
|
cPIPE_ACCESS_DUPLEX = 0x3
|
||||||
|
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000
|
||||||
|
cSECURITY_SQOS_PRESENT = 0x100000
|
||||||
|
cSECURITY_ANONYMOUS = 0
|
||||||
|
|
||||||
|
cPIPE_REJECT_REMOTE_CLIENTS = 0x8
|
||||||
|
|
||||||
|
cPIPE_UNLIMITED_INSTANCES = 255
|
||||||
|
|
||||||
|
cNMPWAIT_USE_DEFAULT_WAIT = 0
|
||||||
|
cNMPWAIT_NOWAIT = 1
|
||||||
|
|
||||||
|
cPIPE_TYPE_MESSAGE = 4
|
||||||
|
|
||||||
|
cPIPE_READMODE_MESSAGE = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||||
|
// This error should match net.errClosing since docker takes a dependency on its text.
|
||||||
|
ErrPipeListenerClosed = errors.New("use of closed network connection")
|
||||||
|
|
||||||
|
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
||||||
|
)
|
||||||
|
|
||||||
|
type win32Pipe struct {
|
||||||
|
*win32File
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32MessageBytePipe struct {
|
||||||
|
win32Pipe
|
||||||
|
writeClosed bool
|
||||||
|
readEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeAddress string
|
||||||
|
|
||||||
|
func (f *win32Pipe) LocalAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) RemoteAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) SetDeadline(t time.Time) error {
|
||||||
|
f.SetReadDeadline(t)
|
||||||
|
f.SetWriteDeadline(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||||
|
func (f *win32MessageBytePipe) CloseWrite() error {
|
||||||
|
if f.writeClosed {
|
||||||
|
return errPipeWriteClosed
|
||||||
|
}
|
||||||
|
err := f.win32File.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.win32File.Write(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writeClosed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||||
|
// they are used to implement CloseWrite().
|
||||||
|
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
||||||
|
if f.writeClosed {
|
||||||
|
return 0, errPipeWriteClosed
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return f.win32File.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
|
||||||
|
// mode pipe will return io.EOF, as will all subsequent reads.
|
||||||
|
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
||||||
|
if f.readEOF {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n, err := f.win32File.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
|
// If this was the result of a zero-byte read, then
|
||||||
|
// it is possible that the read was due to a zero-size
|
||||||
|
// message. Since we are simulating CloseWrite with a
|
||||||
|
// zero-byte message, ensure that all future Read() calls
|
||||||
|
// also return EOF.
|
||||||
|
f.readEOF = true
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipe connects to a named pipe by path, timing out if the connection
|
||||||
|
// takes longer than the specified duration. If timeout is nil, then the timeout
|
||||||
|
// is the default timeout established by the pipe server.
|
||||||
|
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||||
|
var absTimeout time.Time
|
||||||
|
if timeout != nil {
|
||||||
|
absTimeout = time.Now().Add(*timeout)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var h syscall.Handle
|
||||||
|
for {
|
||||||
|
h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||||
|
if err != cERROR_PIPE_BUSY {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
var ms uint32
|
||||||
|
if absTimeout.IsZero() {
|
||||||
|
ms = cNMPWAIT_USE_DEFAULT_WAIT
|
||||||
|
} else if now.After(absTimeout) {
|
||||||
|
ms = cNMPWAIT_NOWAIT
|
||||||
|
} else {
|
||||||
|
ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000)
|
||||||
|
}
|
||||||
|
err = waitNamedPipe(path, ms)
|
||||||
|
if err != nil {
|
||||||
|
if err == cERROR_SEM_TIMEOUT {
|
||||||
|
return nil, ErrTimeout
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var state uint32
|
||||||
|
err = getNamedPipeHandleState(h, &state, nil, nil, nil, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if state&cPIPE_READMODE_MESSAGE != 0 {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("message readmode pipes not supported")}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pipe is in message mode, return a message byte pipe, which
|
||||||
|
// supports CloseWrite().
|
||||||
|
if flags&cPIPE_TYPE_MESSAGE != 0 {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: f, path: path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: f, path: path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptResponse struct {
|
||||||
|
f *win32File
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32PipeListener struct {
|
||||||
|
firstHandle syscall.Handle
|
||||||
|
path string
|
||||||
|
securityDescriptor []byte
|
||||||
|
config PipeConfig
|
||||||
|
acceptCh chan (chan acceptResponse)
|
||||||
|
closeCh chan int
|
||||||
|
doneCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
|
||||||
|
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED
|
||||||
|
if first {
|
||||||
|
flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS
|
||||||
|
if c.MessageMode {
|
||||||
|
mode |= cPIPE_TYPE_MESSAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
var sa securityAttributes
|
||||||
|
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||||
|
if securityDescriptor != nil {
|
||||||
|
sa.SecurityDescriptor = &securityDescriptor[0]
|
||||||
|
}
|
||||||
|
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, &sa)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
||||||
|
h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) listenerRoutine() {
|
||||||
|
closed := false
|
||||||
|
for !closed {
|
||||||
|
select {
|
||||||
|
case <-l.closeCh:
|
||||||
|
closed = true
|
||||||
|
case responseCh := <-l.acceptCh:
|
||||||
|
p, err := l.makeServerPipe()
|
||||||
|
if err == nil {
|
||||||
|
// Wait for the client to connect.
|
||||||
|
ch := make(chan error)
|
||||||
|
go func() {
|
||||||
|
ch <- connectPipe(p)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case err = <-ch:
|
||||||
|
if err != nil {
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
case <-l.closeCh:
|
||||||
|
// Abort the connect request by closing the handle.
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
err = <-ch
|
||||||
|
if err == nil || err == ErrFileClosed {
|
||||||
|
err = ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseCh <- acceptResponse{p, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syscall.Close(l.firstHandle)
|
||||||
|
l.firstHandle = 0
|
||||||
|
// Notify Close() and Accept() callers that the handle has been closed.
|
||||||
|
close(l.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeConfig contain configuration for the pipe listener.
|
||||||
|
type PipeConfig struct {
|
||||||
|
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
|
||||||
|
SecurityDescriptor string
|
||||||
|
|
||||||
|
// MessageMode determines whether the pipe is in byte or message mode. In either
|
||||||
|
// case the pipe is read in byte mode by default. The only practical difference in
|
||||||
|
// this implementation is that CloseWrite() is only supported for message mode pipes;
|
||||||
|
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
|
||||||
|
// transferred to the reader (and returned as io.EOF in this implementation)
|
||||||
|
// when the pipe is in message mode.
|
||||||
|
MessageMode bool
|
||||||
|
|
||||||
|
// InputBufferSize specifies the size the input buffer, in bytes.
|
||||||
|
InputBufferSize int32
|
||||||
|
|
||||||
|
// OutputBufferSize specifies the size the input buffer, in bytes.
|
||||||
|
OutputBufferSize int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
|
||||||
|
// The pipe must not already exist.
|
||||||
|
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
||||||
|
var (
|
||||||
|
sd []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if c == nil {
|
||||||
|
c = &PipeConfig{}
|
||||||
|
}
|
||||||
|
if c.SecurityDescriptor != "" {
|
||||||
|
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h, err := makeServerPipeHandle(path, sd, c, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Immediately open and then close a client handle so that the named pipe is
|
||||||
|
// created but not currently accepting connections.
|
||||||
|
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
syscall.Close(h2)
|
||||||
|
l := &win32PipeListener{
|
||||||
|
firstHandle: h,
|
||||||
|
path: path,
|
||||||
|
securityDescriptor: sd,
|
||||||
|
config: *c,
|
||||||
|
acceptCh: make(chan (chan acceptResponse)),
|
||||||
|
closeCh: make(chan int),
|
||||||
|
doneCh: make(chan int),
|
||||||
|
}
|
||||||
|
go l.listenerRoutine()
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectPipe(p *win32File) error {
|
||||||
|
c, err := p.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = connectNamedPipe(p.handle, &c.o)
|
||||||
|
_, err = p.asyncIo(c, time.Time{}, 0, err)
|
||||||
|
if err != nil && err != cERROR_PIPE_CONNECTED {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Accept() (net.Conn, error) {
|
||||||
|
ch := make(chan acceptResponse)
|
||||||
|
select {
|
||||||
|
case l.acceptCh <- ch:
|
||||||
|
response := <-ch
|
||||||
|
err := response.err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l.config.MessageMode {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
||||||
|
case <-l.doneCh:
|
||||||
|
return nil, ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Close() error {
|
||||||
|
select {
|
||||||
|
case l.closeCh <- 1:
|
||||||
|
<-l.doneCh
|
||||||
|
case <-l.doneCh:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Addr() net.Addr {
|
||||||
|
return pipeAddress(l.path)
|
||||||
|
}
|
327
gateway/vendor/github.com/Microsoft/go-winio/pipe_test.go
generated
vendored
Normal file
327
gateway/vendor/github.com/Microsoft/go-winio/pipe_test.go
generated
vendored
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testPipeName = `\\.\pipe\winiotestpipe`
|
||||||
|
|
||||||
|
func TestDialUnknownFailsImmediately(t *testing.T) {
|
||||||
|
_, err := DialPipe(testPipeName, nil)
|
||||||
|
if err.(*os.PathError).Err != syscall.ENOENT {
|
||||||
|
t.Fatalf("expected ENOENT got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialListenerTimesOut(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
var d = time.Duration(10 * time.Millisecond)
|
||||||
|
_, err = DialPipe(testPipeName, &d)
|
||||||
|
if err != ErrTimeout {
|
||||||
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialAccessDeniedWithRestrictedSD(t *testing.T) {
|
||||||
|
c := PipeConfig{
|
||||||
|
SecurityDescriptor: "D:P(A;;0x1200FF;;;WD)",
|
||||||
|
}
|
||||||
|
l, err := ListenPipe(testPipeName, &c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
_, err = DialPipe(testPipeName, nil)
|
||||||
|
if err.(*os.PathError).Err != syscall.ERROR_ACCESS_DENIED {
|
||||||
|
t.Fatalf("expected ERROR_ACCESS_DENIED, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConnection(cfg *PipeConfig) (client net.Conn, server net.Conn, err error) {
|
||||||
|
l, err := ListenPipe(testPipeName, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
c net.Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
ch := make(chan response)
|
||||||
|
go func() {
|
||||||
|
c, err := l.Accept()
|
||||||
|
ch <- response{c, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c, err := DialPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r := <-ch
|
||||||
|
if err = r.err; err != nil {
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client = c
|
||||||
|
server = r.c
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadTimeout(t *testing.T) {
|
||||||
|
c, s, err := getConnection(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
c.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
|
||||||
|
|
||||||
|
buf := make([]byte, 10)
|
||||||
|
_, err = c.Read(buf)
|
||||||
|
if err != ErrTimeout {
|
||||||
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func server(l net.Listener, ch chan int) {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
|
||||||
|
s, err := rw.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = rw.WriteString("got " + s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = rw.Flush()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
ch <- 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFullListenDialReadWrite(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
ch := make(chan int)
|
||||||
|
go server(l, ch)
|
||||||
|
|
||||||
|
c, err := DialPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
|
||||||
|
_, err = rw.WriteString("hello world\n")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = rw.Flush()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := rw.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ms := "got hello world\n"
|
||||||
|
if s != ms {
|
||||||
|
t.Errorf("expected '%s', got '%s'", ms, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseAbortsListen(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan error)
|
||||||
|
go func() {
|
||||||
|
_, err := l.Accept()
|
||||||
|
ch <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(30 * time.Millisecond)
|
||||||
|
l.Close()
|
||||||
|
|
||||||
|
err = <-ch
|
||||||
|
if err != ErrPipeListenerClosed {
|
||||||
|
t.Fatalf("expected ErrPipeListenerClosed, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureEOFOnClose(t *testing.T, r io.Reader, w io.Closer) {
|
||||||
|
b := make([]byte, 10)
|
||||||
|
w.Close()
|
||||||
|
n, err := r.Read(b)
|
||||||
|
if n > 0 {
|
||||||
|
t.Errorf("unexpected byte count %d", n)
|
||||||
|
}
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Errorf("expected EOF: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseClientEOFServer(t *testing.T) {
|
||||||
|
c, s, err := getConnection(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
ensureEOFOnClose(t, c, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseServerEOFClient(t *testing.T) {
|
||||||
|
c, s, err := getConnection(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
ensureEOFOnClose(t, s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseWriteEOF(t *testing.T) {
|
||||||
|
cfg := &PipeConfig{
|
||||||
|
MessageMode: true,
|
||||||
|
}
|
||||||
|
c, s, err := getConnection(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
type closeWriter interface {
|
||||||
|
CloseWrite() error
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.(closeWriter).CloseWrite()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 10)
|
||||||
|
_, err = s.Read(b)
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAcceptAfterCloseFails(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
l.Close()
|
||||||
|
_, err = l.Accept()
|
||||||
|
if err != ErrPipeListenerClosed {
|
||||||
|
t.Fatalf("expected ErrPipeListenerClosed, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialTimesOutByDefault(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
_, err = DialPipe(testPipeName, nil)
|
||||||
|
if err != ErrTimeout {
|
||||||
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloseWriter interface {
|
||||||
|
CloseWrite() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoWithMessaging(t *testing.T) {
|
||||||
|
c := PipeConfig{
|
||||||
|
MessageMode: true, // Use message mode so that CloseWrite() is supported
|
||||||
|
InputBufferSize: 65536, // Use 64KB buffers to improve performance
|
||||||
|
OutputBufferSize: 65536,
|
||||||
|
}
|
||||||
|
l, err := ListenPipe(testPipeName, &c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
listenerDone := make(chan bool)
|
||||||
|
clientDone := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
// server echo
|
||||||
|
conn, e := l.Accept()
|
||||||
|
if e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond) // make *sure* we don't begin to read before eof signal is sent
|
||||||
|
io.Copy(conn, conn)
|
||||||
|
conn.(CloseWriter).CloseWrite()
|
||||||
|
close(listenerDone)
|
||||||
|
}()
|
||||||
|
timeout := 1 * time.Second
|
||||||
|
client, err := DialPipe(testPipeName, &timeout)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// client read back
|
||||||
|
bytes := make([]byte, 2)
|
||||||
|
n, e := client.Read(bytes)
|
||||||
|
if e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
t.Fatalf("expected 2 bytes, got %v", n)
|
||||||
|
}
|
||||||
|
close(clientDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
payload := make([]byte, 2)
|
||||||
|
payload[0] = 0
|
||||||
|
payload[1] = 1
|
||||||
|
|
||||||
|
n, err := client.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
t.Fatalf("expected 2 bytes, got %v", n)
|
||||||
|
}
|
||||||
|
client.(CloseWriter).CloseWrite()
|
||||||
|
<-listenerDone
|
||||||
|
<-clientDone
|
||||||
|
}
|
202
gateway/vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
202
gateway/vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
||||||
|
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
||||||
|
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
||||||
|
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
||||||
|
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
|
||||||
|
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
||||||
|
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
||||||
|
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
|
||||||
|
|
||||||
|
const (
|
||||||
|
SE_PRIVILEGE_ENABLED = 2
|
||||||
|
|
||||||
|
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
|
||||||
|
|
||||||
|
SeBackupPrivilege = "SeBackupPrivilege"
|
||||||
|
SeRestorePrivilege = "SeRestorePrivilege"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
securityAnonymous = iota
|
||||||
|
securityIdentification
|
||||||
|
securityImpersonation
|
||||||
|
securityDelegation
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
privNames = make(map[string]uint64)
|
||||||
|
privNameMutex sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivilegeError represents an error enabling privileges.
|
||||||
|
type PrivilegeError struct {
|
||||||
|
privileges []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PrivilegeError) Error() string {
|
||||||
|
s := ""
|
||||||
|
if len(e.privileges) > 1 {
|
||||||
|
s = "Could not enable privileges "
|
||||||
|
} else {
|
||||||
|
s = "Could not enable privilege "
|
||||||
|
}
|
||||||
|
for i, p := range e.privileges {
|
||||||
|
if i != 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += `"`
|
||||||
|
s += getPrivilegeName(p)
|
||||||
|
s += `"`
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivilege enables a single privilege for a function call.
|
||||||
|
func RunWithPrivilege(name string, fn func() error) error {
|
||||||
|
return RunWithPrivileges([]string{name}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivileges enables privileges for a function call.
|
||||||
|
func RunWithPrivileges(names []string, fn func() error) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
token, err := newThreadToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer releaseThreadToken(token)
|
||||||
|
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapPrivileges(names []string) ([]uint64, error) {
|
||||||
|
var privileges []uint64
|
||||||
|
privNameMutex.Lock()
|
||||||
|
defer privNameMutex.Unlock()
|
||||||
|
for _, name := range names {
|
||||||
|
p, ok := privNames[name]
|
||||||
|
if !ok {
|
||||||
|
err := lookupPrivilegeValue("", name, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privNames[name] = p
|
||||||
|
}
|
||||||
|
privileges = append(privileges, p)
|
||||||
|
}
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableProcessPrivileges enables privileges globally for the process.
|
||||||
|
func EnableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableProcessPrivileges disables privileges globally for the process.
|
||||||
|
func DisableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableDisableProcessPrivilege(names []string, action uint32) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, _ := windows.GetCurrentProcess()
|
||||||
|
var token windows.Token
|
||||||
|
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer token.Close()
|
||||||
|
return adjustPrivileges(token, privileges, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
||||||
|
for _, p := range privileges {
|
||||||
|
binary.Write(&b, binary.LittleEndian, p)
|
||||||
|
binary.Write(&b, binary.LittleEndian, action)
|
||||||
|
}
|
||||||
|
prevState := make([]byte, b.Len())
|
||||||
|
reqSize := uint32(0)
|
||||||
|
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
|
||||||
|
if !success {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == ERROR_NOT_ALL_ASSIGNED {
|
||||||
|
return &PrivilegeError{privileges}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrivilegeName(luid uint64) string {
|
||||||
|
var nameBuffer [256]uint16
|
||||||
|
bufSize := uint32(len(nameBuffer))
|
||||||
|
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %d>", luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayNameBuffer [256]uint16
|
||||||
|
displayBufSize := uint32(len(displayNameBuffer))
|
||||||
|
var langID uint32
|
||||||
|
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newThreadToken() (windows.Token, error) {
|
||||||
|
err := impersonateSelf(securityImpersonation)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var token windows.Token
|
||||||
|
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
|
||||||
|
if err != nil {
|
||||||
|
rerr := revertToSelf()
|
||||||
|
if rerr != nil {
|
||||||
|
panic(rerr)
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseThreadToken(h windows.Token) {
|
||||||
|
err := revertToSelf()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
h.Close()
|
||||||
|
}
|
17
gateway/vendor/github.com/Microsoft/go-winio/privileges_test.go
generated
vendored
Normal file
17
gateway/vendor/github.com/Microsoft/go-winio/privileges_test.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestRunWithUnavailablePrivilege(t *testing.T) {
|
||||||
|
err := RunWithPrivilege("SeCreateTokenPrivilege", func() error { return nil })
|
||||||
|
if _, ok := err.(*PrivilegeError); err == nil || !ok {
|
||||||
|
t.Fatal("expected PrivilegeError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunWithPrivileges(t *testing.T) {
|
||||||
|
err := RunWithPrivilege("SeShutdownPrivilege", func() error { return nil })
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
128
gateway/vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
128
gateway/vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
reparseTagMountPoint = 0xA0000003
|
||||||
|
reparseTagSymlink = 0xA000000C
|
||||||
|
)
|
||||||
|
|
||||||
|
type reparseDataBuffer struct {
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReparsePoint describes a Win32 symlink or mount point.
|
||||||
|
type ReparsePoint struct {
|
||||||
|
Target string
|
||||||
|
IsMountPoint bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
|
||||||
|
// mount point reparse point.
|
||||||
|
type UnsupportedReparsePointError struct {
|
||||||
|
Tag uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnsupportedReparsePointError) Error() string {
|
||||||
|
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
|
||||||
|
// or a mount point.
|
||||||
|
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
|
||||||
|
tag := binary.LittleEndian.Uint32(b[0:4])
|
||||||
|
return DecodeReparsePointData(tag, b[8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||||
|
isMountPoint := false
|
||||||
|
switch tag {
|
||||||
|
case reparseTagMountPoint:
|
||||||
|
isMountPoint = true
|
||||||
|
case reparseTagSymlink:
|
||||||
|
default:
|
||||||
|
return nil, &UnsupportedReparsePointError{tag}
|
||||||
|
}
|
||||||
|
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
|
||||||
|
if !isMountPoint {
|
||||||
|
nameOffset += 4
|
||||||
|
}
|
||||||
|
nameLength := binary.LittleEndian.Uint16(b[6:8])
|
||||||
|
name := make([]uint16, nameLength/2)
|
||||||
|
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDriveLetter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
||||||
|
// mount point.
|
||||||
|
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
||||||
|
// Generate an NT path and determine if this is a relative path.
|
||||||
|
var ntTarget string
|
||||||
|
relative := false
|
||||||
|
if strings.HasPrefix(rp.Target, `\\?\`) {
|
||||||
|
ntTarget = `\??\` + rp.Target[4:]
|
||||||
|
} else if strings.HasPrefix(rp.Target, `\\`) {
|
||||||
|
ntTarget = `\??\UNC\` + rp.Target[2:]
|
||||||
|
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
|
||||||
|
ntTarget = `\??\` + rp.Target
|
||||||
|
} else {
|
||||||
|
ntTarget = rp.Target
|
||||||
|
relative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The paths must be NUL-terminated even though they are counted strings.
|
||||||
|
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
|
||||||
|
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
|
||||||
|
|
||||||
|
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
|
||||||
|
size += len(ntTarget16)*2 + len(target16)*2
|
||||||
|
|
||||||
|
tag := uint32(reparseTagMountPoint)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
tag = reparseTagSymlink
|
||||||
|
size += 4 // Add room for symlink flags
|
||||||
|
}
|
||||||
|
|
||||||
|
data := reparseDataBuffer{
|
||||||
|
ReparseTag: tag,
|
||||||
|
ReparseDataLength: uint16(size),
|
||||||
|
SubstituteNameOffset: 0,
|
||||||
|
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
|
||||||
|
PrintNameOffset: uint16(len(ntTarget16) * 2),
|
||||||
|
PrintNameLength: uint16((len(target16) - 1) * 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
binary.Write(&b, binary.LittleEndian, &data)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
flags := uint32(0)
|
||||||
|
if relative {
|
||||||
|
flags |= 1
|
||||||
|
}
|
||||||
|
binary.Write(&b, binary.LittleEndian, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.Write(&b, binary.LittleEndian, ntTarget16)
|
||||||
|
binary.Write(&b, binary.LittleEndian, target16)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
98
gateway/vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
98
gateway/vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
|
||||||
|
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
|
||||||
|
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
|
||||||
|
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
|
||||||
|
//sys localFree(mem uintptr) = LocalFree
|
||||||
|
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
|
||||||
|
|
||||||
|
const (
|
||||||
|
cERROR_NONE_MAPPED = syscall.Errno(1332)
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountLookupError struct {
|
||||||
|
Name string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AccountLookupError) Error() string {
|
||||||
|
if e.Name == "" {
|
||||||
|
return "lookup account: empty account name specified"
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
switch e.Err {
|
||||||
|
case cERROR_NONE_MAPPED:
|
||||||
|
s = "not found"
|
||||||
|
default:
|
||||||
|
s = e.Err.Error()
|
||||||
|
}
|
||||||
|
return "lookup account " + e.Name + ": " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
type SddlConversionError struct {
|
||||||
|
Sddl string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SddlConversionError) Error() string {
|
||||||
|
return "convert " + e.Sddl + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupSidByName looks up the SID of an account by name
|
||||||
|
func LookupSidByName(name string) (sid string, err error) {
|
||||||
|
if name == "" {
|
||||||
|
return "", &AccountLookupError{name, cERROR_NONE_MAPPED}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidSize, sidNameUse, refDomainSize uint32
|
||||||
|
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sidBuffer := make([]byte, sidSize)
|
||||||
|
refDomainBuffer := make([]uint16, refDomainSize)
|
||||||
|
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
var strBuffer *uint16
|
||||||
|
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sid = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
|
||||||
|
localFree(uintptr(unsafe.Pointer(strBuffer)))
|
||||||
|
return sid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
|
||||||
|
var sdBuffer uintptr
|
||||||
|
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &SddlConversionError{sddl, err}
|
||||||
|
}
|
||||||
|
defer localFree(sdBuffer)
|
||||||
|
sd := make([]byte, getSecurityDescriptorLength(sdBuffer))
|
||||||
|
copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)])
|
||||||
|
return sd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SecurityDescriptorToSddl(sd []byte) (string, error) {
|
||||||
|
var sddl *uint16
|
||||||
|
// The returned string length seems to including an aribtrary number of terminating NULs.
|
||||||
|
// Don't use it.
|
||||||
|
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer localFree(uintptr(unsafe.Pointer(sddl)))
|
||||||
|
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil
|
||||||
|
}
|
26
gateway/vendor/github.com/Microsoft/go-winio/sd_test.go
generated
vendored
Normal file
26
gateway/vendor/github.com/Microsoft/go-winio/sd_test.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestLookupInvalidSid(t *testing.T) {
|
||||||
|
_, err := LookupSidByName(".\\weoifjdsklfj")
|
||||||
|
aerr, ok := err.(*AccountLookupError)
|
||||||
|
if !ok || aerr.Err != cERROR_NONE_MAPPED {
|
||||||
|
t.Fatalf("expected AccountLookupError with ERROR_NONE_MAPPED, got %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupValidSid(t *testing.T) {
|
||||||
|
sid, err := LookupSidByName("Everyone")
|
||||||
|
if err != nil || sid != "S-1-1-0" {
|
||||||
|
t.Fatal("expected S-1-1-0, got %s, %s", sid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupEmptyNameFails(t *testing.T) {
|
||||||
|
_, err := LookupSidByName(".\\weoifjdsklfj")
|
||||||
|
aerr, ok := err.(*AccountLookupError)
|
||||||
|
if !ok || aerr.Err != cERROR_NONE_MAPPED {
|
||||||
|
t.Fatalf("expected AccountLookupError with ERROR_NONE_MAPPED, got %s", err)
|
||||||
|
}
|
||||||
|
}
|
3
gateway/vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
3
gateway/vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
|
138
gateway/vendor/github.com/Microsoft/go-winio/wim/decompress.go
generated
vendored
Normal file
138
gateway/vendor/github.com/Microsoft/go-winio/wim/decompress.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package wim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/wim/lzx"
|
||||||
|
)
|
||||||
|
|
||||||
|
const chunkSize = 32768 // Compressed resource chunk size
|
||||||
|
|
||||||
|
type compressedReader struct {
|
||||||
|
r *io.SectionReader
|
||||||
|
d io.ReadCloser
|
||||||
|
chunks []int64
|
||||||
|
curChunk int
|
||||||
|
originalSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCompressedReader(r *io.SectionReader, originalSize int64, offset int64) (*compressedReader, error) {
|
||||||
|
nchunks := (originalSize + chunkSize - 1) / chunkSize
|
||||||
|
var base int64
|
||||||
|
chunks := make([]int64, nchunks)
|
||||||
|
if originalSize <= 0xffffffff {
|
||||||
|
// 32-bit chunk offsets
|
||||||
|
base = (nchunks - 1) * 4
|
||||||
|
chunks32 := make([]uint32, nchunks-1)
|
||||||
|
err := binary.Read(r, binary.LittleEndian, chunks32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, n := range chunks32 {
|
||||||
|
chunks[i+1] = int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 64-bit chunk offsets
|
||||||
|
base = (nchunks - 1) * 8
|
||||||
|
err := binary.Read(r, binary.LittleEndian, chunks[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range chunks {
|
||||||
|
chunks[i] = c + base
|
||||||
|
}
|
||||||
|
|
||||||
|
cr := &compressedReader{
|
||||||
|
r: r,
|
||||||
|
chunks: chunks,
|
||||||
|
originalSize: originalSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cr.reset(int(offset / chunkSize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
suboff := offset % chunkSize
|
||||||
|
if suboff != 0 {
|
||||||
|
_, err := io.CopyN(ioutil.Discard, cr.d, suboff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) chunkOffset(n int) int64 {
|
||||||
|
if n == len(r.chunks) {
|
||||||
|
return r.r.Size()
|
||||||
|
}
|
||||||
|
return r.chunks[n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) chunkSize(n int) int {
|
||||||
|
return int(r.chunkOffset(n+1) - r.chunkOffset(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) uncompressedSize(n int) int {
|
||||||
|
if n < len(r.chunks)-1 {
|
||||||
|
return chunkSize
|
||||||
|
}
|
||||||
|
size := int(r.originalSize % chunkSize)
|
||||||
|
if size == 0 {
|
||||||
|
size = chunkSize
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) reset(n int) error {
|
||||||
|
if n >= len(r.chunks) {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
if r.d != nil {
|
||||||
|
r.d.Close()
|
||||||
|
}
|
||||||
|
r.curChunk = n
|
||||||
|
size := r.chunkSize(n)
|
||||||
|
uncompressedSize := r.uncompressedSize(n)
|
||||||
|
section := io.NewSectionReader(r.r, r.chunkOffset(n), int64(size))
|
||||||
|
if size != uncompressedSize {
|
||||||
|
d, err := lzx.NewReader(section, uncompressedSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.d = d
|
||||||
|
} else {
|
||||||
|
r.d = ioutil.NopCloser(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) Read(b []byte) (int, error) {
|
||||||
|
for {
|
||||||
|
n, err := r.d.Read(b)
|
||||||
|
if err != io.EOF {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.reset(r.curChunk + 1)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) Close() error {
|
||||||
|
var err error
|
||||||
|
if r.d != nil {
|
||||||
|
err = r.d.Close()
|
||||||
|
r.d = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
606
gateway/vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go
generated
vendored
Normal file
606
gateway/vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go
generated
vendored
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
// Package lzx implements a decompressor for the the WIM variant of the
|
||||||
|
// LZX compression algorithm.
|
||||||
|
//
|
||||||
|
// The LZX algorithm is an earlier variant of LZX DELTA, which is documented
|
||||||
|
// at https://msdn.microsoft.com/en-us/library/cc483133(v=exchg.80).aspx.
|
||||||
|
package lzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maincodecount = 496
|
||||||
|
maincodesplit = 256
|
||||||
|
lencodecount = 249
|
||||||
|
lenshift = 9
|
||||||
|
codemask = 0x1ff
|
||||||
|
tablebits = 9
|
||||||
|
tablesize = 1 << tablebits
|
||||||
|
|
||||||
|
maxBlockSize = 32768
|
||||||
|
windowSize = 32768
|
||||||
|
|
||||||
|
maxTreePathLen = 16
|
||||||
|
|
||||||
|
e8filesize = 12000000
|
||||||
|
maxe8offset = 0x3fffffff
|
||||||
|
|
||||||
|
verbatimBlock = 1
|
||||||
|
alignedOffsetBlock = 2
|
||||||
|
uncompressedBlock = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var footerBits = [...]byte{
|
||||||
|
0, 0, 0, 0, 1, 1, 2, 2,
|
||||||
|
3, 3, 4, 4, 5, 5, 6, 6,
|
||||||
|
7, 7, 8, 8, 9, 9, 10, 10,
|
||||||
|
11, 11, 12, 12, 13, 13, 14,
|
||||||
|
}
|
||||||
|
|
||||||
|
var basePosition = [...]uint16{
|
||||||
|
0, 1, 2, 3, 4, 6, 8, 12,
|
||||||
|
16, 24, 32, 48, 64, 96, 128, 192,
|
||||||
|
256, 384, 512, 768, 1024, 1536, 2048, 3072,
|
||||||
|
4096, 6144, 8192, 12288, 16384, 24576, 32768,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCorrupt = errors.New("LZX data corrupt")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader is an interface used by the decompressor to access
|
||||||
|
// the input stream. If the provided io.Reader does not implement
|
||||||
|
// Reader, then a bufio.Reader is used.
|
||||||
|
type Reader interface {
|
||||||
|
io.Reader
|
||||||
|
io.ByteReader
|
||||||
|
}
|
||||||
|
|
||||||
|
type decompressor struct {
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
unaligned bool
|
||||||
|
nbits byte
|
||||||
|
c uint32
|
||||||
|
lru [3]uint16
|
||||||
|
uncompressed int
|
||||||
|
windowReader *bytes.Reader
|
||||||
|
mainlens [maincodecount]byte
|
||||||
|
lenlens [lencodecount]byte
|
||||||
|
window [windowSize]byte
|
||||||
|
b []byte
|
||||||
|
bv int
|
||||||
|
bo int
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:noinline
|
||||||
|
func (f *decompressor) fail(err error) {
|
||||||
|
if f.err == nil {
|
||||||
|
f.err = err
|
||||||
|
}
|
||||||
|
f.bo = 0
|
||||||
|
f.bv = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *decompressor) ensureAtLeast(n int) error {
|
||||||
|
if f.bv-f.bo >= n {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.err != nil {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.bv != f.bo {
|
||||||
|
copy(f.b[:f.bv-f.bo], f.b[f.bo:f.bv])
|
||||||
|
}
|
||||||
|
n, err := io.ReadAtLeast(f.r, f.b[f.bv-f.bo:], n)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
} else {
|
||||||
|
f.fail(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.bv = f.bv - f.bo + n
|
||||||
|
f.bo = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// feed retrieves another 16-bit word from the stream and consumes
|
||||||
|
// it into f.c. It returns false if there are no more bytes available.
|
||||||
|
// Otherwise, on error, it sets f.err.
|
||||||
|
func (f *decompressor) feed() bool {
|
||||||
|
err := f.ensureAtLeast(2)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.c |= (uint32(f.b[f.bo+1])<<8 | uint32(f.b[f.bo])) << (16 - f.nbits)
|
||||||
|
f.nbits += 16
|
||||||
|
f.bo += 2
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBits retrieves the next n bits from the byte stream. n
|
||||||
|
// must be <= 16. It sets f.err on error.
|
||||||
|
func (f *decompressor) getBits(n byte) uint16 {
|
||||||
|
if f.nbits < n {
|
||||||
|
if !f.feed() {
|
||||||
|
f.fail(io.ErrUnexpectedEOF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c := uint16(f.c >> (32 - n))
|
||||||
|
f.c <<= n
|
||||||
|
f.nbits -= n
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type huffman struct {
|
||||||
|
extra [][]uint16
|
||||||
|
maxbits byte
|
||||||
|
table [tablesize]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildTable builds a huffman decoding table from a slice of code lengths,
|
||||||
|
// one per code, in order. Each code length must be <= maxTreePathLen.
|
||||||
|
// See https://en.wikipedia.org/wiki/Canonical_Huffman_code.
|
||||||
|
func buildTable(codelens []byte) *huffman {
|
||||||
|
// Determine the number of codes of each length, and the
|
||||||
|
// maximum length.
|
||||||
|
var count [maxTreePathLen + 1]uint
|
||||||
|
var max byte
|
||||||
|
for _, cl := range codelens {
|
||||||
|
count[cl]++
|
||||||
|
if max < cl {
|
||||||
|
max = cl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if max == 0 {
|
||||||
|
return &huffman{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the first code of each length.
|
||||||
|
var first [maxTreePathLen + 1]uint
|
||||||
|
code := uint(0)
|
||||||
|
for i := byte(1); i <= max; i++ {
|
||||||
|
code <<= 1
|
||||||
|
first[i] = code
|
||||||
|
code += count[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if code != 1<<max {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a table for code lookup. For code sizes < max,
|
||||||
|
// put all possible suffixes for the code into the table, too.
|
||||||
|
// For max > tablebits, split long codes into additional tables
|
||||||
|
// of suffixes of max-tablebits length.
|
||||||
|
h := &huffman{maxbits: max}
|
||||||
|
if max > tablebits {
|
||||||
|
core := first[tablebits+1] / 2 // Number of codes that fit without extra tables
|
||||||
|
nextra := 1<<tablebits - core // Number of extra entries
|
||||||
|
h.extra = make([][]uint16, nextra)
|
||||||
|
for code := core; code < 1<<tablebits; code++ {
|
||||||
|
h.table[code] = uint16(code - core)
|
||||||
|
h.extra[code-core] = make([]uint16, 1<<(max-tablebits))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, cl := range codelens {
|
||||||
|
if cl != 0 {
|
||||||
|
code := first[cl]
|
||||||
|
first[cl]++
|
||||||
|
v := uint16(cl)<<lenshift | uint16(i)
|
||||||
|
if cl <= tablebits {
|
||||||
|
extendedCode := code << (tablebits - cl)
|
||||||
|
for j := uint(0); j < 1<<(tablebits-cl); j++ {
|
||||||
|
h.table[extendedCode+j] = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefix := code >> (cl - tablebits)
|
||||||
|
suffix := code & (1<<(cl-tablebits) - 1)
|
||||||
|
extendedCode := suffix << (max - cl)
|
||||||
|
for j := uint(0); j < 1<<(max-cl); j++ {
|
||||||
|
h.extra[h.table[prefix]][extendedCode+j] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCode retrieves the next code using the provided
|
||||||
|
// huffman tree. It sets f.err on error.
|
||||||
|
func (f *decompressor) getCode(h *huffman) uint16 {
|
||||||
|
if h.maxbits > 0 {
|
||||||
|
if f.nbits < maxTreePathLen {
|
||||||
|
f.feed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// For codes with length < tablebits, it doesn't matter
|
||||||
|
// what the remainder of the bits used for table lookup
|
||||||
|
// are, since entries with all possible suffixes were
|
||||||
|
// added to the table.
|
||||||
|
c := h.table[f.c>>(32-tablebits)]
|
||||||
|
if c >= 1<<lenshift {
|
||||||
|
// The code is already in c.
|
||||||
|
} else {
|
||||||
|
c = h.extra[c][f.c<<tablebits>>(32-(h.maxbits-tablebits))]
|
||||||
|
}
|
||||||
|
|
||||||
|
n := byte(c >> lenshift)
|
||||||
|
if f.nbits >= n {
|
||||||
|
// Only consume the length of the code, not the maximum
|
||||||
|
// code length.
|
||||||
|
f.c <<= n
|
||||||
|
f.nbits -= n
|
||||||
|
return c & codemask
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fail(io.ErrUnexpectedEOF)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an empty tree. It should not be used.
|
||||||
|
f.fail(errCorrupt)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTree updates the huffman tree path lengths in lens by
|
||||||
|
// reading and decoding lengths from the byte stream. lens
|
||||||
|
// should be prepopulated with the previous block's tree's path
|
||||||
|
// lengths. For the first block, lens should be zero.
|
||||||
|
func (f *decompressor) readTree(lens []byte) error {
|
||||||
|
// Get the pre-tree for the main tree.
|
||||||
|
var pretreeLen [20]byte
|
||||||
|
for i := range pretreeLen {
|
||||||
|
pretreeLen[i] = byte(f.getBits(4))
|
||||||
|
}
|
||||||
|
if f.err != nil {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
h := buildTable(pretreeLen[:])
|
||||||
|
|
||||||
|
// The lengths are encoded as a series of huffman codes
|
||||||
|
// encoded by the pre-tree.
|
||||||
|
for i := 0; i < len(lens); {
|
||||||
|
c := byte(f.getCode(h))
|
||||||
|
if f.err != nil {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case c <= 16: // length is delta from previous length
|
||||||
|
lens[i] = (lens[i] + 17 - c) % 17
|
||||||
|
i++
|
||||||
|
case c == 17: // next n + 4 lengths are zero
|
||||||
|
zeroes := int(f.getBits(4)) + 4
|
||||||
|
if i+zeroes > len(lens) {
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
for j := 0; j < zeroes; j++ {
|
||||||
|
lens[i+j] = 0
|
||||||
|
}
|
||||||
|
i += zeroes
|
||||||
|
case c == 18: // next n + 20 lengths are zero
|
||||||
|
zeroes := int(f.getBits(5)) + 20
|
||||||
|
if i+zeroes > len(lens) {
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
for j := 0; j < zeroes; j++ {
|
||||||
|
lens[i+j] = 0
|
||||||
|
}
|
||||||
|
i += zeroes
|
||||||
|
case c == 19: // next n + 4 lengths all have the same value
|
||||||
|
same := int(f.getBits(1)) + 4
|
||||||
|
if i+same > len(lens) {
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
c = byte(f.getCode(h))
|
||||||
|
if c > 16 {
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
l := (lens[i] + 17 - c) % 17
|
||||||
|
for j := 0; j < same; j++ {
|
||||||
|
lens[i+j] = l
|
||||||
|
}
|
||||||
|
i += same
|
||||||
|
default:
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.err != nil {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *decompressor) readBlockHeader() (byte, uint16, error) {
|
||||||
|
// If the previous block was an unaligned uncompressed block, restore
|
||||||
|
// 2-byte alignment.
|
||||||
|
if f.unaligned {
|
||||||
|
err := f.ensureAtLeast(1)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
f.bo++
|
||||||
|
f.unaligned = false
|
||||||
|
}
|
||||||
|
|
||||||
|
blockType := f.getBits(3)
|
||||||
|
full := f.getBits(1)
|
||||||
|
var blockSize uint16
|
||||||
|
if full != 0 {
|
||||||
|
blockSize = maxBlockSize
|
||||||
|
} else {
|
||||||
|
blockSize = f.getBits(16)
|
||||||
|
if blockSize > maxBlockSize {
|
||||||
|
return 0, 0, errCorrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.err != nil {
|
||||||
|
return 0, 0, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch blockType {
|
||||||
|
case verbatimBlock, alignedOffsetBlock:
|
||||||
|
// The caller will read the huffman trees.
|
||||||
|
case uncompressedBlock:
|
||||||
|
if f.nbits > 16 {
|
||||||
|
panic("impossible: more than one 16-bit word remains")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the remaining bits in the current 16-bit word
|
||||||
|
// If there are no bits left, discard a full 16-bit word.
|
||||||
|
n := f.nbits
|
||||||
|
if n == 0 {
|
||||||
|
n = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
f.getBits(n)
|
||||||
|
|
||||||
|
// Read the LRU values for the next block.
|
||||||
|
err := f.ensureAtLeast(12)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.lru[0] = uint16(binary.LittleEndian.Uint32(f.b[f.bo : f.bo+4]))
|
||||||
|
f.lru[1] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+4 : f.bo+8]))
|
||||||
|
f.lru[2] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+8 : f.bo+12]))
|
||||||
|
f.bo += 12
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0, 0, errCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
return byte(blockType), blockSize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTrees reads the two or three huffman trees for the current block.
|
||||||
|
// readAligned specifies whether to read the aligned offset tree.
|
||||||
|
func (f *decompressor) readTrees(readAligned bool) (main *huffman, length *huffman, aligned *huffman, err error) {
|
||||||
|
// Aligned offset blocks start with a small aligned offset tree.
|
||||||
|
if readAligned {
|
||||||
|
var alignedLen [8]byte
|
||||||
|
for i := range alignedLen {
|
||||||
|
alignedLen[i] = byte(f.getBits(3))
|
||||||
|
}
|
||||||
|
aligned = buildTable(alignedLen[:])
|
||||||
|
if aligned == nil {
|
||||||
|
err = errors.New("corrupt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main tree is encoded in two parts.
|
||||||
|
err = f.readTree(f.mainlens[:maincodesplit])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = f.readTree(f.mainlens[maincodesplit:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
main = buildTable(f.mainlens[:])
|
||||||
|
if main == nil {
|
||||||
|
err = errors.New("corrupt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The length tree is encoding in a single part.
|
||||||
|
err = f.readTree(f.lenlens[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
length = buildTable(f.lenlens[:])
|
||||||
|
if length == nil {
|
||||||
|
err = errors.New("corrupt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// readCompressedBlock decodes a compressed block, writing into the window
|
||||||
|
// starting at start and ending at end, and using the provided huffman trees.
|
||||||
|
func (f *decompressor) readCompressedBlock(start, end uint16, hmain, hlength, haligned *huffman) (int, error) {
|
||||||
|
i := start
|
||||||
|
for i < end {
|
||||||
|
main := f.getCode(hmain)
|
||||||
|
if f.err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if main < 256 {
|
||||||
|
// Literal byte.
|
||||||
|
f.window[i] = byte(main)
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a match backward in the window. Determine
|
||||||
|
// the offset and dlength.
|
||||||
|
matchlen := (main - 256) % 8
|
||||||
|
slot := (main - 256) / 8
|
||||||
|
|
||||||
|
// The length is either the low bits of the code,
|
||||||
|
// or if this is 7, is encoded with the length tree.
|
||||||
|
if matchlen == 7 {
|
||||||
|
matchlen += f.getCode(hlength)
|
||||||
|
}
|
||||||
|
matchlen += 2
|
||||||
|
|
||||||
|
var matchoffset uint16
|
||||||
|
if slot < 3 {
|
||||||
|
// The offset is one of the LRU values.
|
||||||
|
matchoffset = f.lru[slot]
|
||||||
|
f.lru[slot] = f.lru[0]
|
||||||
|
f.lru[0] = matchoffset
|
||||||
|
} else {
|
||||||
|
// The offset is encoded as a combination of the
|
||||||
|
// slot and more bits from the bit stream.
|
||||||
|
offsetbits := footerBits[slot]
|
||||||
|
var verbatimbits, alignedbits uint16
|
||||||
|
if offsetbits > 0 {
|
||||||
|
if haligned != nil && offsetbits >= 3 {
|
||||||
|
// This is an aligned offset block. Combine
|
||||||
|
// the bits written verbatim with the aligned
|
||||||
|
// offset tree code.
|
||||||
|
verbatimbits = f.getBits(offsetbits-3) * 8
|
||||||
|
alignedbits = f.getCode(haligned)
|
||||||
|
} else {
|
||||||
|
// There are no aligned offset bits to read,
|
||||||
|
// only verbatim bits.
|
||||||
|
verbatimbits = f.getBits(offsetbits)
|
||||||
|
alignedbits = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matchoffset = basePosition[slot] + verbatimbits + alignedbits - 2
|
||||||
|
// Update the LRU cache.
|
||||||
|
f.lru[2] = f.lru[1]
|
||||||
|
f.lru[1] = f.lru[0]
|
||||||
|
f.lru[0] = matchoffset
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchoffset <= i && matchlen <= end-i {
|
||||||
|
copyend := i + matchlen
|
||||||
|
for ; i < copyend; i++ {
|
||||||
|
f.window[i] = f.window[i-matchoffset]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.fail(errCorrupt)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return int(i - start), f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBlock decodes the current block and returns the number of uncompressed bytes.
|
||||||
|
func (f *decompressor) readBlock(start uint16) (int, error) {
|
||||||
|
blockType, size, err := f.readBlockHeader()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockType == uncompressedBlock {
|
||||||
|
if size%2 == 1 {
|
||||||
|
// Remember to realign the byte stream at the next block.
|
||||||
|
f.unaligned = true
|
||||||
|
}
|
||||||
|
copied := 0
|
||||||
|
if f.bo < f.bv {
|
||||||
|
copied = int(size)
|
||||||
|
s := int(start)
|
||||||
|
if copied > f.bv-f.bo {
|
||||||
|
copied = f.bv - f.bo
|
||||||
|
}
|
||||||
|
copy(f.window[s:s+copied], f.b[f.bo:f.bo+copied])
|
||||||
|
f.bo += copied
|
||||||
|
}
|
||||||
|
n, err := io.ReadFull(f.r, f.window[start+uint16(copied):start+size])
|
||||||
|
return copied + n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hmain, hlength, haligned, err := f.readTrees(blockType == alignedOffsetBlock)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.readCompressedBlock(start, start+size, hmain, hlength, haligned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeE8 reverses the 0xe8 x86 instruction encoding that was performed
|
||||||
|
// to the uncompressed data before it was compressed.
|
||||||
|
func decodeE8(b []byte, off int64) {
|
||||||
|
if off > maxe8offset || len(b) < 10 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < len(b)-10; i++ {
|
||||||
|
if b[i] == 0xe8 {
|
||||||
|
currentPtr := int32(off) + int32(i)
|
||||||
|
abs := int32(binary.LittleEndian.Uint32(b[i+1 : i+5]))
|
||||||
|
if abs >= -currentPtr && abs < e8filesize {
|
||||||
|
var rel int32
|
||||||
|
if abs >= 0 {
|
||||||
|
rel = abs - currentPtr
|
||||||
|
} else {
|
||||||
|
rel = abs + e8filesize
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(b[i+1:i+5], uint32(rel))
|
||||||
|
}
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *decompressor) Read(b []byte) (int, error) {
|
||||||
|
// Read and uncompress everything.
|
||||||
|
if f.windowReader == nil {
|
||||||
|
n := 0
|
||||||
|
for n < f.uncompressed {
|
||||||
|
k, err := f.readBlock(uint16(n))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n += k
|
||||||
|
}
|
||||||
|
decodeE8(f.window[:f.uncompressed], 0)
|
||||||
|
f.windowReader = bytes.NewReader(f.window[:f.uncompressed])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just read directly from the window.
|
||||||
|
return f.windowReader.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *decompressor) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a new io.ReadCloser that decompresses a
|
||||||
|
// WIM LZX stream until uncompressedSize bytes have been returned.
|
||||||
|
func NewReader(r io.Reader, uncompressedSize int) (io.ReadCloser, error) {
|
||||||
|
if uncompressedSize > windowSize {
|
||||||
|
return nil, errors.New("uncompressed size is limited to 32KB")
|
||||||
|
}
|
||||||
|
f := &decompressor{
|
||||||
|
lru: [3]uint16{1, 1, 1},
|
||||||
|
uncompressed: uncompressedSize,
|
||||||
|
b: make([]byte, 4096),
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
51
gateway/vendor/github.com/Microsoft/go-winio/wim/validate/validate.go
generated
vendored
Normal file
51
gateway/vendor/github.com/Microsoft/go-winio/wim/validate/validate.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/wim"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
f, err := os.Open(flag.Arg(0))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := wim.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n%#v\n", w.Image[0], w.Image[0].Windows)
|
||||||
|
|
||||||
|
dir, err := w.Image[0].Open()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = recur(dir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recur(d *wim.File) error {
|
||||||
|
files, err := d.Readdir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", d.Name, err)
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if f.IsDir() {
|
||||||
|
err = recur(f)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", f.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
866
gateway/vendor/github.com/Microsoft/go-winio/wim/wim.go
generated
vendored
Normal file
866
gateway/vendor/github.com/Microsoft/go-winio/wim/wim.go
generated
vendored
Normal file
@ -0,0 +1,866 @@
|
|||||||
|
// Package wim implements a WIM file parser.
|
||||||
|
//
|
||||||
|
// WIM files are used to distribute Windows file system and container images.
|
||||||
|
// They are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/dd861280.aspx.
|
||||||
|
package wim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File attribute constants from Windows.
|
||||||
|
const (
|
||||||
|
FILE_ATTRIBUTE_READONLY = 0x00000001
|
||||||
|
FILE_ATTRIBUTE_HIDDEN = 0x00000002
|
||||||
|
FILE_ATTRIBUTE_SYSTEM = 0x00000004
|
||||||
|
FILE_ATTRIBUTE_DIRECTORY = 0x00000010
|
||||||
|
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
|
||||||
|
FILE_ATTRIBUTE_DEVICE = 0x00000040
|
||||||
|
FILE_ATTRIBUTE_NORMAL = 0x00000080
|
||||||
|
FILE_ATTRIBUTE_TEMPORARY = 0x00000100
|
||||||
|
FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
|
||||||
|
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
|
||||||
|
FILE_ATTRIBUTE_COMPRESSED = 0x00000800
|
||||||
|
FILE_ATTRIBUTE_OFFLINE = 0x00001000
|
||||||
|
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000
|
||||||
|
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000
|
||||||
|
FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000
|
||||||
|
FILE_ATTRIBUTE_VIRTUAL = 0x00010000
|
||||||
|
FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000
|
||||||
|
FILE_ATTRIBUTE_EA = 0x00040000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows processor architectures.
|
||||||
|
const (
|
||||||
|
PROCESSOR_ARCHITECTURE_INTEL = 0
|
||||||
|
PROCESSOR_ARCHITECTURE_MIPS = 1
|
||||||
|
PROCESSOR_ARCHITECTURE_ALPHA = 2
|
||||||
|
PROCESSOR_ARCHITECTURE_PPC = 3
|
||||||
|
PROCESSOR_ARCHITECTURE_SHX = 4
|
||||||
|
PROCESSOR_ARCHITECTURE_ARM = 5
|
||||||
|
PROCESSOR_ARCHITECTURE_IA64 = 6
|
||||||
|
PROCESSOR_ARCHITECTURE_ALPHA64 = 7
|
||||||
|
PROCESSOR_ARCHITECTURE_MSIL = 8
|
||||||
|
PROCESSOR_ARCHITECTURE_AMD64 = 9
|
||||||
|
PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10
|
||||||
|
PROCESSOR_ARCHITECTURE_NEUTRAL = 11
|
||||||
|
PROCESSOR_ARCHITECTURE_ARM64 = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0}
|
||||||
|
|
||||||
|
type guid struct {
|
||||||
|
Data1 uint32
|
||||||
|
Data2 uint16
|
||||||
|
Data3 uint16
|
||||||
|
Data4 [8]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g guid) String() string {
|
||||||
|
return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", g.Data1, g.Data2, g.Data3, g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7])
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceDescriptor struct {
|
||||||
|
FlagsAndCompressedSize uint64
|
||||||
|
Offset int64
|
||||||
|
OriginalSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type resFlag byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
resFlagFree resFlag = 1 << iota
|
||||||
|
resFlagMetadata
|
||||||
|
resFlagCompressed
|
||||||
|
resFlagSpanned
|
||||||
|
)
|
||||||
|
|
||||||
|
const validate = false
|
||||||
|
|
||||||
|
const supportedResFlags = resFlagMetadata | resFlagCompressed
|
||||||
|
|
||||||
|
func (r *resourceDescriptor) Flags() resFlag {
|
||||||
|
return resFlag(r.FlagsAndCompressedSize >> 56)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resourceDescriptor) CompressedSize() int64 {
|
||||||
|
return int64(r.FlagsAndCompressedSize & 0xffffffffffffff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resourceDescriptor) String() string {
|
||||||
|
s := fmt.Sprintf("%d bytes at %d", r.CompressedSize(), r.Offset)
|
||||||
|
if r.Flags()&4 != 0 {
|
||||||
|
s += fmt.Sprintf(" (uncompresses to %d)", r.OriginalSize)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHA1Hash contains the SHA1 hash of a file or stream.
|
||||||
|
type SHA1Hash [20]byte
|
||||||
|
|
||||||
|
type streamDescriptor struct {
|
||||||
|
resourceDescriptor
|
||||||
|
PartNumber uint16
|
||||||
|
RefCount uint32
|
||||||
|
Hash SHA1Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
type hdrFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
hdrFlagReserved hdrFlag = 1 << iota
|
||||||
|
hdrFlagCompressed
|
||||||
|
hdrFlagReadOnly
|
||||||
|
hdrFlagSpanned
|
||||||
|
hdrFlagResourceOnly
|
||||||
|
hdrFlagMetadataOnly
|
||||||
|
hdrFlagWriteInProgress
|
||||||
|
hdrFlagRpFix
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hdrFlagCompressReserved hdrFlag = 1 << (iota + 16)
|
||||||
|
hdrFlagCompressXpress
|
||||||
|
hdrFlagCompressLzx
|
||||||
|
)
|
||||||
|
|
||||||
|
const supportedHdrFlags = hdrFlagRpFix | hdrFlagReadOnly | hdrFlagCompressed | hdrFlagCompressLzx
|
||||||
|
|
||||||
|
type wimHeader struct {
|
||||||
|
ImageTag [8]byte
|
||||||
|
Size uint32
|
||||||
|
Version uint32
|
||||||
|
Flags hdrFlag
|
||||||
|
CompressionSize uint32
|
||||||
|
WIMGuid guid
|
||||||
|
PartNumber uint16
|
||||||
|
TotalParts uint16
|
||||||
|
ImageCount uint32
|
||||||
|
OffsetTable resourceDescriptor
|
||||||
|
XMLData resourceDescriptor
|
||||||
|
BootMetadata resourceDescriptor
|
||||||
|
BootIndex uint32
|
||||||
|
Padding uint32
|
||||||
|
Integrity resourceDescriptor
|
||||||
|
Unused [60]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type securityblockDisk struct {
|
||||||
|
TotalLength uint32
|
||||||
|
NumEntries uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const securityblockDiskSize = 8
|
||||||
|
|
||||||
|
type direntry struct {
|
||||||
|
Attributes uint32
|
||||||
|
SecurityID uint32
|
||||||
|
SubdirOffset int64
|
||||||
|
Unused1, Unused2 int64
|
||||||
|
CreationTime Filetime
|
||||||
|
LastAccessTime Filetime
|
||||||
|
LastWriteTime Filetime
|
||||||
|
Hash SHA1Hash
|
||||||
|
Padding uint32
|
||||||
|
ReparseHardLink int64
|
||||||
|
StreamCount uint16
|
||||||
|
ShortNameLength uint16
|
||||||
|
FileNameLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var direntrySize = int64(binary.Size(direntry{}) + 8) // includes an 8-byte length prefix
|
||||||
|
|
||||||
|
type streamentry struct {
|
||||||
|
Unused int64
|
||||||
|
Hash SHA1Hash
|
||||||
|
NameLength int16
|
||||||
|
}
|
||||||
|
|
||||||
|
var streamentrySize = int64(binary.Size(streamentry{}) + 8) // includes an 8-byte length prefix
|
||||||
|
|
||||||
|
// Filetime represents a Windows time.
|
||||||
|
type Filetime struct {
|
||||||
|
LowDateTime uint32
|
||||||
|
HighDateTime uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the time as time.Time.
|
||||||
|
func (ft *Filetime) Time() time.Time {
|
||||||
|
// 100-nanosecond intervals since January 1, 1601
|
||||||
|
nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)
|
||||||
|
// change starting time to the Epoch (00:00:00 UTC, January 1, 1970)
|
||||||
|
nsec -= 116444736000000000
|
||||||
|
// convert into nanoseconds
|
||||||
|
nsec *= 100
|
||||||
|
return time.Unix(0, nsec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML unmarshals the time from a WIM XML blob.
|
||||||
|
func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type time struct {
|
||||||
|
Low string `xml:"LOWPART"`
|
||||||
|
High string `xml:"HIGHPART"`
|
||||||
|
}
|
||||||
|
var t time
|
||||||
|
err := d.DecodeElement(&t, &start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
low, err := strconv.ParseUint(t.Low, 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
high, err := strconv.ParseUint(t.High, 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ft.LowDateTime = uint32(low)
|
||||||
|
ft.HighDateTime = uint32(high)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type info struct {
|
||||||
|
Image []ImageInfo `xml:"IMAGE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageInfo contains information about the image.
|
||||||
|
type ImageInfo struct {
|
||||||
|
Name string `xml:"NAME"`
|
||||||
|
Index int `xml:"INDEX,attr"`
|
||||||
|
CreationTime Filetime `xml:"CREATIONTIME"`
|
||||||
|
ModTime Filetime `xml:"LASTMODIFICATIONTIME"`
|
||||||
|
Windows *WindowsInfo `xml:"WINDOWS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowsInfo contains information about the Windows installation in the image.
|
||||||
|
type WindowsInfo struct {
|
||||||
|
Arch byte `xml:"ARCH"`
|
||||||
|
ProductName string `xml:"PRODUCTNAME"`
|
||||||
|
EditionID string `xml:"EDITIONID"`
|
||||||
|
InstallationType string `xml:"INSTALLATIONTYPE"`
|
||||||
|
ProductType string `xml:"PRODUCTTYPE"`
|
||||||
|
Languages []string `xml:"LANGUAGES>LANGUAGE"`
|
||||||
|
DefaultLanguage string `xml:"LANGUAGES>DEFAULT"`
|
||||||
|
Version Version `xml:"VERSION"`
|
||||||
|
SystemRoot string `xml:"SYSTEMROOT"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version represents a Windows build version.
|
||||||
|
type Version struct {
|
||||||
|
Major int `xml:"MAJOR"`
|
||||||
|
Minor int `xml:"MINOR"`
|
||||||
|
Build int `xml:"BUILD"`
|
||||||
|
SPBuild int `xml:"SPBUILD"`
|
||||||
|
SPLevel int `xml:"SPLEVEL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseError is returned when the WIM cannot be parsed.
|
||||||
|
type ParseError struct {
|
||||||
|
Oper string
|
||||||
|
Path string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ParseError) Error() string {
|
||||||
|
if e.Path == "" {
|
||||||
|
return "WIM parse error at " + e.Oper + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader provides functions to read a WIM file.
|
||||||
|
type Reader struct {
|
||||||
|
hdr wimHeader
|
||||||
|
r io.ReaderAt
|
||||||
|
fileData map[SHA1Hash]resourceDescriptor
|
||||||
|
|
||||||
|
XMLInfo string // The XML information about the WIM.
|
||||||
|
Image []*Image // The WIM's images.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image represents an image within a WIM file.
|
||||||
|
type Image struct {
|
||||||
|
wim *Reader
|
||||||
|
offset resourceDescriptor
|
||||||
|
sds [][]byte
|
||||||
|
rootOffset int64
|
||||||
|
r io.ReadCloser
|
||||||
|
curOffset int64
|
||||||
|
m sync.Mutex
|
||||||
|
|
||||||
|
ImageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamHeader contains alternate data stream metadata.
|
||||||
|
type StreamHeader struct {
|
||||||
|
Name string
|
||||||
|
Hash SHA1Hash
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream represents an alternate data stream or reparse point data stream.
|
||||||
|
type Stream struct {
|
||||||
|
StreamHeader
|
||||||
|
wim *Reader
|
||||||
|
offset resourceDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileHeader contains file metadata.
|
||||||
|
type FileHeader struct {
|
||||||
|
Name string
|
||||||
|
ShortName string
|
||||||
|
Attributes uint32
|
||||||
|
SecurityDescriptor []byte
|
||||||
|
CreationTime Filetime
|
||||||
|
LastAccessTime Filetime
|
||||||
|
LastWriteTime Filetime
|
||||||
|
Hash SHA1Hash
|
||||||
|
Size int64
|
||||||
|
LinkID int64
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseReserved uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// File represents a file or directory in a WIM image.
|
||||||
|
type File struct {
|
||||||
|
FileHeader
|
||||||
|
Streams []*Stream
|
||||||
|
offset resourceDescriptor
|
||||||
|
img *Image
|
||||||
|
subdirOffset int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a Reader that can be used to read WIM file data.
|
||||||
|
func NewReader(f io.ReaderAt) (*Reader, error) {
|
||||||
|
r := &Reader{r: f}
|
||||||
|
section := io.NewSectionReader(f, 0, 0xffff)
|
||||||
|
err := binary.Read(section, binary.LittleEndian, &r.hdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hdr.ImageTag != wimImageTag {
|
||||||
|
return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hdr.Flags&^supportedHdrFlags != 0 {
|
||||||
|
return nil, fmt.Errorf("unsupported WIM flags %x", r.hdr.Flags&^supportedHdrFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hdr.CompressionSize != 0x8000 {
|
||||||
|
return nil, fmt.Errorf("unsupported compression size %d", r.hdr.CompressionSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hdr.TotalParts != 1 {
|
||||||
|
return nil, errors.New("multi-part WIM not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
fileData, images, err := r.readOffsetTable(&r.hdr.OffsetTable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlinfo, err := r.readXML()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var info info
|
||||||
|
err = xml.Unmarshal([]byte(xmlinfo), &info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ParseError{Oper: "XML info", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, img := range images {
|
||||||
|
for _, imgInfo := range info.Image {
|
||||||
|
if imgInfo.Index == i+1 {
|
||||||
|
img.ImageInfo = imgInfo
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.fileData = fileData
|
||||||
|
r.Image = images
|
||||||
|
r.XMLInfo = xmlinfo
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases resources associated with the Reader.
|
||||||
|
func (r *Reader) Close() error {
|
||||||
|
for _, img := range r.Image {
|
||||||
|
img.reset()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) resourceReader(hdr *resourceDescriptor) (io.ReadCloser, error) {
|
||||||
|
return r.resourceReaderWithOffset(hdr, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) (io.ReadCloser, error) {
|
||||||
|
var sr io.ReadCloser
|
||||||
|
section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize())
|
||||||
|
if hdr.Flags()&resFlagCompressed == 0 {
|
||||||
|
section.Seek(offset, 0)
|
||||||
|
sr = ioutil.NopCloser(section)
|
||||||
|
} else {
|
||||||
|
cr, err := newCompressedReader(section, hdr.OriginalSize, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sr = cr
|
||||||
|
}
|
||||||
|
|
||||||
|
return sr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) {
|
||||||
|
rsrc, err := r.resourceReader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rsrc.Close()
|
||||||
|
return ioutil.ReadAll(rsrc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readXML() (string, error) {
|
||||||
|
if r.hdr.XMLData.CompressedSize() == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
rsrc, err := r.resourceReader(&r.hdr.XMLData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer rsrc.Close()
|
||||||
|
|
||||||
|
XMLData := make([]uint16, r.hdr.XMLData.OriginalSize/2)
|
||||||
|
err = binary.Read(rsrc, binary.LittleEndian, XMLData)
|
||||||
|
if err != nil {
|
||||||
|
return "", &ParseError{Oper: "XML data", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The BOM will always indicate little-endian UTF-16.
|
||||||
|
if XMLData[0] != 0xfeff {
|
||||||
|
return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")}
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(XMLData[1:])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) {
|
||||||
|
fileData := make(map[SHA1Hash]resourceDescriptor)
|
||||||
|
var images []*Image
|
||||||
|
|
||||||
|
offsetTable, err := r.readResource(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, &ParseError{Oper: "offset table", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
br := bytes.NewReader(offsetTable)
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
var res streamDescriptor
|
||||||
|
err := binary.Read(br, binary.LittleEndian, &res)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, &ParseError{Oper: "offset table", Err: err}
|
||||||
|
}
|
||||||
|
if res.Flags()&^supportedResFlags != 0 {
|
||||||
|
return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation for ad-hoc testing
|
||||||
|
if validate {
|
||||||
|
sec, err := r.resourceReader(&res.resourceDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint(i, err))
|
||||||
|
}
|
||||||
|
hash := sha1.New()
|
||||||
|
_, err = io.Copy(hash, sec)
|
||||||
|
sec.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint(i, err))
|
||||||
|
}
|
||||||
|
var cmphash SHA1Hash
|
||||||
|
copy(cmphash[:], hash.Sum(nil))
|
||||||
|
if cmphash != res.Hash {
|
||||||
|
panic(fmt.Sprint(i, "hash mismatch"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Flags()&resFlagMetadata != 0 {
|
||||||
|
image := &Image{
|
||||||
|
wim: r,
|
||||||
|
offset: res.resourceDescriptor,
|
||||||
|
}
|
||||||
|
images = append(images, image)
|
||||||
|
} else {
|
||||||
|
fileData[res.Hash] = res.resourceDescriptor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(images) != int(r.hdr.ImageCount) {
|
||||||
|
return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileData, images, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) {
|
||||||
|
var secBlock securityblockDisk
|
||||||
|
err = binary.Read(rsrc, binary.LittleEndian, &secBlock)
|
||||||
|
if err != nil {
|
||||||
|
err = &ParseError{Oper: "security table", Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n += securityblockDiskSize
|
||||||
|
|
||||||
|
secSizes := make([]int64, secBlock.NumEntries)
|
||||||
|
err = binary.Read(rsrc, binary.LittleEndian, &secSizes)
|
||||||
|
if err != nil {
|
||||||
|
err = &ParseError{Oper: "security table sizes", Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n += int64(secBlock.NumEntries * 8)
|
||||||
|
|
||||||
|
sds = make([][]byte, secBlock.NumEntries)
|
||||||
|
for i, size := range secSizes {
|
||||||
|
sd := make([]byte, size&0xffffffff)
|
||||||
|
_, err = io.ReadFull(rsrc, sd)
|
||||||
|
if err != nil {
|
||||||
|
err = &ParseError{Oper: "security descriptor", Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += int64(len(sd))
|
||||||
|
sds[i] = sd
|
||||||
|
}
|
||||||
|
|
||||||
|
secsize := int64((secBlock.TotalLength + 7) &^ 7)
|
||||||
|
if n > secsize {
|
||||||
|
err = &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(ioutil.Discard, rsrc, secsize-n)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n = secsize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open parses the image and returns the root directory.
|
||||||
|
func (img *Image) Open() (*File, error) {
|
||||||
|
if img.sds == nil {
|
||||||
|
rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, img.rootOffset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sds, n, err := img.wim.readSecurityDescriptors(rsrc)
|
||||||
|
if err != nil {
|
||||||
|
rsrc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
img.sds = sds
|
||||||
|
img.r = rsrc
|
||||||
|
img.rootOffset = n
|
||||||
|
img.curOffset = n
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := img.readdir(img.rootOffset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(f) != 1 {
|
||||||
|
return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")}
|
||||||
|
}
|
||||||
|
return f[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) reset() {
|
||||||
|
if img.r != nil {
|
||||||
|
img.r.Close()
|
||||||
|
img.r = nil
|
||||||
|
}
|
||||||
|
img.curOffset = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) readdir(offset int64) ([]*File, error) {
|
||||||
|
img.m.Lock()
|
||||||
|
defer img.m.Unlock()
|
||||||
|
|
||||||
|
if offset < img.curOffset || offset > img.curOffset+chunkSize {
|
||||||
|
// Reset to seek backward or to seek forward very far.
|
||||||
|
img.reset()
|
||||||
|
}
|
||||||
|
if img.r == nil {
|
||||||
|
rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
img.r = rsrc
|
||||||
|
img.curOffset = offset
|
||||||
|
}
|
||||||
|
if offset > img.curOffset {
|
||||||
|
_, err := io.CopyN(ioutil.Discard, img.r, offset-img.curOffset)
|
||||||
|
if err != nil {
|
||||||
|
img.reset()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries []*File
|
||||||
|
for {
|
||||||
|
e, n, err := img.readNextEntry(img.r)
|
||||||
|
img.curOffset += n
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
img.reset()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entries = append(entries, e)
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) {
|
||||||
|
var length int64
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory length check", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if length == 0 {
|
||||||
|
return nil, 8, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
left := length
|
||||||
|
if left < direntrySize {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short")}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dentry direntry
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &dentry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= direntrySize
|
||||||
|
|
||||||
|
namesLen := int64(dentry.FileNameLength + 2 + dentry.ShortNameLength)
|
||||||
|
if left < namesLen {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")}
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([]uint16, namesLen/2)
|
||||||
|
err = binary.Read(r, binary.LittleEndian, names)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "file name", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= namesLen
|
||||||
|
|
||||||
|
var name, shortName string
|
||||||
|
if dentry.FileNameLength > 0 {
|
||||||
|
name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if dentry.ShortNameLength > 0 {
|
||||||
|
shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset resourceDescriptor
|
||||||
|
zerohash := SHA1Hash{}
|
||||||
|
if dentry.Hash != zerohash {
|
||||||
|
var ok bool
|
||||||
|
offset, ok = img.wim.fileData[dentry.Hash]
|
||||||
|
if !ok {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %#v", dentry)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &File{
|
||||||
|
FileHeader: FileHeader{
|
||||||
|
Attributes: dentry.Attributes,
|
||||||
|
CreationTime: dentry.CreationTime,
|
||||||
|
LastAccessTime: dentry.LastAccessTime,
|
||||||
|
LastWriteTime: dentry.LastWriteTime,
|
||||||
|
Hash: dentry.Hash,
|
||||||
|
Size: offset.OriginalSize,
|
||||||
|
Name: name,
|
||||||
|
ShortName: shortName,
|
||||||
|
},
|
||||||
|
|
||||||
|
offset: offset,
|
||||||
|
img: img,
|
||||||
|
subdirOffset: dentry.SubdirOffset,
|
||||||
|
}
|
||||||
|
|
||||||
|
isDir := false
|
||||||
|
|
||||||
|
if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 {
|
||||||
|
f.LinkID = dentry.ReparseHardLink
|
||||||
|
if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
isDir = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.ReparseTag = uint32(dentry.ReparseHardLink)
|
||||||
|
f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDir && f.subdirOffset == 0 {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")}
|
||||||
|
} else if !isDir && f.subdirOffset != 0 {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dentry.SecurityID != 0xffffffff {
|
||||||
|
f.SecurityDescriptor = img.sds[dentry.SecurityID]
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(ioutil.Discard, r, left)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dentry.StreamCount > 0 {
|
||||||
|
var streams []*Stream
|
||||||
|
for i := uint16(0); i < dentry.StreamCount; i++ {
|
||||||
|
s, n, err := img.readNextStream(r)
|
||||||
|
length += n
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
// The first unnamed stream should be treated as the file stream.
|
||||||
|
if i == 0 && s.Name == "" {
|
||||||
|
f.Hash = s.Hash
|
||||||
|
f.Size = s.Size
|
||||||
|
f.offset = s.offset
|
||||||
|
} else if s.Name != "" {
|
||||||
|
streams = append(streams, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Streams = streams
|
||||||
|
}
|
||||||
|
|
||||||
|
if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("reparse point is missing reparse stream")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) {
|
||||||
|
var length int64
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, 0, &ParseError{Oper: "stream length check", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left := length
|
||||||
|
if left < streamentrySize {
|
||||||
|
return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short")}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sentry streamentry
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &sentry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "stream entry", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= streamentrySize
|
||||||
|
|
||||||
|
if left < int64(sentry.NameLength) {
|
||||||
|
return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")}
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([]uint16, sentry.NameLength/2)
|
||||||
|
err = binary.Read(r, binary.LittleEndian, names)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "file name", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= int64(sentry.NameLength)
|
||||||
|
name := string(utf16.Decode(names))
|
||||||
|
|
||||||
|
var offset resourceDescriptor
|
||||||
|
if sentry.Hash != (SHA1Hash{}) {
|
||||||
|
var ok bool
|
||||||
|
offset, ok = img.wim.fileData[sentry.Hash]
|
||||||
|
if !ok {
|
||||||
|
return nil, 0, &ParseError{Oper: "stream entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Stream{
|
||||||
|
StreamHeader: StreamHeader{
|
||||||
|
Hash: sentry.Hash,
|
||||||
|
Size: offset.OriginalSize,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
wim: img.wim,
|
||||||
|
offset: offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(ioutil.Discard, r, left)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns an io.ReadCloser that can be used to read the stream's contents.
|
||||||
|
func (s *Stream) Open() (io.ReadCloser, error) {
|
||||||
|
return s.wim.resourceReader(&s.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns an io.ReadCloser that can be used to read the file's contents.
|
||||||
|
func (f *File) Open() (io.ReadCloser, error) {
|
||||||
|
return f.img.wim.resourceReader(&f.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir reads the directory entries.
|
||||||
|
func (f *File) Readdir() ([]*File, error) {
|
||||||
|
if !f.IsDir() {
|
||||||
|
return nil, errors.New("not a directory")
|
||||||
|
}
|
||||||
|
return f.img.readdir(f.subdirOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir returns whether the given file is a directory. It returns false when it
|
||||||
|
// is a directory reparse point.
|
||||||
|
func (f *FileHeader) IsDir() bool {
|
||||||
|
return f.Attributes&(FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_DIRECTORY
|
||||||
|
}
|
496
gateway/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
496
gateway/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
var (
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
modwinmm = windows.NewLazySystemDLL("winmm.dll")
|
||||||
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
|
||||||
|
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||||
|
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||||
|
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||||
|
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||||
|
proctimeBeginPeriod = modwinmm.NewProc("timeBeginPeriod")
|
||||||
|
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||||
|
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||||
|
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||||
|
procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
|
||||||
|
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
||||||
|
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
||||||
|
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
||||||
|
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
||||||
|
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
|
||||||
|
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
|
||||||
|
procLocalFree = modkernel32.NewProc("LocalFree")
|
||||||
|
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
|
||||||
|
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
|
||||||
|
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
|
||||||
|
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||||
|
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||||
|
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||||
|
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||||
|
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||||
|
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||||
|
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
|
||||||
|
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||||
|
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||||
|
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||||
|
)
|
||||||
|
|
||||||
|
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
|
||||||
|
newport = syscall.Handle(r0)
|
||||||
|
if newport == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeBeginPeriod(period uint32) (n int32) {
|
||||||
|
r0, _, _ := syscall.Syscall(proctimeBeginPeriod.Addr(), 1, uintptr(period), 0, 0)
|
||||||
|
n = int32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
|
||||||
|
handle = syscall.Handle(r0)
|
||||||
|
if handle == syscall.InvalidHandle {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFile(name string, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createFile(name *uint16, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
|
||||||
|
handle = syscall.Handle(r0)
|
||||||
|
if handle == syscall.InvalidHandle {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitNamedPipe(name string, timeout uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _waitNamedPipe(_p0, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _waitNamedPipe(name *uint16, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(accountName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(str)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func localFree(mem uintptr) {
|
||||||
|
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
|
||||||
|
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
|
||||||
|
len = uint32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if releaseAll {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
|
||||||
|
success = r0 != 0
|
||||||
|
if true {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func impersonateSelf(level uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func revertToSelf() (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if openAsSelf {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentThread() (h syscall.Handle) {
|
||||||
|
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
|
||||||
|
h = syscall.Handle(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *uint16
|
||||||
|
_p1, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeValue(_p0, _p1, luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeName(_p0, luid, buffer, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
} else {
|
||||||
|
_p1 = 0
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
} else {
|
||||||
|
_p2 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
} else {
|
||||||
|
_p1 = 0
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
} else {
|
||||||
|
_p2 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
1
gateway/vendor/github.com/Sirupsen/logrus/.gitignore
generated
vendored
Normal file
1
gateway/vendor/github.com/Sirupsen/logrus/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
logrus
|
8
gateway/vendor/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
8
gateway/vendor/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
- tip
|
||||||
|
install:
|
||||||
|
- go get -t ./...
|
||||||
|
script: GOMAXPROCS=4 GORACE="halt_on_error=1" go test -race -v ./...
|
94
gateway/vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
94
gateway/vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# 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)
|
21
gateway/vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
Normal file
21
gateway/vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
Normal file
@ -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.
|
479
gateway/vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
479
gateway/vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/Sirupsen/logrus) [](https://godoc.org/github.com/Sirupsen/logrus)
|
||||||
|
|
||||||
|
**Seeing weird case-sensitive problems?** See [this
|
||||||
|
issue](https://github.com/sirupsen/logrus/issues/451#issuecomment-264332021).
|
||||||
|
This change has been reverted. I apologize for causing this. I greatly
|
||||||
|
underestimated the impact this would have. Logrus strives for stability and
|
||||||
|
backwards compatibility and failed to provide that.
|
||||||
|
|
||||||
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
|
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||||
|
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
|
||||||
|
many large deployments. The core API is unlikely to change much but please
|
||||||
|
version control your Logrus to make sure you aren't fetching latest `master` on
|
||||||
|
every build.**
|
||||||
|
|
||||||
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
|
plain text):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
||||||
|
exit status 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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, 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 though 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 "aibrake"
|
||||||
|
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).
|
||||||
|
|
||||||
|
| Hook | Description |
|
||||||
|
| ----- | ----------- |
|
||||||
|
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
||||||
|
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
|
||||||
|
| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) |
|
||||||
|
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
|
||||||
|
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||||
|
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
||||||
|
| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) |
|
||||||
|
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
||||||
|
| [Firehose](https://github.com/beaubrewer/firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/)
|
||||||
|
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
|
||||||
|
| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) |
|
||||||
|
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||||
|
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||||
|
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
|
||||||
|
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
||||||
|
| [Influxus] (http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB] (http://influxdata.com/) |
|
||||||
|
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||||
|
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
|
||||||
|
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
||||||
|
| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||||
|
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||||
|
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
||||||
|
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||||
|
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
||||||
|
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
||||||
|
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
||||||
|
| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) |
|
||||||
|
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
||||||
|
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
|
||||||
|
| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
|
||||||
|
| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
|
||||||
|
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
|
||||||
|
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
|
||||||
|
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
|
||||||
|
| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
|
||||||
|
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
||||||
|
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||||
|
| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) |
|
||||||
|
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||||
|
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||||
|
| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
|
||||||
|
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||||
|
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
||||||
|
|
||||||
|
#### Level logging
|
||||||
|
|
||||||
|
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||||
|
|
||||||
|
```go
|
||||||
|
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).
|
||||||
|
* 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:
|
||||||
|
|
||||||
|
* [`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 P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||||
|
|
||||||
|
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 generated with different config at different environment.|
|
||||||
|
|[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 add the `test` hook
|
||||||
|
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger, hook := NewNullLogger()
|
||||||
|
logger.Error("Hello error")
|
||||||
|
|
||||||
|
assert.Equal(1, len(hook.Entries))
|
||||||
|
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal("Hello error", hook.LastEntry().Message)
|
||||||
|
|
||||||
|
hook.Reset()
|
||||||
|
assert.Nil(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 a `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 mutex for concurrent writes, this mutex is invoked 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 a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
|
||||||
|
|
||||||
|
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
64
gateway/vendor/github.com/Sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
64
gateway/vendor/github.com/Sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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 adds a Logrus Exit handler, 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)
|
||||||
|
}
|
74
gateway/vendor/github.com/Sirupsen/logrus/alt_exit_test.go
generated
vendored
Normal file
74
gateway/vendor/github.com/Sirupsen/logrus/alt_exit_test.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegister(t *testing.T) {
|
||||||
|
current := len(handlers)
|
||||||
|
RegisterExitHandler(func() {})
|
||||||
|
if len(handlers) != current+1 {
|
||||||
|
t.Fatalf("can't add handler")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandler(t *testing.T) {
|
||||||
|
gofile := "/tmp/testprog.go"
|
||||||
|
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
||||||
|
t.Fatalf("can't create go file")
|
||||||
|
}
|
||||||
|
|
||||||
|
outfile := "/tmp/testprog.out"
|
||||||
|
arg := time.Now().UTC().String()
|
||||||
|
err := exec.Command("go", "run", gofile, outfile, arg).Run()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("completed normally, should have failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(outfile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't read output file %s", outfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(data) != arg {
|
||||||
|
t.Fatalf("bad data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testprog = []byte(`
|
||||||
|
// Test program for atexit, gets output file and data as arguments and writes
|
||||||
|
// data to output file in atexit handler.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var outfile = ""
|
||||||
|
var data = ""
|
||||||
|
|
||||||
|
func handler() {
|
||||||
|
ioutil.WriteFile(outfile, []byte(data), 0666)
|
||||||
|
}
|
||||||
|
|
||||||
|
func badHandler() {
|
||||||
|
n := 0
|
||||||
|
fmt.Println(1/n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
outfile = flag.Arg(0)
|
||||||
|
data = flag.Arg(1)
|
||||||
|
|
||||||
|
logrus.RegisterExitHandler(handler)
|
||||||
|
logrus.RegisterExitHandler(badHandler)
|
||||||
|
logrus.Fatal("Bye bye")
|
||||||
|
}
|
||||||
|
`)
|
26
gateway/vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
Normal file
26
gateway/vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
Normal file
@ -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
|
275
gateway/vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
275
gateway/vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufferPool *sync.Pool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bufferPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// When formatter is called in entry.log(), an Buffer may be set to entry
|
||||||
|
Buffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntry(logger *Logger) *Entry {
|
||||||
|
return &Entry{
|
||||||
|
Logger: logger,
|
||||||
|
// Default is three fields, give a little extra room
|
||||||
|
Data: make(Fields, 5),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string representation from the reader and ultimately the
|
||||||
|
// formatter.
|
||||||
|
func (entry *Entry) String() (string, error) {
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
for k, v := range fields {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
entry.Time = time.Now()
|
||||||
|
entry.Level = level
|
||||||
|
entry.Message = msg
|
||||||
|
|
||||||
|
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
buffer = bufferPool.Get().(*bytes.Buffer)
|
||||||
|
buffer.Reset()
|
||||||
|
defer bufferPool.Put(buffer)
|
||||||
|
entry.Buffer = buffer
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(&entry)
|
||||||
|
entry.Buffer = nil
|
||||||
|
if err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
_, err = entry.Logger.Out.Write(serialized)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||||
|
}
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) Debug(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Print(args ...interface{}) {
|
||||||
|
entry.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warning(args ...interface{}) {
|
||||||
|
entry.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
panic(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Printf family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.Debug(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.Info(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||||
|
entry.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.Warn(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.Error(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.Fatal(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.Panic(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Println family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.Debug(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.Info(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Println(args ...interface{}) {
|
||||||
|
entry.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.Warn(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningln(args ...interface{}) {
|
||||||
|
entry.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.Error(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.Fatal(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.Panic(entry.sprintlnn(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]
|
||||||
|
}
|
77
gateway/vendor/github.com/Sirupsen/logrus/entry_test.go
generated
vendored
Normal file
77
gateway/vendor/github.com/Sirupsen/logrus/entry_test.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEntryWithError(t *testing.T) {
|
||||||
|
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
ErrorKey = "error"
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := fmt.Errorf("kaboom at layer %d", 4711)
|
||||||
|
|
||||||
|
assert.Equal(err, WithError(err).Data["error"])
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &bytes.Buffer{}
|
||||||
|
entry := NewEntry(logger)
|
||||||
|
|
||||||
|
assert.Equal(err, entry.WithError(err).Data["error"])
|
||||||
|
|
||||||
|
ErrorKey = "err"
|
||||||
|
|
||||||
|
assert.Equal(err, entry.WithError(err).Data["err"])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryPanicln(t *testing.T) {
|
||||||
|
errBoom := fmt.Errorf("boom time")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p := recover()
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
|
||||||
|
switch pVal := p.(type) {
|
||||||
|
case *Entry:
|
||||||
|
assert.Equal(t, "kaboom", pVal.Message)
|
||||||
|
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||||
|
default:
|
||||||
|
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &bytes.Buffer{}
|
||||||
|
entry := NewEntry(logger)
|
||||||
|
entry.WithField("err", errBoom).Panicln("kaboom")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryPanicf(t *testing.T) {
|
||||||
|
errBoom := fmt.Errorf("boom again")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p := recover()
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
|
||||||
|
switch pVal := p.(type) {
|
||||||
|
case *Entry:
|
||||||
|
assert.Equal(t, "kaboom true", pVal.Message)
|
||||||
|
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||||
|
default:
|
||||||
|
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &bytes.Buffer{}
|
||||||
|
entry := NewEntry(logger)
|
||||||
|
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
|
||||||
|
}
|
59
gateway/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
Normal file
59
gateway/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
// "os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Formatter = new(logrus.JSONFormatter)
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
|
||||||
|
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
|
// if err == nil {
|
||||||
|
// log.Out = file
|
||||||
|
// } else {
|
||||||
|
// log.Info("Failed to log to file, using default stderr")
|
||||||
|
// }
|
||||||
|
|
||||||
|
log.Level = logrus.DebugLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"err": err,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"number": 8,
|
||||||
|
}).Debug("Started observing beach")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"temperature": -4,
|
||||||
|
}).Debug("Temperature changes")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "orca",
|
||||||
|
"size": 9009,
|
||||||
|
}).Panic("It's over 9000!")
|
||||||
|
}
|
30
gateway/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
Normal file
30
gateway/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
193
gateway/vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
Normal file
193
gateway/vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Out = out
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFormatter sets the standard logger formatter.
|
||||||
|
func SetFormatter(formatter Formatter) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Formatter = formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the standard logger level.
|
||||||
|
func SetLevel(level Level) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the standard logger level.
|
||||||
|
func GetLevel() Level {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
return std.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHook adds a hook to the standard logger hooks.
|
||||||
|
func AddHook(hook Hook) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Hooks.Add(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func Fatal(args ...interface{}) {
|
||||||
|
std.Fatal(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.
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
std.Fatalf(format, 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.
|
||||||
|
func Fatalln(args ...interface{}) {
|
||||||
|
std.Fatalln(args...)
|
||||||
|
}
|
45
gateway/vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
45
gateway/vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const DefaultTimestampFormat = time.RFC3339
|
||||||
|
|
||||||
|
// 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` 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) {
|
||||||
|
if t, ok := data["time"]; ok {
|
||||||
|
data["fields.time"] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok := data["msg"]; ok {
|
||||||
|
data["fields.msg"] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
if l, ok := data["level"]; ok {
|
||||||
|
data["fields.level"] = l
|
||||||
|
}
|
||||||
|
}
|
101
gateway/vendor/github.com/Sirupsen/logrus/formatter_bench_test.go
generated
vendored
Normal file
101
gateway/vendor/github.com/Sirupsen/logrus/formatter_bench_test.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// smallFields is a small size data set for benchmarking
|
||||||
|
var smallFields = Fields{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
"one": "two",
|
||||||
|
"three": "four",
|
||||||
|
}
|
||||||
|
|
||||||
|
// largeFields is a large size data set for benchmarking
|
||||||
|
var largeFields = Fields{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
"one": "two",
|
||||||
|
"three": "four",
|
||||||
|
"five": "six",
|
||||||
|
"seven": "eight",
|
||||||
|
"nine": "ten",
|
||||||
|
"eleven": "twelve",
|
||||||
|
"thirteen": "fourteen",
|
||||||
|
"fifteen": "sixteen",
|
||||||
|
"seventeen": "eighteen",
|
||||||
|
"nineteen": "twenty",
|
||||||
|
"a": "b",
|
||||||
|
"c": "d",
|
||||||
|
"e": "f",
|
||||||
|
"g": "h",
|
||||||
|
"i": "j",
|
||||||
|
"k": "l",
|
||||||
|
"m": "n",
|
||||||
|
"o": "p",
|
||||||
|
"q": "r",
|
||||||
|
"s": "t",
|
||||||
|
"u": "v",
|
||||||
|
"w": "x",
|
||||||
|
"y": "z",
|
||||||
|
"this": "will",
|
||||||
|
"make": "thirty",
|
||||||
|
"entries": "yeah",
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorFields = Fields{
|
||||||
|
"foo": fmt.Errorf("bar"),
|
||||||
|
"baz": fmt.Errorf("qux"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkErrorTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{DisableColors: true}, errorFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallJSONFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &JSONFormatter{}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeJSONFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &JSONFormatter{}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
||||||
|
logger := New()
|
||||||
|
|
||||||
|
entry := &Entry{
|
||||||
|
Time: time.Time{},
|
||||||
|
Level: InfoLevel,
|
||||||
|
Message: "message",
|
||||||
|
Data: fields,
|
||||||
|
Logger: logger,
|
||||||
|
}
|
||||||
|
var d []byte
|
||||||
|
var err error
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
d, err = formatter.Format(entry)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(d)))
|
||||||
|
}
|
||||||
|
}
|
122
gateway/vendor/github.com/Sirupsen/logrus/hook_test.go
generated
vendored
Normal file
122
gateway/vendor/github.com/Sirupsen/logrus/hook_test.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestHook struct {
|
||||||
|
Fired bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *TestHook) Fire(entry *Entry) error {
|
||||||
|
hook.Fired = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *TestHook) Levels() []Level {
|
||||||
|
return []Level{
|
||||||
|
DebugLevel,
|
||||||
|
InfoLevel,
|
||||||
|
WarnLevel,
|
||||||
|
ErrorLevel,
|
||||||
|
FatalLevel,
|
||||||
|
PanicLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHookFires(t *testing.T) {
|
||||||
|
hook := new(TestHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
assert.Equal(t, hook.Fired, false)
|
||||||
|
|
||||||
|
log.Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, hook.Fired, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifyHook struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ModifyHook) Fire(entry *Entry) error {
|
||||||
|
entry.Data["wow"] = "whale"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ModifyHook) Levels() []Level {
|
||||||
|
return []Level{
|
||||||
|
DebugLevel,
|
||||||
|
InfoLevel,
|
||||||
|
WarnLevel,
|
||||||
|
ErrorLevel,
|
||||||
|
FatalLevel,
|
||||||
|
PanicLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHookCanModifyEntry(t *testing.T) {
|
||||||
|
hook := new(ModifyHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
log.WithField("wow", "elephant").Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["wow"], "whale")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCanFireMultipleHooks(t *testing.T) {
|
||||||
|
hook1 := new(ModifyHook)
|
||||||
|
hook2 := new(TestHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook1)
|
||||||
|
log.Hooks.Add(hook2)
|
||||||
|
|
||||||
|
log.WithField("wow", "elephant").Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["wow"], "whale")
|
||||||
|
assert.Equal(t, hook2.Fired, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorHook struct {
|
||||||
|
Fired bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ErrorHook) Fire(entry *Entry) error {
|
||||||
|
hook.Fired = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ErrorHook) Levels() []Level {
|
||||||
|
return []Level{
|
||||||
|
ErrorLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
|
||||||
|
hook := new(ErrorHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
log.Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, hook.Fired, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorHookShouldFireOnError(t *testing.T) {
|
||||||
|
hook := new(ErrorHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
log.Error("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, hook.Fired, true)
|
||||||
|
})
|
||||||
|
}
|
34
gateway/vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
Normal file
34
gateway/vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
39
gateway/vendor/github.com/Sirupsen/logrus/hooks/syslog/README.md
generated
vendored
Normal file
39
gateway/vendor/github.com/Sirupsen/logrus/hooks/syslog/README.md
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log/syslog"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log/syslog"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
54
gateway/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
generated
vendored
Normal file
54
gateway/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// +build !windows,!nacl,!plan9
|
||||||
|
|
||||||
|
package logrus_syslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"log/syslog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyslogHook to send logs via syslog.
|
||||||
|
type SyslogHook struct {
|
||||||
|
Writer *syslog.Writer
|
||||||
|
SyslogNetwork string
|
||||||
|
SyslogRaddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a hook to be added to an instance of logger. This is called with
|
||||||
|
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
|
||||||
|
// `if err == nil { log.Hooks.Add(hook) }`
|
||||||
|
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
|
||||||
|
w, err := syslog.Dial(network, raddr, priority, tag)
|
||||||
|
return &SyslogHook{w, network, raddr}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
|
||||||
|
line, err := entry.String()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch entry.Level {
|
||||||
|
case logrus.PanicLevel:
|
||||||
|
return hook.Writer.Crit(line)
|
||||||
|
case logrus.FatalLevel:
|
||||||
|
return hook.Writer.Crit(line)
|
||||||
|
case logrus.ErrorLevel:
|
||||||
|
return hook.Writer.Err(line)
|
||||||
|
case logrus.WarnLevel:
|
||||||
|
return hook.Writer.Warning(line)
|
||||||
|
case logrus.InfoLevel:
|
||||||
|
return hook.Writer.Info(line)
|
||||||
|
case logrus.DebugLevel:
|
||||||
|
return hook.Writer.Debug(line)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *SyslogHook) Levels() []logrus.Level {
|
||||||
|
return logrus.AllLevels
|
||||||
|
}
|
26
gateway/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
generated
vendored
Normal file
26
gateway/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package logrus_syslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"log/syslog"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalhostAddAndPrint(t *testing.T) {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to connect to local syslog.")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
|
||||||
|
for _, level := range hook.Levels() {
|
||||||
|
if len(log.Hooks[level]) != 1 {
|
||||||
|
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Congratulations!")
|
||||||
|
}
|
67
gateway/vendor/github.com/Sirupsen/logrus/hooks/test/test.go
generated
vendored
Normal file
67
gateway/vendor/github.com/Sirupsen/logrus/hooks/test/test.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test.Hook is a hook designed for dealing with logs in test scenarios.
|
||||||
|
type Hook struct {
|
||||||
|
Entries []*logrus.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installs a test hook for the global logger.
|
||||||
|
func NewGlobal() *Hook {
|
||||||
|
|
||||||
|
hook := new(Hook)
|
||||||
|
logrus.AddHook(hook)
|
||||||
|
|
||||||
|
return hook
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installs a test hook for a given local logger.
|
||||||
|
func NewLocal(logger *logrus.Logger) *Hook {
|
||||||
|
|
||||||
|
hook := new(Hook)
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
|
||||||
|
return hook
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a discarding logger and installs the test hook.
|
||||||
|
func NewNullLogger() (*logrus.Logger, *Hook) {
|
||||||
|
|
||||||
|
logger := logrus.New()
|
||||||
|
logger.Out = ioutil.Discard
|
||||||
|
|
||||||
|
return logger, NewLocal(logger)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Hook) Fire(e *logrus.Entry) error {
|
||||||
|
t.Entries = append(t.Entries, e)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Hook) Levels() []logrus.Level {
|
||||||
|
return logrus.AllLevels
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastEntry returns the last entry that was logged or nil.
|
||||||
|
func (t *Hook) LastEntry() (l *logrus.Entry) {
|
||||||
|
|
||||||
|
if i := len(t.Entries) - 1; i < 0 {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return t.Entries[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset removes all Entries from this test hook.
|
||||||
|
func (t *Hook) Reset() {
|
||||||
|
t.Entries = make([]*logrus.Entry, 0)
|
||||||
|
}
|
39
gateway/vendor/github.com/Sirupsen/logrus/hooks/test/test_test.go
generated
vendored
Normal file
39
gateway/vendor/github.com/Sirupsen/logrus/hooks/test/test_test.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAllHooks(t *testing.T) {
|
||||||
|
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
logger, hook := NewNullLogger()
|
||||||
|
assert.Nil(hook.LastEntry())
|
||||||
|
assert.Equal(0, len(hook.Entries))
|
||||||
|
|
||||||
|
logger.Error("Hello error")
|
||||||
|
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal("Hello error", hook.LastEntry().Message)
|
||||||
|
assert.Equal(1, len(hook.Entries))
|
||||||
|
|
||||||
|
logger.Warn("Hello warning")
|
||||||
|
assert.Equal(logrus.WarnLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal("Hello warning", hook.LastEntry().Message)
|
||||||
|
assert.Equal(2, len(hook.Entries))
|
||||||
|
|
||||||
|
hook.Reset()
|
||||||
|
assert.Nil(hook.LastEntry())
|
||||||
|
assert.Equal(0, len(hook.Entries))
|
||||||
|
|
||||||
|
hook = NewGlobal()
|
||||||
|
|
||||||
|
logrus.Error("Hello error")
|
||||||
|
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal("Hello error", hook.LastEntry().Message)
|
||||||
|
assert.Equal(1, len(hook.Entries))
|
||||||
|
|
||||||
|
}
|
74
gateway/vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
74
gateway/vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fieldKey string
|
||||||
|
type FieldMap map[fieldKey]string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FieldKeyMsg = "msg"
|
||||||
|
FieldKeyLevel = "level"
|
||||||
|
FieldKeyTime = "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f FieldMap) resolve(key fieldKey) string {
|
||||||
|
if k, ok := f[key]; ok {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONFormatter struct {
|
||||||
|
// TimestampFormat sets the format used for marshaling timestamps.
|
||||||
|
TimestampFormat string
|
||||||
|
|
||||||
|
// DisableTimestamp allows disabling automatic timestamps in output
|
||||||
|
DisableTimestamp bool
|
||||||
|
|
||||||
|
// FieldMap allows users to customize the names of keys for various fields.
|
||||||
|
// As an example:
|
||||||
|
// formatter := &JSONFormatter{
|
||||||
|
// FieldMap: FieldMap{
|
||||||
|
// FieldKeyTime: "@timestamp",
|
||||||
|
// FieldKeyLevel: "@level",
|
||||||
|
// FieldKeyLevel: "@message",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
FieldMap FieldMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
data := make(Fields, len(entry.Data)+3)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefixFieldClashes(data)
|
||||||
|
|
||||||
|
timestampFormat := f.TimestampFormat
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = DefaultTimestampFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
199
gateway/vendor/github.com/Sirupsen/logrus/json_formatter_test.go
generated
vendored
Normal file
199
gateway/vendor/github.com/Sirupsen/logrus/json_formatter_test.go
generated
vendored
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestErrorNotLost(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("error", errors.New("wild walrus")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(b, &entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["error"] != "wild walrus" {
|
||||||
|
t.Fatal("Error field not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("omg", errors.New("wild walrus")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(b, &entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["omg"] != "wild walrus" {
|
||||||
|
t.Fatal("Error field not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFieldClashWithTime(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("time", "right now!"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(b, &entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["fields.time"] != "right now!" {
|
||||||
|
t.Fatal("fields.time not set to original time field")
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["time"] != "0001-01-01T00:00:00Z" {
|
||||||
|
t.Fatal("time field not set to current time, was: ", entry["time"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFieldClashWithMsg(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("msg", "something"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(b, &entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["fields.msg"] != "something" {
|
||||||
|
t.Fatal("fields.msg not set to original msg field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFieldClashWithLevel(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("level", "something"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(b, &entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry["fields.level"] != "something" {
|
||||||
|
t.Fatal("fields.level not set to original level field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONEntryEndsWithNewline(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("level", "something"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[len(b)-1] != '\n' {
|
||||||
|
t.Fatal("Expected JSON log entry to end with a newline")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONMessageKey(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{
|
||||||
|
FieldMap: FieldMap{
|
||||||
|
FieldKeyMsg: "message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := formatter.Format(&Entry{Message: "oh hai"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if !(strings.Contains(s, "message") && strings.Contains(s, "oh hai")) {
|
||||||
|
t.Fatal("Expected JSON to format message key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONLevelKey(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{
|
||||||
|
FieldMap: FieldMap{
|
||||||
|
FieldKeyLevel: "somelevel",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("level", "something"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if !strings.Contains(s, "somelevel") {
|
||||||
|
t.Fatal("Expected JSON to format level key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONTimeKey(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{
|
||||||
|
FieldMap: FieldMap{
|
||||||
|
FieldKeyTime: "timeywimey",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("level", "something"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if !strings.Contains(s, "timeywimey") {
|
||||||
|
t.Fatal("Expected JSON to format time key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONDisableTimestamp(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{
|
||||||
|
DisableTimestamp: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("level", "something"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if strings.Contains(s, FieldKeyTime) {
|
||||||
|
t.Error("Did not prevent timestamp", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONEnableTimestamp(t *testing.T) {
|
||||||
|
formatter := &JSONFormatter{}
|
||||||
|
|
||||||
|
b, err := formatter.Format(WithField("level", "something"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to format entry: ", err)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if !strings.Contains(s, FieldKeyTime) {
|
||||||
|
t.Error("Timestamp not present", s)
|
||||||
|
}
|
||||||
|
}
|
308
gateway/vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
308
gateway/vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 adventorous, 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
|
||||||
|
// 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. `logrus.Debug` is useful in
|
||||||
|
Level Level
|
||||||
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
|
mu MutexWrap
|
||||||
|
// Reusable empty entry
|
||||||
|
entryPool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = &Logger{
|
||||||
|
// Out: os.Stderr,
|
||||||
|
// Formatter: new(JSONFormatter),
|
||||||
|
// Hooks: make(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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) newEntry() *Entry {
|
||||||
|
entry, ok := logger.entryPool.Get().(*Entry)
|
||||||
|
if ok {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
return NewEntry(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||||
|
logger.entryPool.Put(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a field to the log entry, note that it doesn't log until you call
|
||||||
|
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= DebugLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Debugf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= InfoLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Infof(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= ErrorLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Errorf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= FatalLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Fatalf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
|
if logger.Level >= PanicLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Panicf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
|
if logger.Level >= DebugLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Debug(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
|
if logger.Level >= InfoLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Info(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Print(args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Info(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warn(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warn(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
|
if logger.Level >= ErrorLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Error(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
|
if logger.Level >= FatalLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Fatal(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
|
if logger.Level >= PanicLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Panic(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
|
if logger.Level >= DebugLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Debugln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
|
if logger.Level >= InfoLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Infoln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Println(args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Println(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warnln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
|
if logger.Level >= WarnLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Warnln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
|
if logger.Level >= ErrorLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Errorln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
|
if logger.Level >= FatalLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Fatalln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
|
if logger.Level >= PanicLevel {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Panicln(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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()
|
||||||
|
}
|
61
gateway/vendor/github.com/Sirupsen/logrus/logger_bench_test.go
generated
vendored
Normal file
61
gateway/vendor/github.com/Sirupsen/logrus/logger_bench_test.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// smallFields is a small size data set for benchmarking
|
||||||
|
var loggerFields = Fields{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
"one": "two",
|
||||||
|
"three": "four",
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDummyLogger(b *testing.B) {
|
||||||
|
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY, 0666)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
defer nullf.Close()
|
||||||
|
doLoggerBenchmark(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDummyLoggerNoLock(b *testing.B) {
|
||||||
|
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
defer nullf.Close()
|
||||||
|
doLoggerBenchmarkNoLock(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doLoggerBenchmark(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
|
||||||
|
logger := Logger{
|
||||||
|
Out: out,
|
||||||
|
Level: InfoLevel,
|
||||||
|
Formatter: formatter,
|
||||||
|
}
|
||||||
|
entry := logger.WithFields(fields)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
entry.Info("aaa")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doLoggerBenchmarkNoLock(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
|
||||||
|
logger := Logger{
|
||||||
|
Out: out,
|
||||||
|
Level: InfoLevel,
|
||||||
|
Formatter: formatter,
|
||||||
|
}
|
||||||
|
logger.SetNoLock()
|
||||||
|
entry := logger.WithFields(fields)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
entry.Info("aaa")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
143
gateway/vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
143
gateway/vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fields type, used to pass to `WithFields`.
|
||||||
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
|
// Level type
|
||||||
|
type Level uint8
|
||||||
|
|
||||||
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
|
func (level Level) String() string {
|
||||||
|
switch level {
|
||||||
|
case DebugLevel:
|
||||||
|
return "debug"
|
||||||
|
case InfoLevel:
|
||||||
|
return "info"
|
||||||
|
case WarnLevel:
|
||||||
|
return "warning"
|
||||||
|
case ErrorLevel:
|
||||||
|
return "error"
|
||||||
|
case FatalLevel:
|
||||||
|
return "fatal"
|
||||||
|
case PanicLevel:
|
||||||
|
return "panic"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var l Level
|
||||||
|
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A constant exposing all logging levels
|
||||||
|
var AllLevels = []Level{
|
||||||
|
PanicLevel,
|
||||||
|
FatalLevel,
|
||||||
|
ErrorLevel,
|
||||||
|
WarnLevel,
|
||||||
|
InfoLevel,
|
||||||
|
DebugLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 `os.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
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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{})
|
||||||
|
}
|
386
gateway/vendor/github.com/Sirupsen/logrus/logrus_test.go
generated
vendored
Normal file
386
gateway/vendor/github.com/Sirupsen/logrus/logrus_test.go
generated
vendored
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var fields Fields
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = new(JSONFormatter)
|
||||||
|
|
||||||
|
log(logger)
|
||||||
|
|
||||||
|
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assertions(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = &TextFormatter{
|
||||||
|
DisableColors: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
log(logger)
|
||||||
|
|
||||||
|
fields := make(map[string]string)
|
||||||
|
for _, kv := range strings.Split(buffer.String(), " ") {
|
||||||
|
if !strings.Contains(kv, "=") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kvArr := strings.Split(kv, "=")
|
||||||
|
key := strings.TrimSpace(kvArr[0])
|
||||||
|
val := kvArr[1]
|
||||||
|
if kvArr[1][0] == '"' {
|
||||||
|
var err error
|
||||||
|
val, err = strconv.Unquote(val)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
fields[key] = val
|
||||||
|
}
|
||||||
|
assertions(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrint(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarn(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Warn("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "warning")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln("test", "test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test test")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln("test", 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln(10, 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "10 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln(10, 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "10 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test", 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test", "test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "testtest")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var fields Fields
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = new(JSONFormatter)
|
||||||
|
|
||||||
|
localLog := logger.WithFields(Fields{
|
||||||
|
"key1": "value1",
|
||||||
|
})
|
||||||
|
|
||||||
|
localLog.WithField("key2", "value2").Info("test")
|
||||||
|
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "value2", fields["key2"])
|
||||||
|
assert.Equal(t, "value1", fields["key1"])
|
||||||
|
|
||||||
|
buffer = bytes.Buffer{}
|
||||||
|
fields = Fields{}
|
||||||
|
localLog.Info("test")
|
||||||
|
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, ok := fields["key2"]
|
||||||
|
assert.Equal(t, false, ok)
|
||||||
|
assert.Equal(t, "value1", fields["key1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("msg", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("msg", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["fields.msg"], "hello")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("time", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["fields.time"], "hello")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("level", 1).Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
assert.Equal(t, fields["fields.level"], 1.0) // JSON has floats only
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
|
||||||
|
LogAndAssertText(t, func(log *Logger) {
|
||||||
|
ll := log.WithField("herp", "derp")
|
||||||
|
ll.Info("hello")
|
||||||
|
ll.Info("bye")
|
||||||
|
}, func(fields map[string]string) {
|
||||||
|
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
|
||||||
|
if _, ok := fields[fieldName]; ok {
|
||||||
|
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var fields Fields
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = new(JSONFormatter)
|
||||||
|
|
||||||
|
llog := logger.WithField("context", "eating raw fish")
|
||||||
|
|
||||||
|
llog.Info("looks delicious")
|
||||||
|
|
||||||
|
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.NoError(t, err, "should have decoded first message")
|
||||||
|
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
|
||||||
|
assert.Equal(t, fields["msg"], "looks delicious")
|
||||||
|
assert.Equal(t, fields["context"], "eating raw fish")
|
||||||
|
|
||||||
|
buffer.Reset()
|
||||||
|
|
||||||
|
llog.Warn("omg it is!")
|
||||||
|
|
||||||
|
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.NoError(t, err, "should have decoded second message")
|
||||||
|
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
|
||||||
|
assert.Equal(t, fields["msg"], "omg it is!")
|
||||||
|
assert.Equal(t, fields["context"], "eating raw fish")
|
||||||
|
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertLevelToString(t *testing.T) {
|
||||||
|
assert.Equal(t, "debug", DebugLevel.String())
|
||||||
|
assert.Equal(t, "info", InfoLevel.String())
|
||||||
|
assert.Equal(t, "warning", WarnLevel.String())
|
||||||
|
assert.Equal(t, "error", ErrorLevel.String())
|
||||||
|
assert.Equal(t, "fatal", FatalLevel.String())
|
||||||
|
assert.Equal(t, "panic", PanicLevel.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLevel(t *testing.T) {
|
||||||
|
l, err := ParseLevel("panic")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, PanicLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("PANIC")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, PanicLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("fatal")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, FatalLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("FATAL")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, FatalLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("error")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, ErrorLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("ERROR")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, ErrorLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("warn")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, WarnLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("WARN")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, WarnLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("warning")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, WarnLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("WARNING")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, WarnLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("info")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, InfoLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("INFO")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, InfoLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("debug")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, DebugLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("DEBUG")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, DebugLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("invalid")
|
||||||
|
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSetLevelRace(t *testing.T) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
if i%2 == 0 {
|
||||||
|
SetLevel(InfoLevel)
|
||||||
|
} else {
|
||||||
|
GetLevel()
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggingRace(t *testing.T) {
|
||||||
|
logger := New()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(100)
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
go func() {
|
||||||
|
logger.Info("info")
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile test
|
||||||
|
func TestLogrusInterface(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
fn := func(l FieldLogger) {
|
||||||
|
b := l.WithField("key", "value")
|
||||||
|
b.Debug("Test")
|
||||||
|
}
|
||||||
|
// test logger
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
fn(logger)
|
||||||
|
|
||||||
|
// test Entry
|
||||||
|
e := logger.WithField("another", "value")
|
||||||
|
fn(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements io.Writer using channels for synchronization, so we can wait on
|
||||||
|
// the Entry.Writer goroutine to write in a non-racey way. This does assume that
|
||||||
|
// there is a single call to Logger.Out for each message.
|
||||||
|
type channelWriter chan []byte
|
||||||
|
|
||||||
|
func (cw channelWriter) Write(p []byte) (int, error) {
|
||||||
|
cw <- p
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryWriter(t *testing.T) {
|
||||||
|
cw := channelWriter(make(chan []byte, 1))
|
||||||
|
log := New()
|
||||||
|
log.Out = cw
|
||||||
|
log.Formatter = new(JSONFormatter)
|
||||||
|
log.WithField("foo", "bar").WriterLevel(WarnLevel).Write([]byte("hello\n"))
|
||||||
|
|
||||||
|
bs := <-cw
|
||||||
|
var fields Fields
|
||||||
|
err := json.Unmarshal(bs, &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, fields["foo"], "bar")
|
||||||
|
assert.Equal(t, fields["level"], "warning")
|
||||||
|
}
|
10
gateway/vendor/github.com/Sirupsen/logrus/terminal_appengine.go
generated
vendored
Normal file
10
gateway/vendor/github.com/Sirupsen/logrus/terminal_appengine.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||||
|
func IsTerminal(f io.Writer) bool {
|
||||||
|
return true
|
||||||
|
}
|
10
gateway/vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
Normal file
10
gateway/vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// +build darwin freebsd openbsd netbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
14
gateway/vendor/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
Normal file
14
gateway/vendor/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TCGETS
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
28
gateway/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
28
gateway/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux darwin freebsd openbsd netbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||||
|
func IsTerminal(f io.Writer) bool {
|
||||||
|
var termios Termios
|
||||||
|
switch v := f.(type) {
|
||||||
|
case *os.File:
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user