Compare commits

..

15 Commits

Author SHA1 Message Date
66e016c6dc swarm: release v0.4.3 (#1602)
* swarm: 0.4.3 release notes

* swarm: 0.4.3 stable version

* swarm: 0.4.3 release notes - fix
2019-07-25 13:08:29 +02:00
74b12e35bf network/retrieve: add bzz-retrieve protocol (#1589)
* network/retrieve: initial commit

* network: import dependencies from stream package branch

* network/retrieve: address pr comments

* network/retrieve: fix pr comments

* network/retrieve: create logger for peer

* network: address pr comments

* network/retrieve: lint

* network/retrieve: prevent forever loop

* network/retrieve: address pr comments

* network/retrieve: fix linter

* network/retrieve: pr comments

* network/retrieval: pr comments
2019-07-25 10:53:46 +02:00
388d8ccd9f PoC: Network simulation framework (#1555)
* simv2: wip

* simulation: exec adapter start/stop

* simulation: add node status to exec adapter

* simulation: initial simulation code

* simulation: exec adapter, configure path to executable

* simulation: initial docker adapter

* simulation: wip kubernetes adapter

* simulation: kubernetes adapter proxy

* simulation: implement GetAll/StartAll/StopAll

* simulation: kuberentes adapter - set env vars and resource limits

* simulation: discovery test

* simulation: remove port definitions within docker adapter

* simulation: simplify wait for healthy loop

* simulation: get nat ip addr from interface

* simulation: pull docker images automatically

* simulation: NodeStatus -> NodeInfo

* simulation: move discovery test to example dir

* simulation: example snapshot usage

* simulation: add goclient specific simulation

* simulation: add peer connections to snapshot

* simulation: close rpc client

* simulation: don't export kubernetes proxy server

* simulation: merge simulation code

* simulation: don't export nodemap

* simulation: rename SimulationSnapshot -> Snapshot

* simulation: linting fixes

* simulation: add k8s available helper func

* simulation: vendor

* simulation: fix 'no non-test Go files' when building

* simulation: remove errors from interface methods where non were returned

* simulation: run getHealthInfo check in parallel
2019-07-24 17:00:13 +02:00
9720da34db network: structured output for kademlia table (#1586) 2019-07-19 13:35:11 +02:00
090197227f client: add bzz client, update smoke tests (#1582) 2019-07-18 18:49:20 +02:00
48857ae88e swarm-smoke: fix check max prox hosts for pull/push sync modes (#1578) 2019-07-17 16:21:50 +02:00
ad77f43b9d cmd/swarm: allow using a network interface by name for nat purposes (#1557) 2019-07-16 09:35:04 +02:00
9d96d9bef5 pss: disable TestForwardBasic (#1544) 2019-07-11 18:17:26 +02:00
7101f65a8b api, network: count chunk deliveries per peer (#1534) 2019-07-09 22:50:06 +02:00
af3b5e9ce1 network/newstream: new stream! protocol base implementation (#1500)
network/newstream: merge new stream protocol wire protocol changes, changes to docs, add basic protocol handlers and placeholders
2019-07-08 13:25:56 +03:00
7deac5693c swarm: fix bzz_info.port when using dynamic port allocation (#1537) 2019-07-05 12:15:17 +02:00
d5f6ee4620 cmd/swarm: make bzzaccount flag optional and add bzzkeyhex flag (#1531)
* cmd/swarm: don't require bzzaccount flag

* docker: remove setup script because bzzaccount is not required anymore

* cmd/swarm: manage account creation/selection when bzzflag is not defined

* cmd/swarm: no bzzaccount flag tests

* cmd/swarm: use random network ports on tests

* cmd/swarm: fix typo

* cmd/swarm: add --bzzkeyhex flag

* cmd/swarm: use different ipc paths for tests

* readme: update example on how to run swarm

* cmd/swarm: rename getAccount -> getOrCreateAccount

* cmd/swarm: remove unneeded comment

* cmd/swarm: use shorter ipc path for test
2019-07-04 13:14:10 +02:00
fb73e6cb9b cmd/swarm: remove separate function to parse env vars (#1536) 2019-07-04 13:13:39 +02:00
a6e64aea67 network/bitvector: Multibit set/unset + string rep (#1530)
* network/bitvector: Multibit set/unset + string rep

* network/bitvector: Add code comments

* network/bitvector: Make Unset -> Set with bool false

* network/bitvector: Revert to Set/Unset

* network/stream: Update to new bitvector signature
2019-07-02 18:08:00 +02:00
b3f21ddbba swarm: 0.4.3 unstable (#1526)
* swarm: 0.4.3 unstable

* CHANGELOG: consistent date format
2019-06-28 12:22:07 +02:00
1607 changed files with 463192 additions and 3601 deletions

View File

@ -1,20 +1,36 @@
## v0.4.3 (Unreleased)
## v0.4.3 (July 25, 2019)
### Notes
### Features
- **Docker users:** The `$PASSWORD` and `$DATADIR` environment variables are not supported anymore since this release. From now on you should mount the password or data dirctories as a volume. For example:
```bash
$ docker run -it -v $PWD/hostdata:/data \
-v $PWD/password:/password \
ethersphere/swarm:0.4.3 \
--datadir /data \
--password /password
```
### Improvements
### Bug fixes and improvements
### Bug fixes
* [#1586](https://github.com/ethersphere/swarm/pull/1586): network: structured output for kademlia table
* [#1582](https://github.com/ethersphere/swarm/pull/1582): client: add bzz client, update smoke tests
* [#1578](https://github.com/ethersphere/swarm/pull/1578): swarm-smoke: fix check max prox hosts for pull/push sync modes
* [#1557](https://github.com/ethersphere/swarm/pull/1557): cmd/swarm: allow using a network interface by name for nat purposes
* [#1534](https://github.com/ethersphere/swarm/pull/1534): api, network: count chunk deliveries per peer
* [#1537](https://github.com/ethersphere/swarm/pull/1537): swarm: fix bzz_info.port when using dynamic port allocation
* [#1531](https://github.com/ethersphere/swarm/pull/1531): cmd/swarm: make bzzaccount flag optional and add bzzkeyhex flag
* [#1536](https://github.com/ethersphere/swarm/pull/1536): cmd/swarm: use only one function to parse flags
* [#1530](https://github.com/ethersphere/swarm/pull/1530): network/bitvector: Multibit set/unset + string rep
* [#1555](https://github.com/ethersphere/swarm/pull/1555): PoC: Network simulation framework
## v0.4.2 (28 June 2019)
## v0.4.2 (June 28, 2019)
### Notes
This release is not backward compatible with the previous versions of Swarm due to changes to the wire protocol of the Retrieve Request messages. Please update your nodes.
### Bug fixes and Improvements
### Bug fixes and improvements
* [#1503](https://github.com/ethersphere/swarm/pull/1503): network/simulation: add ExecAdapter capability to swarm simulations
* [#1495](https://github.com/ethersphere/swarm/pull/1495): build: enable ubuntu ppa disco (19.04) builds

View File

@ -10,5 +10,4 @@ FROM alpine:3.9
RUN apk --no-cache add ca-certificates && update-ca-certificates
COPY --from=builder /swarm/build/bin/swarm /usr/local/bin/
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
COPY docker/run.sh /run.sh
ENTRYPOINT ["/run.sh"]
ENTRYPOINT ["/usr/local/bin/swarm"]

View File

@ -10,6 +10,5 @@ FROM alpine:3.9
RUN apk --no-cache add ca-certificates
COPY --from=builder /swarm/build/bin/* /usr/local/bin/
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
COPY docker/run.sh /run.sh
COPY docker/run-smoke.sh /run-smoke.sh
ENTRYPOINT ["/run.sh"]
ENTRYPOINT ["/usr/local/bin/swarm"]

View File

@ -9,26 +9,25 @@ Swarm is a distributed storage platform and content distribution service, a nati
## Table of Contents <!-- omit in toc -->
- [Building the source](#building-the-source)
- [Running Swarm](#running-swarm)
- [Verifying that your local Swarm node is running](#verifying-that-your-local-swarm-node-is-running)
- [Ethereum Name Service resolution](#ethereum-name-service-resolution)
- [Documentation](#documentation)
- [Docker](#docker)
- [Docker tags](#docker-tags)
- [Environment variables](#environment-variables)
- [Swarm command line arguments](#swarm-command-line-arguments)
- [Developers Guide](#developers-guide)
- [Go Environment](#go-environment)
- [Vendored Dependencies](#vendored-dependencies)
- [Testing](#testing)
- [Profiling Swarm](#profiling-swarm)
- [Metrics and Instrumentation in Swarm](#metrics-and-instrumentation-in-swarm)
- [Visualizing metrics](#visualizing-metrics)
- [Public Gateways](#public-gateways)
- [Swarm Dapps](#swarm-dapps)
- [Contributing](#contributing)
- [License](#license)
- [Building the source](#Building-the-source)
- [Running Swarm](#Running-Swarm)
- [Verifying that your local Swarm node is running](#Verifying-that-your-local-Swarm-node-is-running)
- [Ethereum Name Service resolution](#Ethereum-Name-Service-resolution)
- [Documentation](#Documentation)
- [Docker](#Docker)
- [Docker tags](#Docker-tags)
- [Swarm command line arguments](#Swarm-command-line-arguments)
- [Developers Guide](#Developers-Guide)
- [Go Environment](#Go-Environment)
- [Vendored Dependencies](#Vendored-Dependencies)
- [Testing](#Testing)
- [Profiling Swarm](#Profiling-Swarm)
- [Metrics and Instrumentation in Swarm](#Metrics-and-Instrumentation-in-Swarm)
- [Visualizing metrics](#Visualizing-metrics)
- [Public Gateways](#Public-Gateways)
- [Swarm Dapps](#Swarm-Dapps)
- [Contributing](#Contributing)
- [License](#License)
## Building the source
@ -53,15 +52,11 @@ $ go install github.com/ethersphere/swarm/cmd/swarm
## Running Swarm
Going through all the possible command line flags is out of scope here, but we've enumerated a few common parameter combos to get you up to speed quickly on how you can run your own Swarm node.
To run Swarm you need an Ethereum account. Download and install [Geth](https://geth.ethereum.org) if you don't have it on your system. You can create a new Ethereum account by running the following command:
```bash
$ geth account new
$ swarm
```
You will be prompted for a password:
If you don't have an account yet, then you will be prompted to create one and secure it with a password:
```
Your new account is locked with a password. Please give a password. Do not forget this password.
@ -69,18 +64,12 @@ Passphrase:
Repeat passphrase:
```
Once you have specified the password, the output will be the Ethereum address representing that account. For example:
```
Address: {2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1}
```
Using this account, connect to Swarm with
If you have multiple accounts created, then you'll have to choose one of the accounts by using the `--bzzaccount` flag.
```bash
$ swarm --bzzaccount <your-account-here>
# in our example
# example
$ swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1
```
@ -119,11 +108,6 @@ Swarm container images are available at Docker Hub: [ethersphere/swarm](https://
* `edge` - latest build from `master`
* `v0.x.y` - specific stable release
### Environment variables
* `PASSWORD` - *required* - Used to setup a sample Ethereum account in the data directory. If a data directory is mounted with a volume, the first Ethereum account from it is loaded, and Swarm will try to decrypt it non-interactively with `PASSWORD`
* `DATADIR` - *optional* - Defaults to `/root/.ethereum`
### Swarm command line arguments
All Swarm command line arguments are supported and can be sent as part of the CMD field to the Docker container.
@ -133,15 +117,16 @@ All Swarm command line arguments are supported and can be sent as part of the CM
Running a Swarm container from the command line
```bash
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
$ docker run -it ethersphere/swarm \
--debug \
--verbosity 4
```
Running a Swarm container with custom ENS endpoint
```bash
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
$ docker run -it ethersphere/swarm \
--ens-api http://1.2.3.4:8545 \
--debug \
--verbosity 4
@ -150,7 +135,7 @@ $ docker run -e PASSWORD=password123 -t ethersphere/swarm \
Running a Swarm container with metrics enabled
```bash
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
$ docker run -it ethersphere/swarm \
--debug \
--metrics \
--metrics.influxdb.export \
@ -165,7 +150,7 @@ $ docker run -e PASSWORD=password123 -t ethersphere/swarm \
Running a Swarm container with tracing and pprof server enabled
```bash
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
$ docker run -it ethersphere/swarm \
--debug \
--tracing \
--tracing.endpoint 127.0.0.1:6831 \
@ -175,10 +160,14 @@ $ docker run -e PASSWORD=password123 -t ethersphere/swarm \
--pprofport 6060
```
Running a Swarm container with custom data directory mounted from a volume
Running a Swarm container with a custom data directory mounted from a volume and a password file to unlock the swarm account
```bash
$ docker run -e DATADIR=/data -e PASSWORD=password123 -v /tmp/hostdata:/data -t ethersphere/swarm \
$ docker run -it -v $PWD/hostdata:/data \
-v $PWD/password:/password \
ethersphere/swarm \
--datadir /data \
--password /password \
--debug \
--verbosity 4
```

View File

@ -39,19 +39,16 @@ func NewInspector(api *API, hive *network.Hive, netStore *storage.NetStore) *Ins
}
// Hive prints the kademlia table
func (inspector *Inspector) Hive() string {
return inspector.hive.String()
func (i *Inspector) Hive() string {
return i.hive.String()
}
func (inspector *Inspector) ListKnown() []string {
res := []string{}
for _, v := range inspector.hive.Kademlia.ListKnown() {
res = append(res, fmt.Sprintf("%v", v))
}
return res
// KademliaInfo returns structured output of the Kademlia state that we can check for equality
func (i *Inspector) KademliaInfo() network.KademliaInfo {
return i.hive.KademliaInfo()
}
func (inspector *Inspector) IsSyncing() bool {
func (i *Inspector) IsPullSyncing() bool {
lastReceivedChunksMsg := metrics.GetOrRegisterGauge("network.stream.received_chunks", nil)
// last received chunks msg time
@ -63,13 +60,30 @@ func (inspector *Inspector) IsSyncing() bool {
return lrct.After(time.Now().Add(-15 * time.Second))
}
// DeliveriesPerPeer returns the sum of chunks we received from a given peer
func (i *Inspector) DeliveriesPerPeer() map[string]int64 {
res := map[string]int64{}
// iterate connection in kademlia
i.hive.Kademlia.EachConn(nil, 255, func(p *network.Peer, po int) bool {
// get how many chunks we receive for retrieve requests per peer
peermetric := fmt.Sprintf("chunk.delivery.%x", p.Over()[:16])
res[fmt.Sprintf("%x", p.Over()[:16])] = metrics.GetOrRegisterCounter(peermetric, nil).Count()
return true
})
return res
}
// Has checks whether each chunk address is present in the underlying datastore,
// the bool in the returned structs indicates if the underlying datastore has
// the chunk stored with the given address (true), or not (false)
func (inspector *Inspector) Has(chunkAddresses []storage.Address) string {
func (i *Inspector) Has(chunkAddresses []storage.Address) string {
hostChunks := []string{}
for _, addr := range chunkAddresses {
has, err := inspector.netStore.Has(context.Background(), addr)
has, err := i.netStore.Has(context.Background(), addr)
if err != nil {
log.Error(err.Error())
}

81
client/bzz.go Normal file
View File

@ -0,0 +1,81 @@
package client
import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm"
"github.com/ethersphere/swarm/log"
"github.com/ethersphere/swarm/storage"
)
type Bzz struct {
client *rpc.Client
}
// NewBzz is a constructor for a Bzz API
func NewBzz(client *rpc.Client) *Bzz {
return &Bzz{
client: client,
}
}
// GetChunksBitVector returns a bit vector of presence for a given slice of chunks
func (b *Bzz) GetChunksBitVector(addrs []storage.Address) (string, error) {
var hostChunks string
const trackChunksPageSize = 7500
for len(addrs) > 0 {
var pageChunks string
// get current page size, so that we avoid a slice out of bounds on the last page
pagesize := trackChunksPageSize
if len(addrs) < trackChunksPageSize {
pagesize = len(addrs)
}
err := b.client.Call(&pageChunks, "bzz_has", addrs[:pagesize])
if err != nil {
return "", err
}
hostChunks += pageChunks
addrs = addrs[pagesize:]
}
return hostChunks, nil
}
// GetBzzAddr returns the bzzAddr of the node
func (b *Bzz) GetBzzAddr() (string, error) {
var info swarm.Info
err := b.client.Call(&info, "bzz_info")
if err != nil {
return "", err
}
return info.BzzKey[2:], nil
}
// IsPullSyncing is checking if the node is still receiving chunk deliveries due to pull syncing
func (b *Bzz) IsPullSyncing() (bool, error) {
var isSyncing bool
err := b.client.Call(&isSyncing, "bzz_isPullSyncing")
if err != nil {
log.Error("error calling host for isPullSyncing", "err", err)
return false, err
}
return isSyncing, nil
}
// IsPushSynced checks if the given `tag` is done syncing, i.e. we've received receipts for all chunks
func (b *Bzz) IsPushSynced(tagname string) (bool, error) {
var isSynced bool
err := b.client.Call(&isSynced, "bzz_isPushSynced", tagname)
if err != nil {
log.Error("error calling host for isPushSynced", "err", err)
return false, err
}
return isSynced, nil
}

View File

@ -37,22 +37,23 @@ var (
)
var (
allhosts string
hosts []string
filesize int
syncDelay bool
inputSeed int
httpPort int
wsPort int
verbosity int
timeout int
single bool
onlyUpload bool
debug bool
allhosts string
hosts []string
filesize int
syncDelay bool
pushsyncDelay bool
syncMode string
inputSeed int
httpPort int
wsPort int
verbosity int
timeout int
single bool
onlyUpload bool
debug bool
)
func main() {
app := cli.NewApp()
app.Name = "smoke-test"
app.Usage = ""
@ -88,6 +89,17 @@ func main() {
Usage: "file size for generated random file in KB",
Destination: &filesize,
},
cli.StringFlag{
Name: "sync-mode",
Value: "pullsync",
Usage: "sync mode - pushsync or pullsync or both",
Destination: &syncMode,
},
cli.BoolFlag{
Name: "pushsync-delay",
Usage: "wait for content to be push synced",
Destination: &pushsyncDelay,
},
cli.BoolFlag{
Name: "sync-delay",
Usage: "wait for content to be synced",

View File

@ -24,7 +24,6 @@ import (
"io/ioutil"
"math/rand"
"os"
"strings"
"sync"
"sync/atomic"
"time"
@ -33,8 +32,11 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm/chunk"
"github.com/ethersphere/swarm/client"
"github.com/ethersphere/swarm/storage"
"github.com/ethersphere/swarm/testutil"
"github.com/pborman/uuid"
"golang.org/x/sync/errgroup"
cli "gopkg.in/urfave/cli.v1"
)
@ -116,14 +118,16 @@ func trackChunks(testData []byte, submitMetrics bool) error {
return
}
hostChunks, err := getChunksBitVectorFromHost(rpcClient, addrs)
bzzClient := client.NewBzz(rpcClient)
hostChunks, err := bzzClient.GetChunksBitVector(addrs)
if err != nil {
log.Error("error getting chunks bit vector from host", "err", err, "host", httpHost)
hasErr = true
return
}
bzzAddr, err := getBzzAddrFromHost(rpcClient)
bzzAddr, err := bzzClient.GetBzzAddr()
if err != nil {
log.Error("error getting bzz addrs from host", "err", err, "host", httpHost)
hasErr = true
@ -175,46 +179,6 @@ func trackChunks(testData []byte, submitMetrics bool) error {
return nil
}
// getChunksBitVectorFromHost returns a bit vector of presence for a given slice of chunks from a given host
func getChunksBitVectorFromHost(client *rpc.Client, addrs []storage.Address) (string, error) {
var hostChunks string
const trackChunksPageSize = 7500
for len(addrs) > 0 {
var pageChunks string
// get current page size, so that we avoid a slice out of bounds on the last page
pagesize := trackChunksPageSize
if len(addrs) < trackChunksPageSize {
pagesize = len(addrs)
}
err := client.Call(&pageChunks, "bzz_has", addrs[:pagesize])
if err != nil {
return "", err
}
hostChunks += pageChunks
addrs = addrs[pagesize:]
}
return hostChunks, nil
}
// getBzzAddrFromHost returns the bzzAddr for a given host
func getBzzAddrFromHost(client *rpc.Client) (string, error) {
var hive string
err := client.Call(&hive, "bzz_hive")
if err != nil {
return "", err
}
// we make an ugly assumption about the output format of the hive.String() method
// ideally we should replace this with an API call that returns the bzz addr for a given host,
// but this also works for now (provided we don't change the hive.String() method, which we haven't in some time
ss := strings.Split(strings.Split(hive, "\n")[3], " ")
return ss[len(ss)-1], nil
}
// checkChunksVsMostProxHosts is checking:
// 1. whether a chunk has been found at less than 2 hosts. Considering our NN size, this should not happen.
// 2. if a chunk is not found at its closest node. This should also not happen.
@ -232,7 +196,7 @@ func checkChunksVsMostProxHosts(addrs []storage.Address, allHostChunks map[strin
for i := range addrs {
var foundAt int
maxProx := -1
var maxProxHost string
var maxProxHosts []string
for host := range allHostChunks {
if allHostChunks[host][i] == '1' {
foundAt++
@ -247,19 +211,43 @@ func checkChunksVsMostProxHosts(addrs []storage.Address, allHostChunks map[strin
prox := chunk.Proximity(addrs[i], ba)
if prox > maxProx {
maxProx = prox
maxProxHost = host
maxProxHosts = []string{host}
} else if prox == maxProx {
maxProxHosts = append(maxProxHosts, host)
}
}
if allHostChunks[maxProxHost][i] == '0' {
log.Error("chunk not found at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
} else {
log.Trace("chunk present at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
log.Debug("sync mode", "sync mode", syncMode)
if syncMode == "pullsync" || syncMode == "both" {
for _, maxProxHost := range maxProxHosts {
if allHostChunks[maxProxHost][i] == '0' {
log.Error("chunk not found at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
} else {
log.Trace("chunk present at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
}
}
// if chunk found at less than 2 hosts, which is actually less that the min size of a NN
if foundAt < 2 {
log.Error("chunk found at less than two hosts", "foundAt", foundAt, "ref", addrs[i])
}
}
// if chunk found at less than 2 hosts
if foundAt < 2 {
log.Error("chunk found at less than two hosts", "foundAt", foundAt, "ref", addrs[i])
if syncMode == "pushsync" {
var found bool
for _, maxProxHost := range maxProxHosts {
if allHostChunks[maxProxHost][i] == '1' {
found = true
log.Trace("chunk present at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
}
}
if !found {
for _, maxProxHost := range maxProxHosts {
log.Error("chunk not found at any max prox host", "ref", addrs[i], "hosts", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
}
}
}
}
}
@ -284,7 +272,8 @@ func uploadAndSync(c *cli.Context, randomBytes []byte) error {
log.Info("uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed)
t1 := time.Now()
hash, err := upload(randomBytes, httpEndpoint(hosts[0]))
tag := uuid.New()[:8]
hash, err := uploadWithTag(randomBytes, httpEndpoint(hosts[0]), tag)
if err != nil {
log.Error(err.Error())
return err
@ -300,6 +289,11 @@ func uploadAndSync(c *cli.Context, randomBytes []byte) error {
log.Info("uploaded successfully", "hash", hash, "took", t2, "digest", fmt.Sprintf("%x", fhash))
// wait to push sync sync
if pushsyncDelay {
waitToPushSynced(tag)
}
// wait to sync and log chunks before fetch attempt, only if syncDelay is set to true
if syncDelay {
waitToSync()
@ -338,27 +332,31 @@ func uploadAndSync(c *cli.Context, randomBytes []byte) error {
return nil
}
func isSyncing(wsHost string) (bool, error) {
rpcClient, err := rpc.Dial(wsHost)
if rpcClient != nil {
defer rpcClient.Close()
func waitToPushSynced(tagname string) {
for {
time.Sleep(200 * time.Millisecond)
rpcClient, err := rpc.Dial(wsEndpoint(hosts[0]))
if rpcClient != nil {
defer rpcClient.Close()
}
if err != nil {
log.Error("error dialing host", "err", err)
continue
}
bzzClient := client.NewBzz(rpcClient)
synced, err := bzzClient.IsPushSynced(tagname)
if err != nil {
log.Error(err.Error())
continue
}
if synced {
return
}
}
if err != nil {
log.Error("error dialing host", "err", err)
return false, err
}
var isSyncing bool
err = rpcClient.Call(&isSyncing, "bzz_isSyncing")
if err != nil {
log.Error("error calling host for isSyncing", "err", err)
return false, err
}
log.Debug("isSyncing result", "host", wsHost, "isSyncing", isSyncing)
return isSyncing, nil
}
func waitToSync() {
@ -370,22 +368,39 @@ func waitToSync() {
time.Sleep(3 * time.Second)
notSynced := uint64(0)
var wg sync.WaitGroup
wg.Add(len(hosts))
var g errgroup.Group
for i := 0; i < len(hosts); i++ {
i := i
go func(idx int) {
stillSyncing, err := isSyncing(wsEndpoint(hosts[idx]))
g.Go(func() error {
rpcClient, err := rpc.Dial(wsEndpoint(hosts[i]))
if rpcClient != nil {
defer rpcClient.Close()
}
if err != nil {
log.Error("error dialing host", "err", err)
return err
}
if stillSyncing || err != nil {
bzzClient := client.NewBzz(rpcClient)
stillSyncing, err := bzzClient.IsPullSyncing()
if err != nil {
return err
}
if stillSyncing {
atomic.AddUint64(&notSynced, 1)
}
wg.Done()
}(i)
}
wg.Wait()
ns = atomic.LoadUint64(&notSynced)
return nil
})
}
// Wait for all RPC calls to complete.
if err := g.Wait(); err == nil {
ns = atomic.LoadUint64(&notSynced)
}
}
t2 := time.Since(t1)

View File

@ -38,6 +38,7 @@ import (
"github.com/ethersphere/swarm/api/client"
"github.com/ethersphere/swarm/spancontext"
opentracing "github.com/opentracing/opentracing-go"
"github.com/pborman/uuid"
cli "gopkg.in/urfave/cli.v1"
)
@ -193,8 +194,13 @@ func fetch(hash string, endpoint string, original []byte, ruid string) error {
return nil
}
// upload an arbitrary byte as a plaintext file to `endpoint` using the api client
// upload an arbitrary byte as a plaintext file to `endpoint` using the api client
func upload(data []byte, endpoint string) (string, error) {
return uploadWithTag(data, endpoint, uuid.New()[:8])
}
// uploadWithTag an arbitrary byte as a plaintext file to `endpoint` using the api client with a given tag
func uploadWithTag(data []byte, endpoint string, tag string) (string, error) {
swarm := client.NewClient(endpoint)
f := &client.File{
ReadCloser: ioutil.NopCloser(bytes.NewReader(data)),
@ -203,10 +209,10 @@ func upload(data []byte, endpoint string) (string, error) {
Mode: 0660,
Size: int64(len(data)),
},
Tag: tag,
}
// upload data to bzz:// and retrieve the content-addressed manifest hash, hex-encoded.
return swarm.Upload(f, "", false)
return swarm.TarUpload("", &client.FileUploader{f}, "", false)
}
func digest(r io.Reader) ([]byte, error) {

View File

@ -106,7 +106,7 @@ func accessNewPass(ctx *cli.Context) {
accessKey []byte
err error
ref = args[0]
password = getPassPhrase("", 0, makePasswordList(ctx))
password = getPassPhrase("", false, 0, makePasswordList(ctx))
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
accessKey, ae, err = api.DoPassword(ctx, password, salt)

View File

@ -17,6 +17,7 @@
package main
import (
"crypto/ecdsa"
"errors"
"fmt"
"io"
@ -24,7 +25,6 @@ import (
"reflect"
"strconv"
"strings"
"time"
"unicode"
cli "gopkg.in/urfave/cli.v1"
@ -61,6 +61,7 @@ var (
const (
SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR"
SwarmEnvAccount = "SWARM_ACCOUNT"
SwarmEnvBzzKeyHex = "SWARM_BZZ_KEY_HEX"
SwarmEnvListenAddr = "SWARM_LISTEN_ADDR"
SwarmEnvPort = "SWARM_PORT"
SwarmEnvNetworkID = "SWARM_NETWORK_ID"
@ -80,6 +81,7 @@ const (
SwarmEnvStoreCapacity = "SWARM_STORE_CAPACITY"
SwarmEnvStoreCacheCapacity = "SWARM_STORE_CACHE_CAPACITY"
SwarmEnvBootnodeMode = "SWARM_BOOTNODE_MODE"
SwarmEnvNATInterface = "SWARM_NAT_INTERFACE"
SwarmAccessPassword = "SWARM_ACCESS_PASSWORD"
SwarmAutoDefaultPath = "SWARM_AUTO_DEFAULTPATH"
SwarmGlobalstoreAPI = "SWARM_GLOBALSTORE_API"
@ -112,10 +114,8 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
if err != nil {
return nil, err
}
//override settings provided by environment variables
config = envVarsOverride(config)
//override settings provided by command line
config = cmdLineOverride(config, ctx)
//override settings provided by flags
config = flagsOverride(config, ctx)
//validate configuration parameters
err = validateConfig(config)
@ -124,9 +124,9 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
//finally, after the configuration build phase is finished, initialize
func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context, nodeconfig *node.Config) error {
//at this point, all vars should be set in the Config
//get the account for the provided swarm account
prvkey := getAccount(config.BzzAccount, ctx, stack)
var prvkey *ecdsa.PrivateKey
config.BzzAccount, prvkey = getOrCreateAccount(ctx, stack)
//set the resolved config path (geth --datadir)
config.Path = expandPath(stack.InstanceDir())
//finally, initialize the configuration
@ -170,9 +170,9 @@ func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config
return config, err
}
// cmdLineOverride overrides the current config with whatever is provided through the command line
// flagsOverride overrides the current config with whatever is provided through flags (cli or env vars)
// most values are not allowed a zero value (empty string), if not otherwise noted
func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
func flagsOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" {
currentConfig.BzzAccount = keyid
}
@ -275,122 +275,6 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
}
// envVarsOverride overrides the current config with whatver is provided in environment variables
// most values are not allowed a zero value (empty string), if not otherwise noted
func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
if keyid := os.Getenv(SwarmEnvAccount); keyid != "" {
currentConfig.BzzAccount = keyid
}
if chbookaddr := os.Getenv(SwarmEnvChequebookAddr); chbookaddr != "" {
currentConfig.Contract = common.HexToAddress(chbookaddr)
}
if networkid := os.Getenv(SwarmEnvNetworkID); networkid != "" {
id, err := strconv.ParseUint(networkid, 10, 64)
if err != nil {
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvNetworkID, err)
}
if id != 0 {
currentConfig.NetworkID = id
}
}
if datadir := os.Getenv(GethEnvDataDir); datadir != "" {
currentConfig.Path = expandPath(datadir)
}
bzzport := os.Getenv(SwarmEnvPort)
if len(bzzport) > 0 {
currentConfig.Port = bzzport
}
if bzzaddr := os.Getenv(SwarmEnvListenAddr); bzzaddr != "" {
currentConfig.ListenAddr = bzzaddr
}
if swapenable := os.Getenv(SwarmEnvSwapEnable); swapenable != "" {
swap, err := strconv.ParseBool(swapenable)
if err != nil {
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSwapEnable, err)
}
currentConfig.SwapEnabled = swap
}
if syncdisable := os.Getenv(SwarmEnvSyncDisable); syncdisable != "" {
sync, err := strconv.ParseBool(syncdisable)
if err != nil {
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncDisable, err)
}
currentConfig.SyncEnabled = !sync
}
if v := os.Getenv(SwarmEnvDeliverySkipCheck); v != "" {
skipCheck, err := strconv.ParseBool(v)
if err != nil {
currentConfig.DeliverySkipCheck = skipCheck
}
}
if v := os.Getenv(SwarmEnvSyncUpdateDelay); v != "" {
d, err := time.ParseDuration(v)
if err != nil {
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncUpdateDelay, err)
}
currentConfig.SyncUpdateDelay = d
}
if max := os.Getenv(SwarmEnvMaxStreamPeerServers); max != "" {
m, err := strconv.Atoi(max)
if err != nil {
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvMaxStreamPeerServers, err)
}
currentConfig.MaxStreamPeerServers = m
}
if lne := os.Getenv(SwarmEnvLightNodeEnable); lne != "" {
lightnode, err := strconv.ParseBool(lne)
if err != nil {
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvLightNodeEnable, err)
}
currentConfig.LightNodeEnabled = lightnode
}
if swapapi := os.Getenv(SwarmEnvSwapAPI); swapapi != "" {
currentConfig.SwapAPI = swapapi
}
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
utils.Fatalf(SwarmErrSwapSetNoAPI)
}
if ensapi := os.Getenv(SwarmEnvENSAPI); ensapi != "" {
currentConfig.EnsAPIs = strings.Split(ensapi, ",")
}
if ensaddr := os.Getenv(SwarmEnvENSAddr); ensaddr != "" {
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
}
if cors := os.Getenv(SwarmEnvCORS); cors != "" {
currentConfig.Cors = cors
}
if bm := os.Getenv(SwarmEnvBootnodeMode); bm != "" {
bootnodeMode, err := strconv.ParseBool(bm)
if err != nil {
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvBootnodeMode, err)
}
currentConfig.BootnodeMode = bootnodeMode
}
if api := os.Getenv(SwarmGlobalstoreAPI); api != "" {
currentConfig.GlobalStoreAPI = api
}
return currentConfig
}
// dumpConfig is the dumpconfig command.
// writes a default config to STDOUT
func dumpConfig(ctx *cli.Context) error {

View File

@ -17,6 +17,7 @@
package main
import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
@ -28,6 +29,8 @@ import (
"github.com/docker/docker/pkg/reexec"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm"
"github.com/ethersphere/swarm/api"
@ -48,6 +51,7 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name),
}
@ -56,15 +60,161 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
swarm.ExpectExit()
}
func TestConfigFailsNoBzzAccount(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
func TestBzzKeyFlag(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("unable to generate key")
}
hexKey := hex.EncodeToString(crypto.FromECDSA(key))
bzzaccount := crypto.PubkeyToAddress(key.PublicKey).Hex()
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
conf := &node.Config{
DataDir: dir,
IPCPath: "testbzzkeyflag.ipc",
NoUSB: true,
}
swarm := runSwarm(t, flags...)
swarm.Expect("Fatal: " + SwarmErrNoBZZAccount + "\n")
swarm.ExpectExit()
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
fmt.Sprintf("--%s", SwarmBzzKeyHexFlag.Name), hexKey,
}
node.Cmd = runSwarm(t, flags...)
defer func() {
node.Shutdown()
}()
node.Cmd.InputLine(testPassphrase)
// wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint())
if err == nil {
break
}
}
if node.Client == nil {
t.Fatal(err)
}
// load info
var info swarm.Info
if err := node.Client.Call(&info, "bzz_info"); err != nil {
t.Fatal(err)
}
if info.BzzAccount != bzzaccount {
t.Fatalf("Expected account to be %s, got %s", bzzaccount, info.BzzAccount)
}
}
func TestEmptyBzzAccountFlagMultipleAccounts(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Create two accounts on disk
getTestAccount(t, dir)
getTestAccount(t, dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
}
node.Cmd = runSwarm(t, flags...)
node.Cmd.ExpectRegexp(fmt.Sprintf("Please choose one of the accounts by running swarm with the --%s flag.", SwarmAccountFlag.Name))
}
func TestEmptyBzzAccountFlagSingleAccount(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
conf, account := getTestAccount(t, dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
}
node.Cmd = runSwarm(t, flags...)
defer func() {
node.Shutdown()
}()
node.Cmd.InputLine(testPassphrase)
// wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint())
if err == nil {
break
}
}
if node.Client == nil {
t.Fatal(err)
}
// load info
var info swarm.Info
if err := node.Client.Call(&info, "bzz_info"); err != nil {
t.Fatal(err)
}
if info.BzzAccount != account.Address.Hex() {
t.Fatalf("Expected account to be %s, got %s", account.Address.Hex(), info.BzzAccount)
}
}
func TestEmptyBzzAccountFlagNoAccountWrongPassword(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
}
node.Cmd = runSwarm(t, flags...)
// Set password
node.Cmd.InputLine("goodpassword")
// Confirm password
node.Cmd.InputLine("wrongpassword")
node.Cmd.ExpectRegexp("Passphrases do not match")
}
func TestConfigCmdLineOverrides(t *testing.T) {
@ -86,6 +236,7 @@ func TestConfigCmdLineOverrides(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),

View File

@ -101,7 +101,7 @@ func download(ctx *cli.Context) {
return nil
}
if passwords := makePasswordList(ctx); passwords != nil {
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), false, 0, passwords)
err = dl(password)
} else {
err = dl("")

View File

@ -30,6 +30,11 @@ var (
Usage: "Swarm account key file",
EnvVar: SwarmEnvAccount,
}
SwarmBzzKeyHexFlag = cli.StringFlag{
Name: "bzzkeyhex",
Usage: "BzzAccount key in hex (for testing)",
EnvVar: SwarmEnvBzzKeyHex,
}
SwarmListenAddrFlag = cli.StringFlag{
Name: "httpaddr",
Usage: "Swarm HTTP API listening interface",
@ -40,6 +45,11 @@ var (
Usage: "Swarm local http api port",
EnvVar: SwarmEnvPort,
}
SwarmNATInterfaceFlag = cli.StringFlag{
Name: "natif",
Usage: "Announce the IP address of a given network interface (e.g. eth0)",
EnvVar: SwarmEnvNATInterface,
}
SwarmNetworkIdFlag = cli.IntFlag{
Name: "bzznetworkid",
Usage: "Network identifier (integer, default 3=swarm testnet)",

View File

@ -21,6 +21,7 @@ import (
"encoding/hex"
"fmt"
"io/ioutil"
"net"
"os"
"os/signal"
"runtime"
@ -38,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm"
bzzapi "github.com/ethersphere/swarm/api"
@ -170,6 +172,7 @@ func init() {
utils.IPCDisabledFlag,
utils.IPCPathFlag,
utils.PasswordFileFlag,
SwarmNATInterfaceFlag,
// bzzd-specific flags
CorsStringFlag,
EnsAPIFlag,
@ -184,6 +187,7 @@ func init() {
SwarmListenAddrFlag,
SwarmPortFlag,
SwarmAccountFlag,
SwarmBzzKeyHexFlag,
SwarmNetworkIdFlag,
ChequebookAddrFlag,
// upload flags
@ -290,6 +294,9 @@ func bzzd(ctx *cli.Context) error {
//disable dynamic dialing from p2p/discovery
cfg.P2P.NoDial = true
//optionally set the NAT IP from a network interface
setSwarmNATFromInterface(ctx, &cfg)
stack, err := node.New(&cfg)
if err != nil {
utils.Fatalf("can't create node: %v", err)
@ -351,21 +358,62 @@ func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
}
}
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
//an account is mandatory
if bzzaccount == "" {
utils.Fatalf(SwarmErrNoBZZAccount)
// getOrCreateAccount returns the address and associated private key for a bzzaccount
// If no account exists, it will create an account for you.
func getOrCreateAccount(ctx *cli.Context, stack *node.Node) (string, *ecdsa.PrivateKey) {
var bzzaddr string
// Check if a key was provided
if hexkey := ctx.GlobalString(SwarmBzzKeyHexFlag.Name); hexkey != "" {
key, err := crypto.HexToECDSA(hexkey)
if err != nil {
utils.Fatalf("failed using %s: %v", SwarmBzzKeyHexFlag.Name, err)
}
bzzaddr := crypto.PubkeyToAddress(key.PublicKey).Hex()
log.Info(fmt.Sprintf("Swarm account key loaded from %s", SwarmBzzKeyHexFlag.Name), "address", bzzaddr)
return bzzaddr, key
}
// Try to load the arg as a hex key file.
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
return key
}
// Otherwise try getting it from the keystore.
am := stack.AccountManager()
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
// Check if an address was provided
if bzzaddr = ctx.GlobalString(SwarmAccountFlag.Name); bzzaddr != "" {
// Try to load the arg as a hex key file.
if key, err := crypto.LoadECDSA(bzzaddr); err == nil {
bzzaddr := crypto.PubkeyToAddress(key.PublicKey).Hex()
log.Info("Swarm account key loaded", "address", bzzaddr)
return bzzaddr, key
}
return bzzaddr, decryptStoreAccount(ks, bzzaddr, utils.MakePasswordList(ctx))
}
// No address or key were provided
accounts := ks.Accounts()
switch l := len(accounts); l {
case 0:
// Create an account
log.Info("You don't have an account yet. Creating one...")
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
account, err := ks.NewAccount(password)
if err != nil {
utils.Fatalf("failed creating an account: %v", err)
}
bzzaddr = account.Address.Hex()
case 1:
// Use existing account
bzzaddr = accounts[0].Address.Hex()
default:
// Inform user about multiple accounts
log.Info(fmt.Sprintf("Multiple (%d) accounts were found in your keystore.", l))
for _, a := range accounts {
log.Info(fmt.Sprintf("Account: %s", a.Address.Hex()))
}
utils.Fatalf(fmt.Sprintf("Please choose one of the accounts by running swarm with the --%s flag.", SwarmAccountFlag.Name))
}
return bzzaddr, decryptStoreAccount(ks, bzzaddr, utils.MakePasswordList(ctx))
}
// getPrivKey returns the private key of the specified bzzaccount
@ -387,7 +435,9 @@ func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
}
defer stack.Close()
return getAccount(bzzconfig.BzzAccount, ctx, stack)
var privkey *ecdsa.PrivateKey
bzzconfig.BzzAccount, privkey = getOrCreateAccount(ctx, stack)
return privkey
}
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
@ -412,7 +462,7 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
utils.Fatalf("Can't load swarm account key: %v", err)
}
for i := 0; i < 3; i++ {
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), false, i, passwords)
key, err := keystore.DecryptKey(keyjson, password)
if err == nil {
return key.PrivateKey
@ -422,18 +472,17 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
return nil
}
// getPassPhrase retrieves the password associated with bzz account, either by fetching
// from a list of pre-loaded passwords, or by requesting it interactively from user.
func getPassPhrase(prompt string, i int, passwords []string) string {
// non-interactive
// getPassPhrase retrieves the password associated with a bzzaccount, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
// If a list of passwords was supplied, retrieve from them
if len(passwords) > 0 {
if i < len(passwords) {
return passwords[i]
}
return passwords[len(passwords)-1]
}
// fallback to interactive mode
// Otherwise prompt the user for the password
if prompt != "" {
fmt.Println(prompt)
}
@ -441,6 +490,15 @@ func getPassPhrase(prompt string, i int, passwords []string) string {
if err != nil {
utils.Fatalf("Failed to read passphrase: %v", err)
}
if confirmation {
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
if err != nil {
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
}
if password != confirm {
utils.Fatalf("Passphrases do not match")
}
}
return password
}
@ -473,3 +531,26 @@ func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) {
}
}
func setSwarmNATFromInterface(ctx *cli.Context, cfg *node.Config) {
ifacename := ctx.GlobalString(SwarmNATInterfaceFlag.Name)
if ifacename == "" {
return
}
iface, err := net.InterfaceByName(ifacename)
if err != nil {
utils.Fatalf("can't get network interface %s", ifacename)
}
addrs, err := iface.Addrs()
if err != nil || len(addrs) == 0 {
utils.Fatalf("could not get address from interface %s: %v", ifacename, err)
}
ip, _, err := net.ParseCIDR(addrs[0].String())
if err != nil {
utils.Fatalf("could not parse IP addr from interface %s: %v", ifacename, err)
}
cfg.P2P.NAT = nat.ExtIP(ip)
}

View File

@ -1,26 +0,0 @@
#!/bin/sh
set -o errexit
set -o pipefail
set -o nounset
PASSWORD=${PASSWORD:-}
DATADIR=${DATADIR:-/root/.ethereum/}
if [ "$PASSWORD" == "" ]; then echo "Password must be set, in order to use swarm non-interactively." && exit 1; fi
echo $PASSWORD > /password
KEYFILE=`find $DATADIR | grep UTC | head -n 1` || true
if [ ! -f "$KEYFILE" ]; then echo "No keyfile found. Generating..." && geth --datadir $DATADIR --password /password account new; fi
KEYFILE=`find $DATADIR | grep UTC | head -n 1` || true
if [ ! -f "$KEYFILE" ]; then echo "Could not find nor generate a BZZ keyfile." && exit 1; else echo "Found keyfile $KEYFILE"; fi
VERSION=`swarm version`
echo "Running Swarm:"
echo $VERSION
export BZZACCOUNT="`echo -n $KEYFILE | tail -c 40`" || true
if [ "$BZZACCOUNT" == "" ]; then echo "Could not parse BZZACCOUNT from keyfile." && exit 1; fi
exec swarm --bzzaccount=$BZZACCOUNT --password /password --datadir $DATADIR $@ 2>&1

View File

@ -67,8 +67,8 @@ Wire Protocol Specifications
| Msg Name | From->To | Params | Example |
| -------- | -------- | -------- | ------- |
| StreamInfoReq | Client->Server | Streams`[]string` | `SYNC\|6, SYNC\|5` |
| StreamInfoRes | Server->Client | Streams`[]StreamDescriptor` <br>Stream`string`<br>Cursor`uint64`<br>Bounded`bool` | `SYNC\|6;CUR=1632;bounded, SYNC\|7;CUR=18433;bounded` |
| StreamInfoReq | Client->Server | Streams`[]ID` | `SYNC\|6, SYNC\|5` |
| StreamInfoRes | Server->Client | Streams`[]StreamDescriptor` <br>Stream`ID`<br>Cursor`uint64`<br>Bounded`bool` | `SYNC\|6;CUR=1632;bounded, SYNC\|7;CUR=18433;bounded` |
| GetRange | Client->Server| Ruid`uint`<br>Stream `string`<br>From`uint`<br>To`*uint`(nullable)<br>Roundtrip`bool` | `Ruid: 21321, Stream: SYNC\|6, From: 1, To: 100`(bounded), Roundtrip: true<br>`Stream: SYNC\|7, From: 109, Roundtrip: true`(unbounded) |
| OfferedHashes | Server->Client| Ruid`uint`<br>Hashes `[]byte` | `Ruid: 21321, Hashes: [cbcbbaddda, bcbbbdbbdc, ....]` |
| WantedHashes | Client->Server | Ruid`uint`<br>Bitvector`[]byte` | `Ruid: 21321, Bitvector: [0100100100] ` |
@ -81,65 +81,120 @@ Notes:
* two notions of bounded - on the stream level and on the localstore
* if TO is not specified - we assume unbounded stream, and we just send whatever, until at most, we fill up an entire batch.
### Message struct definitions:
### Message and interface definitions:
```go
// StreamProvider interface provides a lightweight abstraction that allows an easily-pluggable
// stream provider as part of the Stream! protocol specification.
type StreamProvider interface {
NeedData(ctx context.Context, key []byte) (need bool, wait func(context.Context) error)
Get(ctx context.Context, addr chunk.Address) ([]byte, error)
Put(ctx context.Context, addr chunk.Address, data []byte) (exists bool, err error)
Subscribe(ctx context.Context, key interface{}, from, to uint64) (<-chan chunk.Descriptor, func())
Cursor(interface{}) (uint64, error)
RunUpdateStreams(p *Peer)
StreamName() string
ParseKey(string) (interface{}, error)
EncodeKey(interface{}) (string, error)
StreamBehavior() StreamInitBehavior
Boundedness() bool
}
```
```go
type StreamInitBehavior int
```
```go
// StreamInfoReq is a request to get information about particular streams
type StreamInfoReq struct {
Streams []string
Streams []ID
}
```
```go
// StreamInfoRes is a response to StreamInfoReq with the corresponding stream descriptors
type StreamInfoRes struct {
Streams []StreamDescriptor
Streams []StreamDescriptor
}
```
```go
// StreamDescriptor describes an arbitrary stream
type StreamDescriptor struct {
Name string
Cursor uint
Bounded bool
Stream ID
Cursor uint64
Bounded bool
}
```
```go
// GetRange is a message sent from the downstream peer to the upstream peer asking for chunks
// within a particular interval for a certain stream
type GetRange struct {
Ruid uint
Stream string
From uint
To uint `rlp:nil`
BatchSize uint
Roundtrip bool
Ruid uint
Stream ID
From uint64
To uint64 `rlp:nil`
BatchSize uint
Roundtrip bool
}
```
```go
// OfferedHashes is a message sent from the upstream peer to the downstream peer allowing the latter
// to selectively ask for chunks within a particular requested interval
type OfferedHashes struct {
Ruid uint
LastIndex uint
Hashes []byte
Ruid uint
LastIndex uint
Hashes []byte
}
```
```go
// WantedHashes is a message sent from the downstream peer to the upstream peer in response
// to OfferedHashes in order to selectively ask for a particular chunks within an interval
type WantedHashes struct {
Ruid uint
BitVector []byte
Ruid uint
BitVector []byte
}
```
```go
// ChunkDelivery delivers a frame of chunks in response to a WantedHashes message
type ChunkDelivery struct {
Ruid uint
LastIndex uint
Chunks [][]byte
Ruid uint
LastIndex uint
Chunks []DeliveredChunk
}
```
```go
type BatchDone struct {
Ruid uint
Last uint
// DeliveredChunk encapsulates a particular chunk's underlying data within a ChunkDelivery message
type DeliveredChunk struct {
Addr storage.Address
Data []byte
}
```
```go
// StreamState is a message exchanged between two nodes to notify of changes or errors in a stream's state
type StreamState struct {
Stream string
Code uint16
Message string
Stream ID
Code uint16
Message string
}
```
```go
// Stream defines a unique stream identifier in a textual representation
type ID struct {
// Name is used for the Stream provider identification
Name string
// Key is the name of specific data stream within the stream provider. The semantics of this value
// is at the discretion of the stream provider implementation
Key string
}
```
@ -147,14 +202,14 @@ Message exchange examples:
======
Initial handshake - client queries server for stream states<br>
![handshake](https://raw.githubusercontent.com/ethersphere/swarm/stream-spec/docs/diagrams/stream-handshake.png)
![handshake](https://raw.githubusercontent.com/ethersphere/swarm/master/docs/diagrams/stream-handshake.png)
<br>
GetRange (bounded) - client requests a bounded range within a stream<br>
![bounded-range](https://raw.githubusercontent.com/ethersphere/swarm/stream-spec/docs/diagrams/stream-bounded.png)
![bounded-range](https://raw.githubusercontent.com/ethersphere/swarm/master/docs/diagrams/stream-bounded.png)
<br>
GetRange (unbounded) - client requests an unbounded range (specifies only `From` parameter)<br>
![unbounded-range](https://raw.githubusercontent.com/ethersphere/swarm/stream-spec/docs/diagrams/stream-unbounded.png)
![unbounded-range](https://raw.githubusercontent.com/ethersphere/swarm/master/docs/diagrams/stream-unbounded.png)
<br>
GetRange (no roundtrip) - client requests an unbounded or bounded range with no roundtrip configured<br>
![unbounded-range](https://raw.githubusercontent.com/ethersphere/swarm/stream-spec/docs/diagrams/stream-no-roundtrip.png)
![unbounded-range](https://raw.githubusercontent.com/ethersphere/swarm/master/docs/diagrams/stream-no-roundtrip.png)

View File

@ -22,15 +22,20 @@ import (
var errInvalidLength = errors.New("invalid length")
// BitVector is a convenience object for manipulating and representing bit vectors
type BitVector struct {
len int
b []byte
}
// New creates a new bit vector with the given length
func New(l int) (bv *BitVector, err error) {
return NewFromBytes(make([]byte, l/8+1), l)
}
// NewFromBytes creates a bit vector from the passed byte slice.
//
// Leftmost bit in byte slice becomes leftmost bit in bit vector
func NewFromBytes(b []byte, l int) (bv *BitVector, err error) {
if l <= 0 {
return nil, errInvalidLength
@ -44,12 +49,14 @@ func NewFromBytes(b []byte, l int) (bv *BitVector, err error) {
}, nil
}
// Get gets the corresponding bit, counted from left to right
func (bv *BitVector) Get(i int) bool {
bi := i / 8
return bv.b[bi]&(0x1<<uint(i%8)) != 0
}
func (bv *BitVector) Set(i int, v bool) {
// Set sets the bit corresponding to the index in the bitvector, counted from left to right
func (bv *BitVector) set(i int, v bool) {
bi := i / 8
cv := bv.Get(i)
if cv != v {
@ -57,6 +64,61 @@ func (bv *BitVector) Set(i int, v bool) {
}
}
// Set sets the bit corresponding to the index in the bitvector, counted from left to right
func (bv *BitVector) Set(i int) {
bv.set(i, true)
}
// Unset UNSETS the corresponding bit, counted from left to right
func (bv *BitVector) Unset(i int) {
bv.set(i, false)
}
// SetBytes sets all bits in the bitvector that are set in the argument
//
// The argument must be the same as the bitvector length
func (bv *BitVector) SetBytes(bs []byte) error {
if len(bs) != bv.len {
return errors.New("invalid length")
}
for i := 0; i < bv.len*8; i++ {
bi := i / 8
if bs[bi]&(0x01<<uint(i%8)) > 0 {
bv.set(i, true)
}
}
return nil
}
// UnsetBytes UNSETS all bits in the bitvector that are set in the argument
//
// The argument must be the same as the bitvector length
func (bv *BitVector) UnsetBytes(bs []byte) error {
if len(bs) != bv.len {
return errors.New("invalid length")
}
for i := 0; i < bv.len*8; i++ {
bi := i / 8
if bs[bi]&(0x01<<uint(i%8)) > 0 {
bv.set(i, false)
}
}
return nil
}
// String implements Stringer interface
func (bv *BitVector) String() (s string) {
for i := 0; i < bv.len*8; i++ {
if bv.Get(i) {
s += "1"
} else {
s += "0"
}
}
return s
}
// Bytes retrieves the underlying bytes of the bitvector
func (bv *BitVector) Bytes() []byte {
return bv.b
}

View File

@ -18,6 +18,7 @@ package bitvector
import "testing"
// TestBitvectorNew checks that enforcements of argument length works in the constructors
func TestBitvectorNew(t *testing.T) {
_, err := New(0)
if err != errInvalidLength {
@ -40,6 +41,7 @@ func TestBitvectorNew(t *testing.T) {
}
}
// TestBitvectorGetSet tests correctness of individual Set and Get commands
func TestBitvectorGetSet(t *testing.T) {
for _, length := range []int{
1,
@ -71,7 +73,7 @@ func TestBitvectorGetSet(t *testing.T) {
}()
for i := 0; i < length; i++ {
bv.Set(i, true)
bv.Set(i)
for j := 0; j < length; j++ {
if j == i {
if !bv.Get(j) {
@ -84,7 +86,7 @@ func TestBitvectorGetSet(t *testing.T) {
}
}
bv.Set(i, false)
bv.Unset(i)
if bv.Get(i) {
t.Errorf("element on index %v is not set to false", i)
@ -93,6 +95,7 @@ func TestBitvectorGetSet(t *testing.T) {
}
}
// TestBitvectorNewFromBytesGet tests that bit vector is initialized correctly from underlying byte slice
func TestBitvectorNewFromBytesGet(t *testing.T) {
bv, err := NewFromBytes([]byte{8}, 8)
if err != nil {
@ -102,3 +105,36 @@ func TestBitvectorNewFromBytesGet(t *testing.T) {
t.Fatalf("element 3 is not set to true: state %08b", bv.b[0])
}
}
// TestBitVectorString tests that string representation of bit vector is correct
func TestBitVectorString(t *testing.T) {
b := []byte{0xa5, 0x81}
expect := "1010010110000001"
bv, err := NewFromBytes(b, 2)
if err != nil {
t.Fatal(err)
}
if bv.String() != expect {
t.Fatalf("bitvector string fail: got %s, expect %s", bv.String(), expect)
}
}
// TestBitVectorSetUnsetBytes tests that setting and unsetting by byte slice modifies the bit vector correctly
func TestBitVectorSetBytes(t *testing.T) {
b := []byte{0xff, 0xff}
cb := []byte{0xa5, 0x81}
expectUnset := "0101101001111110"
expectReset := "1111111111111111"
bv, err := NewFromBytes(b, 2)
if err != nil {
t.Fatal(err)
}
bv.UnsetBytes(cb)
if bv.String() != expectUnset {
t.Fatalf("bitvector unset bytes fail: got %s, expect %s", bv.String(), expectUnset)
}
bv.SetBytes(cb)
if bv.String() != expectReset {
t.Fatalf("bitvector reset bytes fail: got %s, expect %s", bv.String(), expectReset)
}
}

View File

@ -20,6 +20,7 @@ import (
"bytes"
"fmt"
"math/rand"
"sort"
"strings"
"sync"
"time"
@ -93,6 +94,14 @@ type Kademlia struct {
nDepthSig []chan struct{} // signals when neighbourhood depth nDepth is changed
}
type KademliaInfo struct {
Depth int `json:"depth"`
TotalConnections int `json:"total_connections"`
TotalKnown int `json:"total_known"`
Connections [][]string `json:"connections"`
Known [][]string `json:"known"`
}
// NewKademlia creates a Kademlia table for base address addr
// with parameters as in params
// if params is nil, it uses default values
@ -422,18 +431,6 @@ func (k *Kademlia) Off(p *Peer) {
}
}
func (k *Kademlia) ListKnown() []*BzzAddr {
res := []*BzzAddr{}
k.addrs.Each(func(val pot.Val) bool {
e := val.(*entry)
res = append(res, e.BzzAddr)
return true
})
return res
}
// EachConn is an iterator with args (base, po, f) applies f to each live peer
// that has proximity order po or less as measured from the base
// if base is nil, kademlia base address is used
@ -576,6 +573,56 @@ func (k *Kademlia) BaseAddr() []byte {
return k.base
}
func (k *Kademlia) KademliaInfo() KademliaInfo {
k.lock.RLock()
defer k.lock.RUnlock()
return k.kademliaInfo()
}
func (k *Kademlia) kademliaInfo() (ki KademliaInfo) {
ki.Depth = depthForPot(k.conns, k.NeighbourhoodSize, k.base)
ki.TotalConnections = k.conns.Size()
ki.TotalKnown = k.addrs.Size()
ki.Connections = make([][]string, k.MaxProxDisplay)
ki.Known = make([][]string, k.MaxProxDisplay)
k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
if po >= k.MaxProxDisplay {
po = k.MaxProxDisplay - 1
}
row := []string{}
f(func(val pot.Val) bool {
e := val.(*Peer)
row = append(row, fmt.Sprintf("%x", e.Address()))
return true
})
sort.Strings(row)
ki.Connections[po] = row
return true
})
k.addrs.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
if po >= k.MaxProxDisplay {
po = k.MaxProxDisplay - 1
}
row := []string{}
f(func(val pot.Val) bool {
e := val.(*entry)
row = append(row, fmt.Sprintf("%x", e.Address()))
return true
})
sort.Strings(row)
ki.Known[po] = row
return true
})
return
}
// String returns kademlia table + kaddb table displayed with ascii
func (k *Kademlia) String() string {
k.lock.RLock()

View File

@ -0,0 +1,65 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package newstream
import (
"flag"
"io/ioutil"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethersphere/swarm/network"
"github.com/ethersphere/swarm/storage/localstore"
"github.com/ethersphere/swarm/storage/mock"
)
var (
loglevel = flag.Int("loglevel", 5, "verbosity of logs")
)
func init() {
flag.Parse()
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
}
func newTestLocalStore(id enode.ID, addr *network.BzzAddr, globalStore mock.GlobalStorer) (localStore *localstore.DB, cleanup func(), err error) {
dir, err := ioutil.TempDir("", "swarm-stream-")
if err != nil {
return nil, nil, err
}
cleanup = func() {
os.RemoveAll(dir)
}
var mockStore *mock.NodeStore
if globalStore != nil {
mockStore = globalStore.NewNodeStore(common.BytesToAddress(id.Bytes()))
}
localStore, err = localstore.New(dir, addr.Over(), &localstore.Options{
MockStore: mockStore,
})
if err != nil {
cleanup()
return nil, nil, err
}
return localStore, cleanup, nil
}

100
network/newstream/peer.go Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package newstream
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/ethersphere/swarm/chunk"
"github.com/ethersphere/swarm/network"
"github.com/ethersphere/swarm/network/bitvector"
"github.com/ethersphere/swarm/state"
)
var ErrEmptyBatch = errors.New("empty batch")
const (
HashSize = 32
BatchSize = 16
)
// Peer is the Peer extension for the streaming protocol
type Peer struct {
*network.BzzPeer
mtx sync.Mutex
providers map[string]StreamProvider
intervalsStore state.Store
streamCursorsMu sync.Mutex
streamCursors map[string]uint64 // key: Stream ID string representation, value: session cursor. Keeps cursors for all streams. when unset - we are not interested in that bin
dirtyStreams map[string]bool // key: stream ID, value: whether cursors for a stream should be updated
activeBoundedGets map[string]chan struct{}
openWants map[uint]*want // maintain open wants on the client side
openOffers map[uint]offer // maintain open offers on the server side
quit chan struct{} // closed when peer is going offline
}
// NewPeer is the constructor for Peer
func NewPeer(peer *network.BzzPeer, i state.Store, providers map[string]StreamProvider) *Peer {
p := &Peer{
BzzPeer: peer,
providers: providers,
intervalsStore: i,
streamCursors: make(map[string]uint64),
dirtyStreams: make(map[string]bool),
openWants: make(map[uint]*want),
openOffers: make(map[uint]offer),
quit: make(chan struct{}),
}
return p
}
func (p *Peer) Left() {
close(p.quit)
}
// HandleMsg is the message handler that delegates incoming messages
func (p *Peer) HandleMsg(ctx context.Context, msg interface{}) error {
switch msg := msg.(type) {
default:
return fmt.Errorf("unknown message type: %T", msg)
}
return nil
}
type offer struct {
ruid uint
stream ID
hashes []byte
requested time.Time
}
type want struct {
ruid uint
from uint64
to uint64
stream ID
hashes map[string]bool
bv *bitvector.BitVector
requested time.Time
remaining uint64
chunks chan chunk.Chunk
done chan error
}

167
network/newstream/stream.go Normal file
View File

@ -0,0 +1,167 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package newstream
import (
"sync"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm/log"
"github.com/ethersphere/swarm/network"
"github.com/ethersphere/swarm/p2p/protocols"
"github.com/ethersphere/swarm/state"
)
// SlipStream implements node.Service
var _ node.Service = (*SlipStream)(nil)
var SyncerSpec = &protocols.Spec{
Name: "bzz-stream",
Version: 8,
MaxMsgSize: 10 * 1024 * 1024,
Messages: []interface{}{
StreamInfoReq{},
StreamInfoRes{},
GetRange{},
OfferedHashes{},
ChunkDelivery{},
WantedHashes{},
},
}
// SlipStream is the base type that handles all client/server operations on a node
// it is instantiated once per stream protocol instance, that is, it should have
// one instance per node
type SlipStream struct {
mtx sync.RWMutex
intervalsStore state.Store //every protocol would make use of this
peers map[enode.ID]*Peer
kad *network.Kademlia
providers map[string]StreamProvider
spec *protocols.Spec //this protocol's spec
balance protocols.Balance //implements protocols.Balance, for accounting
prices protocols.Prices //implements protocols.Prices, provides prices to accounting
quit chan struct{} // terminates registry goroutines
}
func NewSlipStream(intervalsStore state.Store, kad *network.Kademlia, providers ...StreamProvider) *SlipStream {
slipStream := &SlipStream{
intervalsStore: intervalsStore,
kad: kad,
peers: make(map[enode.ID]*Peer),
providers: make(map[string]StreamProvider),
quit: make(chan struct{}),
}
for _, p := range providers {
slipStream.providers[p.StreamName()] = p
}
slipStream.spec = SyncerSpec
return slipStream
}
func (s *SlipStream) getPeer(id enode.ID) *Peer {
s.mtx.Lock()
defer s.mtx.Unlock()
p := s.peers[id]
return p
}
func (s *SlipStream) addPeer(p *Peer) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.peers[p.ID()] = p
}
func (s *SlipStream) removePeer(p *Peer) {
s.mtx.Lock()
defer s.mtx.Unlock()
if _, found := s.peers[p.ID()]; found {
log.Error("removing peer", "id", p.ID())
delete(s.peers, p.ID())
p.Left()
} else {
log.Warn("peer was marked for removal but not found", "peer", p.ID())
}
}
// Run is being dispatched when 2 nodes connect
func (s *SlipStream) Run(p *p2p.Peer, rw p2p.MsgReadWriter) error {
peer := protocols.NewPeer(p, rw, s.spec)
bp := network.NewBzzPeer(peer)
np := network.NewPeer(bp, s.kad)
s.kad.On(np)
defer s.kad.Off(np)
sp := NewPeer(bp, s.intervalsStore, s.providers)
s.addPeer(sp)
defer s.removePeer(sp)
return peer.Run(sp.HandleMsg)
}
func (s *SlipStream) Protocols() []p2p.Protocol {
return []p2p.Protocol{
{
Name: "bzz-stream",
Version: 1,
Length: 10 * 1024 * 1024,
Run: s.Run,
},
}
}
func (s *SlipStream) APIs() []rpc.API {
return []rpc.API{
{
Namespace: "bzz-stream",
Version: "1.0",
Service: NewAPI(s),
Public: false,
},
}
}
// Additional public methods accessible through API for pss
type API struct {
*SlipStream
}
func NewAPI(s *SlipStream) *API {
return &API{SlipStream: s}
}
func (s *SlipStream) Start(server *p2p.Server) error {
log.Info("slip stream starting")
return nil
}
func (s *SlipStream) Stop() error {
log.Info("slip stream closing")
s.mtx.Lock()
defer s.mtx.Unlock()
close(s.quit)
return nil
}

174
network/newstream/wire.go Normal file
View File

@ -0,0 +1,174 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package newstream
import (
"context"
"fmt"
"github.com/ethersphere/swarm/chunk"
"github.com/ethersphere/swarm/storage"
)
// StreamProvider interface provides a lightweight abstraction that allows an easily-pluggable
// stream provider as part of the Stream! protocol specification.
// Since Stream! thoroughly defines the concepts of a stream, intervals, clients and servers, the
// interface therefore needs only a pluggable provider.
// The domain interpretable notions which are at the discretion of the implementing
// provider therefore are - sourcing data (get, put, subscribe for constant new data, and need data
// which is to decide whether to retrieve data or not), retrieving cursors from the data store, the
// implementation of which streams to maintain with a certain peer and providing functionality
// to expose, parse and encode values related to the string represntation of the stream
type StreamProvider interface {
// NeedData informs the caller whether a certain chunk needs to be fetched from another peer or not.
// Typically this will involve checking whether a certain chunk exists locally.
// In case a chunk does not exist locally - a `wait` function returns upon chunk delivery
NeedData(ctx context.Context, key []byte) (need bool, wait func(context.Context) error)
// Get a particular chunk identified by addr from the local storage
Get(ctx context.Context, addr chunk.Address) ([]byte, error)
// Put a certain chunk into the local storage
Put(ctx context.Context, addr chunk.Address, data []byte) (exists bool, err error)
// Subscribe to a data stream from an arbitrary data source
Subscribe(ctx context.Context, key interface{}, from, to uint64) (<-chan chunk.Descriptor, func())
// Cursor returns the last known Cursor for a given Stream Key
Cursor(interface{}) (uint64, error)
// RunUpdateStreams is a provider specific implementation on how to maintain running streams with
// an arbitrary Peer. This method should always be run in a separate goroutine
RunUpdateStreams(p *Peer)
// StreamName returns the Name of the Stream (see ID)
StreamName() string
// ParseStream from a standard pipe-separated string and return the Stream Key
ParseKey(string) (interface{}, error)
// EncodeStream from a Stream Key to a Stream pipe-separated string representation
EncodeKey(interface{}) (string, error)
// StreamBehavior defines how the stream behaves upon initialisation
StreamBehavior() StreamInitBehavior
Boundedness() bool
}
// StreamInitBehavior defines the stream behavior upon init
type StreamInitBehavior int
const (
// StreamIdle means that there is no initial automatic message exchange
// between the nodes when the protocol gets established
StreamIdle StreamInitBehavior = iota
// StreamGetCursors tells the two nodes to automatically fetch stream
// cursors from each other
StreamGetCursors
// StreamAutostart automatically starts fetching data from the streams
// once the cursors arrive
StreamAutostart
)
// StreamInfoReq is a request to get information about particular streams
type StreamInfoReq struct {
Streams []ID
}
// StreamInfoRes is a response to StreamInfoReq with the corresponding stream descriptors
type StreamInfoRes struct {
Streams []StreamDescriptor
}
// StreamDescriptor describes an arbitrary stream
type StreamDescriptor struct {
Stream ID
Cursor uint64
Bounded bool
}
// GetRange is a message sent from the downstream peer to the upstream peer asking for chunks
// within a particular interval for a certain stream
type GetRange struct {
Ruid uint
Stream ID
From uint64
To uint64 `rlp:nil`
BatchSize uint
Roundtrip bool
}
// OfferedHashes is a message sent from the upstream peer to the downstream peer allowing the latter
// to selectively ask for chunks within a particular requested interval
type OfferedHashes struct {
Ruid uint
LastIndex uint
Hashes []byte
}
// WantedHashes is a message sent from the downstream peer to the upstream peer in response
// to OfferedHashes in order to selectively ask for a particular chunks within an interval
type WantedHashes struct {
Ruid uint
BitVector []byte
}
// ChunkDelivery delivers a frame of chunks in response to a WantedHashes message
type ChunkDelivery struct {
Ruid uint
LastIndex uint
Chunks []DeliveredChunk
}
// DeliveredChunk encapsulates a particular chunk's underlying data within a ChunkDelivery message
type DeliveredChunk struct {
Addr storage.Address //chunk address
Data []byte //chunk data
}
// StreamState is a message exchanged between two nodes to notify of changes or errors in a stream's state
type StreamState struct {
Stream ID
Code uint16
Message string
}
// Stream defines a unique stream identifier in a textual representation
type ID struct {
// Name is used for the Stream provider identification
Name string
// Key is the name of specific data stream within the stream provider. The semantics of this value
// is at the discretion of the stream provider implementation
Key string
}
// NewID returns a new Stream ID for a particular stream Name and Key
func NewID(name string, key string) ID {
return ID{
Name: name,
Key: key,
}
}
// String return a stream id based on all Stream fields.
func (s ID) String() string {
return fmt.Sprintf("%s|%s", s.Name, s.Key)
}

36
network/retrieval/peer.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package retrieval
import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethersphere/swarm/network"
)
// Peer wraps BzzPeer with a contextual logger for this peer
type Peer struct {
*network.BzzPeer
logger log.Logger
}
// NewPeer is the constructor for Peer
func NewPeer(peer *network.BzzPeer) *Peer {
return &Peer{
BzzPeer: peer,
logger: log.New("peer", peer.ID()),
}
}

View File

@ -0,0 +1,424 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package retrieval
import (
"bytes"
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm/chunk"
"github.com/ethersphere/swarm/log"
"github.com/ethersphere/swarm/network"
"github.com/ethersphere/swarm/network/timeouts"
"github.com/ethersphere/swarm/p2p/protocols"
"github.com/ethersphere/swarm/spancontext"
"github.com/ethersphere/swarm/storage"
opentracing "github.com/opentracing/opentracing-go"
olog "github.com/opentracing/opentracing-go/log"
)
var (
// Compile time interface check
_ node.Service = &Retrieval{}
// Metrics
processReceivedChunksCount = metrics.NewRegisteredCounter("network.retrieve.received_chunks.count", nil)
handleRetrieveRequestMsgCount = metrics.NewRegisteredCounter("network.retrieve.handle_retrieve_request_msg.count", nil)
retrieveChunkFail = metrics.NewRegisteredCounter("network.retrieve.retrieve_chunks_fail.count", nil)
lastReceivedRetrieveChunksMsg = metrics.GetOrRegisterGauge("network.retrieve.received_chunks", nil)
// Protocol spec
spec = &protocols.Spec{
Name: "bzz-retrieve",
Version: 1,
MaxMsgSize: 10 * 1024 * 1024,
Messages: []interface{}{
ChunkDelivery{},
RetrieveRequest{},
},
}
ErrNoPeerFound = errors.New("no peer found")
)
// Retrieval holds state and handles protocol messages for the `bzz-retrieve` protocol
type Retrieval struct {
mtx sync.Mutex
netStore *storage.NetStore
kad *network.Kademlia
peers map[enode.ID]*Peer
spec *protocols.Spec //this protocol's spec
quit chan struct{} // termination
}
// NewRetrieval returns a new instance of the retrieval protocol handler
func New(kad *network.Kademlia, ns *storage.NetStore) *Retrieval {
return &Retrieval{
kad: kad,
peers: make(map[enode.ID]*Peer),
netStore: ns,
quit: make(chan struct{}),
spec: spec,
}
}
func (r *Retrieval) addPeer(p *Peer) {
r.mtx.Lock()
defer r.mtx.Unlock()
r.peers[p.ID()] = p
}
func (r *Retrieval) removePeer(p *Peer) {
r.mtx.Lock()
defer r.mtx.Unlock()
delete(r.peers, p.ID())
}
func (r *Retrieval) getPeer(id enode.ID) *Peer {
r.mtx.Lock()
defer r.mtx.Unlock()
return r.peers[id]
}
// Run protocol function
func (r *Retrieval) Run(p *p2p.Peer, rw p2p.MsgReadWriter) error {
peer := protocols.NewPeer(p, rw, r.spec)
bp := network.NewBzzPeer(peer)
sp := NewPeer(bp)
r.addPeer(sp)
defer r.removePeer(sp)
return peer.Run(r.handleMsg(sp))
}
func (r *Retrieval) handleMsg(p *Peer) func(context.Context, interface{}) error {
return func(ctx context.Context, msg interface{}) error {
switch msg := msg.(type) {
case *RetrieveRequest:
go r.handleRetrieveRequest(ctx, p, msg)
case *ChunkDelivery:
go r.handleChunkDelivery(ctx, p, msg)
}
return nil
}
}
// getOriginPo returns the originPo if the incoming Request has an Origin
// if our node is the first node that requests this chunk, then we don't have an Origin,
// and return -1
// this is used only for tracing, and can probably be refactor so that we don't have to
// iterater over Kademlia
func (r *Retrieval) getOriginPo(req *storage.Request) int {
log.Trace("retrieval.getOriginPo", "req.Addr", req.Addr)
originPo := -1
r.kad.EachConn(req.Addr[:], 255, func(p *network.Peer, po int) bool {
id := p.ID()
// get po between chunk and origin
if bytes.Equal(req.Origin.Bytes(), id.Bytes()) {
originPo = po
return false
}
return true
})
return originPo
}
// findPeer finds a peer we need to ask for a specific chunk from according to our kademlia
func (r *Retrieval) findPeer(ctx context.Context, req *storage.Request) (retPeer *network.Peer, err error) {
log.Trace("retrieval.findPeer", "req.Addr", req.Addr)
osp, _ := ctx.Value("remote.fetch").(opentracing.Span)
// originPo - proximity of the node that made the request; -1 if the request originator is our node;
// myPo - this node's proximity with the requested chunk
// selectedPeerPo - kademlia suggested node's proximity with the requested chunk (computed further below)
originPo := r.getOriginPo(req)
myPo := chunk.Proximity(req.Addr, r.kad.BaseAddr())
selectedPeerPo := -1
depth := r.kad.NeighbourhoodDepth()
if osp != nil {
osp.LogFields(olog.Int("originPo", originPo))
osp.LogFields(olog.Int("depth", depth))
osp.LogFields(olog.Int("myPo", myPo))
}
// do not forward requests if origin proximity is bigger than our node's proximity
// this means that origin is closer to the chunk
if originPo > myPo {
return nil, errors.New("not forwarding request, origin node is closer to chunk than this node")
}
r.kad.EachConn(req.Addr[:], 255, func(p *network.Peer, po int) bool {
id := p.ID()
// skip light nodes
if p.LightNode {
return true
}
// do not send request back to peer who asked us. maybe merge with SkipPeer at some point
if bytes.Equal(req.Origin.Bytes(), id.Bytes()) {
return true
}
// skip peers that we have already tried
if req.SkipPeer(id.String()) {
log.Trace("findpeer skip peer", "peer", id, "ref", req.Addr.String())
return true
}
if myPo < depth { // chunk is NOT within the neighbourhood
if po <= myPo { // always choose a peer strictly closer to chunk than us
log.Trace("findpeer1a", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
return false
} else {
log.Trace("findpeer1b", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
}
} else { // chunk IS WITHIN neighbourhood
if po < depth { // do not select peer outside the neighbourhood. But allows peers further from the chunk than us
log.Trace("findpeer2a", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
return false
} else if po <= originPo { // avoid loop in neighbourhood, so not forward when a request comes from the neighbourhood
log.Trace("findpeer2b", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
return false
} else {
log.Trace("findpeer2c", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
}
}
// if selected peer is not in the depth (2nd condition; if depth <= po, then peer is in nearest neighbourhood)
// and they have a lower po than ours, return error
if po < myPo && depth > po {
log.Trace("findpeer4 skip peer because origin was closer", "originpo", originPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
err = fmt.Errorf("not asking peers further away from origin; ref=%s originpo=%v po=%v depth=%v myPo=%v", req.Addr.String(), originPo, po, depth, myPo)
return false
}
// if chunk falls in our nearest neighbourhood (1st condition), but suggested peer is not in
// the nearest neighbourhood (2nd condition), don't forward the request to suggested peer
if depth <= myPo && depth > po {
log.Trace("findpeer5 skip peer because depth", "originpo", originPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
err = fmt.Errorf("not going outside of depth; ref=%s originpo=%v po=%v depth=%v myPo=%v", req.Addr.String(), originPo, po, depth, myPo)
return false
}
retPeer = p
// sp could be nil, if we encountered a peer that is not registered for delivery, i.e. doesn't support the `stream` protocol
// if sp is not nil, then we have selected the next peer and we stop iterating
// if sp is nil, we continue iterating
if retPeer != nil {
selectedPeerPo = po
return false
}
// continue iterating
return true
})
if osp != nil {
osp.LogFields(olog.Int("selectedPeerPo", selectedPeerPo))
}
if err != nil {
return nil, err
}
if retPeer == nil {
return nil, ErrNoPeerFound
}
return retPeer, nil
}
// handleRetrieveRequest handles an incoming retrieve request from a certain Peer
// if the chunk is found in the localstore it is served immediately, otherwise
// it results in a new retrieve request to candidate peers in our kademlia
func (r *Retrieval) handleRetrieveRequest(ctx context.Context, p *Peer, msg *RetrieveRequest) {
p.logger.Debug("retrieval.handleRetrieveRequest", "ref", msg.Addr)
handleRetrieveRequestMsgCount.Inc(1)
ctx, osp := spancontext.StartSpan(
ctx,
"handle.retrieve.request")
osp.LogFields(olog.String("ref", msg.Addr.String()))
defer osp.Finish()
ctx, cancel := context.WithTimeout(ctx, timeouts.FetcherGlobalTimeout)
defer cancel()
req := &storage.Request{
Addr: msg.Addr,
Origin: p.ID(),
}
chunk, err := r.netStore.Get(ctx, chunk.ModeGetRequest, req)
if err != nil {
retrieveChunkFail.Inc(1)
p.logger.Debug("netstore.Get can not retrieve chunk", "ref", msg.Addr, "err", err)
return
}
p.logger.Trace("retrieval.handleRetrieveRequest - delivery", "ref", msg.Addr)
deliveryMsg := &ChunkDelivery{
Addr: chunk.Address(),
SData: chunk.Data(),
}
err = p.Send(ctx, deliveryMsg)
if err != nil {
p.logger.Error("retrieval.handleRetrieveRequest - peer delivery failed", "ref", msg.Addr, "err", err)
osp.LogFields(olog.Bool("delivered", false))
return
}
osp.LogFields(olog.Bool("delivered", true))
}
// handleChunkDelivery handles a ChunkDelivery message from a certain peer
// if the chunk proximity order in relation to our base address is within depth
// we treat the chunk as a chunk received in syncing
func (r *Retrieval) handleChunkDelivery(ctx context.Context, p *Peer, msg *ChunkDelivery) error {
p.logger.Debug("retrieval.handleChunkDelivery", "ref", msg.Addr)
var osp opentracing.Span
ctx, osp = spancontext.StartSpan(
ctx,
"handle.chunk.delivery")
processReceivedChunksCount.Inc(1)
// record the last time we received a chunk delivery message
lastReceivedRetrieveChunksMsg.Update(time.Now().UnixNano())
// count how many chunks we receive for retrieve requests per peer
peermetric := fmt.Sprintf("chunk.delivery.%x", p.BzzAddr.Over()[:16])
metrics.GetOrRegisterCounter(peermetric, nil).Inc(1)
peerPO := chunk.Proximity(p.BzzAddr.Over(), msg.Addr)
po := chunk.Proximity(r.kad.BaseAddr(), msg.Addr)
depth := r.kad.NeighbourhoodDepth()
var mode chunk.ModePut
// chunks within the area of responsibility should always sync
// https://github.com/ethersphere/go-ethereum/pull/1282#discussion_r269406125
if po >= depth || peerPO < po {
mode = chunk.ModePutSync
} else {
// do not sync if peer that is sending us a chunk is closer to the chunk then we are
mode = chunk.ModePutRequest
}
p.logger.Trace("handle.chunk.delivery", "ref", msg.Addr)
go func() {
defer osp.Finish()
p.logger.Trace("handle.chunk.delivery", "put", msg.Addr)
_, err := r.netStore.Put(ctx, mode, storage.NewChunk(msg.Addr, msg.SData))
if err != nil {
if err == storage.ErrChunkInvalid {
p.Drop()
}
}
p.logger.Trace("handle.chunk.delivery", "done put", msg.Addr, "err", err)
}()
return nil
}
// RequestFromPeers sends a chunk retrieve request to the next found peer
func (r *Retrieval) RequestFromPeers(ctx context.Context, req *storage.Request, localID enode.ID) (*enode.ID, error) {
log.Debug("retrieval.requestFromPeers", "req.Addr", req.Addr)
metrics.GetOrRegisterCounter("network.retrieve.request_from_peers", nil).Inc(1)
const maxFindPeerRetries = 5
retries := 0
FINDPEER:
sp, err := r.findPeer(ctx, req)
if err != nil {
log.Trace(err.Error())
return nil, err
}
protoPeer := r.getPeer(sp.ID())
if protoPeer == nil {
retries++
if retries == maxFindPeerRetries {
log.Error("max find peer retries reached", "max retries", maxFindPeerRetries)
return nil, ErrNoPeerFound
}
goto FINDPEER
}
ret := RetrieveRequest{
Addr: req.Addr,
}
protoPeer.logger.Trace("sending retrieve request", "ref", ret.Addr, "origin", localID)
err = protoPeer.Send(ctx, ret)
if err != nil {
protoPeer.logger.Error("error sending retrieve request to peer", "err", err)
return nil, err
}
spID := protoPeer.ID()
return &spID, nil
}
func (r *Retrieval) Start(server *p2p.Server) error {
log.Info("starting bzz-retrieve")
return nil
}
func (r *Retrieval) Stop() error {
log.Info("shutting down bzz-retrieve")
close(r.quit)
return nil
}
func (r *Retrieval) Protocols() []p2p.Protocol {
return []p2p.Protocol{
{
Name: r.spec.Name,
Version: r.spec.Version,
Length: r.spec.Length(),
Run: r.Run,
},
}
}
func (r *Retrieval) APIs() []rpc.API {
return nil
}

View File

@ -0,0 +1,398 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package retrieval
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethersphere/swarm/chunk"
"github.com/ethersphere/swarm/network"
"github.com/ethersphere/swarm/network/simulation"
"github.com/ethersphere/swarm/p2p/protocols"
"github.com/ethersphere/swarm/state"
"github.com/ethersphere/swarm/storage"
"github.com/ethersphere/swarm/storage/localstore"
"github.com/ethersphere/swarm/storage/mock"
"github.com/ethersphere/swarm/testutil"
"golang.org/x/crypto/sha3"
)
var (
loglevel = flag.Int("loglevel", 5, "verbosity of logs")
bucketKeyFileStore = simulation.BucketKey("filestore")
bucketKeyNetstore = simulation.BucketKey("netstore")
hash0 = sha3.Sum256([]byte{0})
)
func init() {
flag.Parse()
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
}
// TestChunkDelivery brings up two nodes, stores a few chunks on the first node, then tries to retrieve them through the second node
func TestChunkDelivery(t *testing.T) {
chunkCount := 10
filesize := chunkCount * 4096
sim := simulation.NewBzzInProc(map[string]simulation.ServiceFunc{
"bzz-retrieve": newBzzRetrieveWithLocalstore,
})
defer sim.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := sim.AddNode()
if err != nil {
t.Fatal(err)
}
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
nodeIDs := sim.UpNodeIDs()
log.Debug("uploader node", "enode", nodeIDs[0])
fs := sim.MustNodeItem(nodeIDs[0], bucketKeyFileStore).(*storage.FileStore)
//put some data into just the first node
data := make([]byte, filesize)
if _, err := io.ReadFull(rand.Reader, data); err != nil {
t.Fatalf("reading from crypto/rand failed: %v", err.Error())
}
refs, err := getAllRefs(data)
if err != nil {
return err
}
log.Trace("got all refs", "refs", refs)
_, wait, err := fs.Store(context.Background(), bytes.NewReader(data), int64(filesize), false)
if err != nil {
return err
}
if err := wait(context.Background()); err != nil {
return err
}
id, err := sim.AddNode()
if err != nil {
return err
}
err = sim.Net.Connect(id, nodeIDs[0])
if err != nil {
return err
}
nodeIDs = sim.UpNodeIDs()
if len(nodeIDs) != 2 {
return fmt.Errorf("wrong number of nodes, expected %d got %d", 2, len(nodeIDs))
}
// allow the two nodes time to set up the protocols otherwise kademlias will be empty when retrieve requests happen
time.Sleep(50 * time.Millisecond)
log.Debug("fetching through node", "enode", nodeIDs[1])
ns := sim.MustNodeItem(nodeIDs[1], bucketKeyNetstore).(*storage.NetStore)
ctr := 0
for _, ch := range refs {
ctr++
_, err := ns.Get(context.Background(), chunk.ModeGetRequest, storage.NewRequest(ch))
if err != nil {
return err
}
}
return nil
})
if result.Error != nil {
t.Fatal(result.Error)
}
}
// TestDeliveryForwarding tests that chunk delivery forwarding requests happen. It creates three nodes (fetching, forwarding and uploading)
// where po(fetching,forwarding) = 1 and po(forwarding,uploading) = 1, then uploads chunks to the uploading node, afterwards
// tries to retrieve the relevant chunks (ones with po = 0 to fetching i.e. no bits in common with fetching and with
// po >= 1 with uploading i.e. with 1 bit or more in common with the uploading)
func TestDeliveryForwarding(t *testing.T) {
chunkCount := 100
filesize := chunkCount * 4096
sim, uploader, forwarder, fetcher := setupTestDeliveryForwardingSimulation(t)
defer sim.Close()
log.Debug("test delivery forwarding", "uploader", uploader, "forwarder", forwarder, "fetcher", fetcher)
uploaderNodeStore := sim.MustNodeItem(uploader, bucketKeyFileStore).(*storage.FileStore)
fetcherBase := sim.MustNodeItem(fetcher, simulation.BucketKeyKademlia).(*network.Kademlia).BaseAddr()
uploaderBase := sim.MustNodeItem(fetcher, simulation.BucketKeyKademlia).(*network.Kademlia).BaseAddr()
ctx := context.Background()
_, wait, err := uploaderNodeStore.Store(ctx, testutil.RandomReader(101010, filesize), int64(filesize), false)
if err != nil {
t.Fatal(err)
}
if err = wait(ctx); err != nil {
t.Fatal(err)
}
chunks, err := getChunks(uploaderNodeStore.ChunkStore)
if err != nil {
t.Fatal(err)
}
for c := range chunks {
addr, err := hex.DecodeString(c)
if err != nil {
t.Fatal(err)
}
// try to retrieve all of the chunks which have no bits in common with the
// fetcher, but have more than one bit in common with the uploader node
if chunk.Proximity(addr, fetcherBase) == 0 && chunk.Proximity(addr, uploaderBase) >= 1 {
req := storage.NewRequest(chunk.Address(addr))
fetcherNetstore := sim.MustNodeItem(fetcher, bucketKeyNetstore).(*storage.NetStore)
_, err := fetcherNetstore.Get(ctx, chunk.ModeGetRequest, req)
if err != nil {
t.Fatal(err)
}
}
}
}
func setupTestDeliveryForwardingSimulation(t *testing.T) (sim *simulation.Simulation, uploader, forwarder, fetching enode.ID) {
sim = simulation.NewBzzInProc(map[string]simulation.ServiceFunc{
"bzz-retrieve": newBzzRetrieveWithLocalstore,
})
fetching, err := sim.AddNode()
if err != nil {
t.Fatal(err)
}
fetcherBase := sim.MustNodeItem(fetching, simulation.BucketKeyKademlia).(*network.Kademlia).BaseAddr()
override := func(o *adapters.NodeConfig) func(*adapters.NodeConfig) {
return func(c *adapters.NodeConfig) {
*o = *c
}
}
// create a node that will be in po 1 from fetcher
forwarderConfig := testutil.NodeConfigAtPo(t, fetcherBase, 1)
forwarder, err = sim.AddNode(override(forwarderConfig))
if err != nil {
t.Fatal(err)
}
err = sim.Net.Connect(fetching, forwarder)
if err != nil {
t.Fatal(err)
}
forwarderBase := sim.MustNodeItem(forwarder, simulation.BucketKeyKademlia).(*network.Kademlia).BaseAddr()
// create a node on which the files will be stored at po 1 in relation to the forwarding node
uploaderConfig := testutil.NodeConfigAtPo(t, forwarderBase, 1)
uploader, err = sim.AddNode(override(uploaderConfig))
if err != nil {
t.Fatal(err)
}
err = sim.Net.Connect(forwarder, uploader)
if err != nil {
t.Fatal(err)
}
return sim, uploader, forwarder, fetching
}
// if there is one peer in the Kademlia, RequestFromPeers should return it
func TestRequestFromPeers(t *testing.T) {
dummyPeerID := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8")
addr := network.RandomAddr()
to := network.NewKademlia(addr.OAddr, network.NewKadParams())
protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil)
peer := network.NewPeer(&network.BzzPeer{
BzzAddr: network.RandomAddr(),
LightNode: false,
Peer: protocolsPeer,
}, to)
to.On(peer)
s := New(to, nil)
req := storage.NewRequest(storage.Address(hash0[:]))
id, err := s.findPeer(context.Background(), req)
if err != nil {
t.Fatal(err)
}
if id.ID() != dummyPeerID {
t.Fatalf("Expected an id, got %v", id)
}
}
// RequestFromPeers should not return light nodes
func TestRequestFromPeersWithLightNode(t *testing.T) {
dummyPeerID := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8")
addr := network.RandomAddr()
to := network.NewKademlia(addr.OAddr, network.NewKadParams())
protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil)
// setting up a lightnode
peer := network.NewPeer(&network.BzzPeer{
BzzAddr: network.RandomAddr(),
LightNode: true,
Peer: protocolsPeer,
}, to)
to.On(peer)
r := New(to, nil)
req := storage.NewRequest(storage.Address(hash0[:]))
// making a request which should return with "no peer found"
_, err := r.findPeer(context.Background(), req)
if err != ErrNoPeerFound {
t.Fatalf("expected '%v', got %v", ErrNoPeerFound, err)
}
}
func newBzzRetrieveWithLocalstore(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
n := ctx.Config.Node()
addr := network.NewAddr(n)
localStore, localStoreCleanup, err := newTestLocalStore(n.ID(), addr, nil)
if err != nil {
return nil, nil, err
}
var kad *network.Kademlia
if kv, ok := bucket.Load(simulation.BucketKeyKademlia); ok {
kad = kv.(*network.Kademlia)
} else {
kad = network.NewKademlia(addr.Over(), network.NewKadParams())
bucket.Store(simulation.BucketKeyKademlia, kad)
}
netStore := storage.NewNetStore(localStore, n.ID())
lnetStore := storage.NewLNetStore(netStore)
fileStore := storage.NewFileStore(lnetStore, storage.NewFileStoreParams(), chunk.NewTags())
var store *state.DBStore
// Use on-disk DBStore to reduce memory consumption in race tests.
dir, err := ioutil.TempDir("", "statestore-")
if err != nil {
return nil, nil, err
}
store, err = state.NewDBStore(dir)
if err != nil {
return nil, nil, err
}
r := New(kad, netStore)
netStore.RemoteGet = r.RequestFromPeers
bucket.Store(bucketKeyFileStore, fileStore)
bucket.Store(bucketKeyNetstore, netStore)
bucket.Store(simulation.BucketKeyKademlia, kad)
cleanup = func() {
localStore.Close()
localStoreCleanup()
store.Close()
os.RemoveAll(dir)
}
return r, cleanup, nil
}
func newTestLocalStore(id enode.ID, addr *network.BzzAddr, globalStore mock.GlobalStorer) (localStore *localstore.DB, cleanup func(), err error) {
dir, err := ioutil.TempDir("", "localstore-")
if err != nil {
return nil, nil, err
}
cleanup = func() {
os.RemoveAll(dir)
}
var mockStore *mock.NodeStore
if globalStore != nil {
mockStore = globalStore.NewNodeStore(common.BytesToAddress(id.Bytes()))
}
localStore, err = localstore.New(dir, addr.Over(), &localstore.Options{
MockStore: mockStore,
})
if err != nil {
cleanup()
return nil, nil, err
}
return localStore, cleanup, nil
}
func getAllRefs(testData []byte) (storage.AddressCollection, error) {
datadir, err := ioutil.TempDir("", "chunk-debug")
if err != nil {
return nil, err
}
defer os.RemoveAll(datadir)
fileStore, cleanup, err := storage.NewLocalFileStore(datadir, make([]byte, 32), chunk.NewTags())
if err != nil {
return nil, err
}
defer cleanup()
reader := bytes.NewReader(testData)
return fileStore.GetAllReferences(context.Background(), reader, false)
}
func getChunks(store chunk.Store) (chunks map[string]struct{}, err error) {
chunks = make(map[string]struct{})
for po := uint8(0); po <= chunk.MaxPO; po++ {
last, err := store.LastPullSubscriptionBinID(uint8(po))
if err != nil {
return nil, err
}
if last == 0 {
continue
}
ch, _ := store.SubscribePull(context.Background(), po, 0, last)
for c := range ch {
addr := c.Address.Hex()
if _, ok := chunks[addr]; ok {
return nil, fmt.Errorf("duplicate chunk %s", addr)
}
chunks[addr] = struct{}{}
}
}
return chunks, nil
}

30
network/retrieval/wire.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package retrieval
import "github.com/ethersphere/swarm/storage"
// RetrieveRequestMsg is the protocol msg for chunk retrieve requests
type RetrieveRequest struct {
Addr storage.Address
}
// ChunkDelivery is the protocol msg for delivering a solicited chunk to a peer
type ChunkDelivery struct {
Addr storage.Address
SData []byte // the stored chunk Data (incl size)
}

View File

@ -16,7 +16,11 @@
package simulation
import "github.com/ethereum/go-ethereum/p2p/enode"
import (
"fmt"
"github.com/ethereum/go-ethereum/p2p/enode"
)
// BucketKey is the type that should be used for keys in simulation buckets.
type BucketKey string
@ -32,6 +36,24 @@ func (s *Simulation) NodeItem(id enode.ID, key interface{}) (value interface{},
return s.buckets[id].Load(key)
}
// MustNodeItem returns the item set in ServiceFunc for a particular node or panics in case
// the item is not found
func (s *Simulation) MustNodeItem(id enode.ID, key interface{}) (value interface{}) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.buckets[id]; !ok {
e := fmt.Errorf("cannot find node id %s in bucket", id.String())
panic(e)
}
if v, ok := s.buckets[id].Load(key); ok {
return v
} else {
e := fmt.Errorf("cannot find key %v on node bucket", key)
panic(e)
}
}
// SetNodeItem sets a new item associated with the node with provided NodeID.
// Buckets should be used to avoid managing separate simulation global state.
func (s *Simulation) SetNodeItem(id enode.ID, key interface{}, value interface{}) {

View File

@ -99,6 +99,34 @@ func NewInProc(services map[string]ServiceFunc) (s *Simulation) {
return s
}
// NewBzzInProc is the same as NewInProc but injects bzz as a default protocol
func NewBzzInProc(services map[string]ServiceFunc) (s *Simulation) {
services["bzz"] = func(ctx *adapters.ServiceContext, bucket *sync.Map) (node.Service, func(), error) {
addr := network.NewAddr(ctx.Config.Node())
hp := network.NewHiveParams()
hp.KeepAliveInterval = time.Duration(200) * time.Millisecond
hp.Discovery = false
var kad *network.Kademlia
// check if another kademlia already exists and load it if necessary - we dont want two independent copies of it
if kv, ok := bucket.Load(BucketKeyKademlia); ok {
kad = kv.(*network.Kademlia)
} else {
kad = network.NewKademlia(addr.Over(), network.NewKadParams())
bucket.Store(BucketKeyKademlia, kad)
}
config := &network.BzzConfig{
OverlayAddr: addr.Over(),
UnderlayAddr: addr.Under(),
HiveParams: hp,
}
return network.NewBzz(config, kad, nil, nil, nil), nil, nil
}
return NewInProc(services)
}
// NewExec does the same as New but lets the caller specify the adapter to use
func NewExec(services map[string]ServiceFunc) (s *Simulation, err error) {
s = &Simulation{

View File

@ -131,6 +131,10 @@ func (d *Delivery) handleChunkDeliveryMsg(ctx context.Context, sp *Peer, req int
var mode chunk.ModePut
switch r := req.(type) {
case *ChunkDeliveryMsgRetrieval:
// count how many chunks we receive for retrieve requests per peer
peermetric := fmt.Sprintf("chunk.delivery.%x", sp.BzzAddr.Over()[:16])
metrics.GetOrRegisterCounter(peermetric, nil).Inc(1)
msg = (*ChunkDeliveryMsg)(r)
peerPO := chunk.Proximity(sp.BzzAddr.Over(), msg.Addr)
po := chunk.Proximity(d.kad.BaseAddr(), msg.Addr)

View File

@ -231,7 +231,7 @@ func (p *Peer) handleOfferedHashesMsg(ctx context.Context, req *OfferedHashesMsg
ctr++
// set the bit, so create a request
want.Set(i/HashSize, true)
want.Set(i / HashSize)
log.Trace("need data", "ref", fmt.Sprintf("%x", hash), "request", true)
// measure how long it takes before we mark chunks for retrieval, and actually send the request

View File

@ -32,6 +32,7 @@ var testCases []testCase
// selects the peers for message forwarding, depending on the message address
// and kademlia constellation.
func TestForwardBasic(t *testing.T) {
t.Skip("Flaky on macOS on local machines")
baseAddrBytes := make([]byte, 32)
for i := 0; i < len(baseAddrBytes); i++ {
baseAddrBytes[i] = 0xFF

416
simulation/docker.go Normal file
View File

@ -0,0 +1,416 @@
package simulation
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm"
"github.com/ethersphere/swarm/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/jsonmessage"
)
const (
dockerP2PPort = 30399
dockerWebsocketPort = 8546
dockerHTTPPort = 8500
dockerPProfPort = 6060
)
// DockerAdapter is an adapter that can manage DockerNodes
type DockerAdapter struct {
client *client.Client
image string
config DockerAdapterConfig
}
// DockerAdapterConfig is the configuration that can be provided when
// initializing a DockerAdapter
type DockerAdapterConfig struct {
// BuildContext can be used to build a docker image
// from a Dockerfile and a context directory
BuildContext *DockerBuildContext `json:"build,omitempty"`
// DockerImage points to an existing docker image
// e.g. ethersphere/swarm:latest
DockerImage string `json:"image,omitempty"`
// DaemonAddr is the docker daemon address
DaemonAddr string `json:"daemonAddr,omitempty"`
}
// DockerBuildContext defines the build context to build
// local docker images
type DockerBuildContext struct {
// Dockefile is the path to the dockerfile
Dockerfile string `json:"dockerfile"`
// Directory is the directory that will be used
// in the context of a docker build
Directory string `json:"directory"`
// Tag is used to tag the image
Tag string `json:"tag"`
}
// DockerNode is a node that was started via the DockerAdapter
type DockerNode struct {
config NodeConfig
adapter *DockerAdapter
info NodeInfo
ipAddr string
}
// DefaultDockerAdapterConfig returns the default configuration
// that uses the local docker daemon
func DefaultDockerAdapterConfig() DockerAdapterConfig {
return DockerAdapterConfig{
DaemonAddr: client.DefaultDockerHost,
}
}
// DefaultDockerBuildContext returns the default build context that uses a Dockerfile
func DefaultDockerBuildContext() DockerBuildContext {
return DockerBuildContext{
Dockerfile: "Dockerfile",
Directory: ".",
}
}
// IsDockerAvailable can be used to check the connectivity to the docker daemon
func IsDockerAvailable(daemonAddr string) bool {
cli, err := client.NewClientWithOpts(
client.WithHost(daemonAddr),
client.WithAPIVersionNegotiation(),
)
if err != nil {
return false
}
_, err = cli.ServerVersion(context.Background())
if err != nil {
return false
}
cli.Close()
return true
}
// NewDockerAdapter creates a DockerAdapter by receiving a DockerAdapterConfig
func NewDockerAdapter(config DockerAdapterConfig) (*DockerAdapter, error) {
if config.DockerImage != "" && config.BuildContext != nil {
return nil, fmt.Errorf("only one can be defined: BuildContext (%v) or DockerImage(%s)",
config.BuildContext, config.DockerImage)
}
if config.DockerImage == "" && config.BuildContext == nil {
return nil, errors.New("required: BuildContext or ExecutablePath")
}
// Create docker client
cli, err := client.NewClientWithOpts(
client.WithHost(config.DaemonAddr),
client.WithAPIVersionNegotiation(),
)
if err != nil {
return nil, fmt.Errorf("could not create the docker client: %v", err)
}
// Figure out which docker image should be used
image := config.DockerImage
// Build docker image
if config.BuildContext != nil {
var err error
image, err = buildImage(*config.BuildContext, config.DaemonAddr)
if err != nil {
return nil, fmt.Errorf("could not build the docker image: %v", err)
}
}
// Pull docker image
if config.DockerImage != "" {
reader, err := cli.ImagePull(context.Background(), config.DockerImage, types.ImagePullOptions{})
if err != nil {
return nil, fmt.Errorf("pull image error: %v", err)
}
if _, err := io.Copy(os.Stdout, reader); err != nil && err != io.EOF {
log.Error("Error pulling docker image", "err", err)
}
}
return &DockerAdapter{
image: image,
client: cli,
config: config,
}, nil
}
// NewNode creates a new node
func (a DockerAdapter) NewNode(config NodeConfig) Node {
info := NodeInfo{
ID: config.ID,
}
node := &DockerNode{
config: config,
adapter: &a,
info: info,
}
return node
}
// Snapshot returns a snapshot of the adapter
func (a DockerAdapter) Snapshot() AdapterSnapshot {
return AdapterSnapshot{
Type: "docker",
Config: a.config,
}
}
// Info returns the node status
func (n *DockerNode) Info() NodeInfo {
return n.info
}
// Start starts the node
func (n *DockerNode) Start() error {
var err error
defer func() {
if err != nil {
log.Error("Stopping node due to errors", "err", err)
if err := n.Stop(); err != nil {
log.Error("Failed stopping node", "err", err)
}
}
}()
// Define arguments
args := []string{}
// Append user defined arguments
args = append(args, n.config.Args...)
// Append network ports arguments
args = append(args, "--pprofport", strconv.Itoa(dockerPProfPort))
args = append(args, "--bzzport", strconv.Itoa(dockerHTTPPort))
args = append(args, "--ws")
// TODO: Can we get the APIs from somewhere instead of hardcoding them here?
args = append(args, "--wsapi", "admin,net,debug,bzz,accounting,hive")
args = append(args, "--wsport", strconv.Itoa(dockerWebsocketPort))
args = append(args, "--wsaddr", "0.0.0.0")
args = append(args, "--wsorigins", "*")
args = append(args, "--port", strconv.Itoa(dockerP2PPort))
args = append(args, "--natif", "eth0")
// Start the node via a container
ctx := context.Background()
dockercli := n.adapter.client
resp, err := dockercli.ContainerCreate(ctx, &container.Config{
Image: n.adapter.image,
Cmd: args,
Env: n.config.Env,
}, &container.HostConfig{}, nil, n.containerName())
if err != nil {
return fmt.Errorf("failed to create container %s: %v", n.containerName(), err)
}
if err := dockercli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("failed to start container %s: %v", n.containerName(), err)
}
// Get container logs
if n.config.Stderr != nil {
go func() {
// Stderr
stderr, err := dockercli.ContainerLogs(context.Background(), n.containerName(), types.ContainerLogsOptions{
ShowStderr: true,
ShowStdout: false,
Follow: true,
})
if err != nil && err != io.EOF {
log.Error("Error getting stderr container logs", "err", err)
}
defer stderr.Close()
if _, err := io.Copy(n.config.Stderr, stderr); err != nil && err != io.EOF {
log.Error("Error writing stderr container logs", "err", err)
}
}()
}
if n.config.Stdout != nil {
go func() {
// Stdout
stdout, err := dockercli.ContainerLogs(context.Background(), n.containerName(), types.ContainerLogsOptions{
ShowStderr: false,
ShowStdout: true,
Follow: true,
})
if err != nil && err != io.EOF {
log.Error("Error getting stdout container logs", "err", err)
}
defer stdout.Close()
if _, err := io.Copy(n.config.Stdout, stdout); err != nil && err != io.EOF {
log.Error("Error writing stdout container logs", "err", err)
}
}()
}
// Get container info
cinfo := types.ContainerJSON{}
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
cinfo, err = dockercli.ContainerInspect(ctx, n.containerName())
if err == nil {
break
}
}
if err != nil {
return fmt.Errorf("could not get container info: %v", err)
}
// Get the container IP addr
n.ipAddr = cinfo.NetworkSettings.IPAddress
// Wait for the node to start
client, err := n.rpcClient()
if err != nil {
return err
}
defer client.Close()
var swarminfo swarm.Info
err = client.Call(&swarminfo, "bzz_info")
if err != nil {
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
}
var p2pinfo p2p.NodeInfo
err = client.Call(&p2pinfo, "admin_nodeInfo")
if err != nil {
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
}
n.info = NodeInfo{
ID: n.config.ID,
Enode: strings.Replace(p2pinfo.Enode, "127.0.0.1", n.ipAddr, 1),
BzzAddr: swarminfo.BzzKey,
RPCListen: fmt.Sprintf("ws://%s:%d", n.ipAddr, dockerWebsocketPort),
HTTPListen: fmt.Sprintf("http://%s:%d", n.ipAddr, dockerHTTPPort),
PprofListen: fmt.Sprintf("http://%s:%d", n.ipAddr, dockerPProfPort),
}
return nil
}
// Stop stops the node
func (n *DockerNode) Stop() error {
cli := n.adapter.client
var stopTimeout = 30 * time.Second
err := cli.ContainerStop(context.Background(), n.containerName(), &stopTimeout)
if err != nil {
return fmt.Errorf("failed to stop container %s : %v", n.containerName(), err)
}
err = cli.ContainerRemove(context.Background(), n.containerName(), types.ContainerRemoveOptions{})
if err != nil {
return fmt.Errorf("failed to remove container %s : %v", n.containerName(), err)
}
return nil
}
// Snapshot returns a snapshot of the node
func (n *DockerNode) Snapshot() (NodeSnapshot, error) {
snap := NodeSnapshot{
Config: n.config,
}
adapterSnap := n.adapter.Snapshot()
snap.Adapter = &adapterSnap
return snap, nil
}
func (n *DockerNode) containerName() string {
return fmt.Sprintf("sim-docker-%s", n.config.ID)
}
func (n *DockerNode) rpcClient() (*rpc.Client, error) {
var client *rpc.Client
var err error
wsAddr := fmt.Sprintf("ws://%s:%d", n.ipAddr, dockerWebsocketPort)
for start := time.Now(); time.Since(start) < 30*time.Second; time.Sleep(50 * time.Millisecond) {
client, err = rpc.Dial(wsAddr)
if err == nil {
break
}
}
if client == nil {
return nil, fmt.Errorf("could not establish rpc connection. node %s: %v", n.config.ID, err)
}
return client, nil
}
// buildImage builds a docker image and returns the image identifier (tag).
func buildImage(buildContext DockerBuildContext, deamonAddr string) (string, error) {
// Connect to docker daemon
c, err := client.NewClientWithOpts(
client.WithHost(deamonAddr),
client.WithAPIVersionNegotiation(),
)
if err != nil {
return "", fmt.Errorf("could not create docker client: %v", err)
}
defer c.Close()
// Use directory for build context
ctx, err := archive.TarWithOptions(buildContext.Directory, &archive.TarOptions{})
if err != nil {
return "", err
}
// Default image tag
imageTag := "sim-docker:latest"
// Use a tag if one is defined
if buildContext.Tag != "" {
imageTag = buildContext.Tag
}
// Build image
opts := types.ImageBuildOptions{
SuppressOutput: false,
PullParent: true,
Tags: []string{imageTag},
Dockerfile: buildContext.Dockerfile,
}
buildResp, err := c.ImageBuild(context.Background(), ctx, opts)
if err != nil {
return "", fmt.Errorf("build error: %v", err)
}
// Parse build output
d := json.NewDecoder(buildResp.Body)
var event *jsonmessage.JSONMessage
for {
if err := d.Decode(&event); err != nil {
if err == io.EOF {
break
}
return "", err
}
log.Info("Docker build", "msg", event.Stream)
if event.Error != nil {
log.Error("Docker build error", "err", event.Error.Message)
return "", fmt.Errorf("failed to build docker image: %v", event.Error)
}
}
return imageTag, nil
}

53
simulation/docker_test.go Normal file
View File

@ -0,0 +1,53 @@
package simulation
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
func TestDockerAdapterBuild(t *testing.T) {
if !IsDockerAvailable(client.DefaultDockerHost) {
t.Skip("could not connect to the docker daemon")
}
// Create a docker client
c, err := dockerClient()
if err != nil {
t.Fatalf("could not create docker client: %v", err)
}
defer c.Close()
imageTag := "test-docker-adapter-build:latest"
config := DefaultDockerAdapterConfig()
// Build based on a Dockerfile
config.BuildContext = &DockerBuildContext{
Directory: "../",
Dockerfile: "Dockerfile",
Tag: imageTag,
}
// Create docker adapter: This will build the image
_, err = NewDockerAdapter(config)
if err != nil {
t.Fatalf("could not create docker adapter: %v", err)
}
// Cleanup image
_, err = c.ImageRemove(context.Background(), imageTag, types.ImageRemoveOptions{})
if err != nil {
t.Fatalf("could not delete docker image: %v", err)
}
}
// Create docker client
func dockerClient() (*client.Client, error) {
return client.NewClientWithOpts(
client.WithHost(client.DefaultDockerHost),
client.WithAPIVersionNegotiation(),
)
}

View File

@ -0,0 +1 @@
package cluster

View File

@ -0,0 +1,132 @@
package cluster
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/ethereum/go-ethereum/log"
"github.com/ethersphere/swarm/simulation"
colorable "github.com/mattn/go-colorable"
)
var (
nodes = flag.Int("nodes", 20, "number of nodes to create")
loglevel = flag.Int("loglevel", 3, "verbosity of logs")
rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs")
)
func init() {
flag.Parse()
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog))))
}
func TestCluster(t *testing.T) {
nodeCount := *nodes
// Test exec adapter
t.Run("exec", func(t *testing.T) {
execPath := "../../../build/bin/swarm"
if _, err := os.Stat(execPath); err != nil {
if os.IsNotExist(err) {
t.Skip("swarm binary not found. build it before running the test")
}
}
tmpdir, err := ioutil.TempDir("", "test-sim-exec")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
adapter, err := simulation.NewExecAdapter(simulation.ExecAdapterConfig{
ExecutablePath: execPath,
BaseDataDirectory: tmpdir,
})
if err != nil {
t.Fatalf("could not create exec adapter: %v", err)
}
startSimulation(t, adapter, nodeCount)
})
// Test docker adapter
t.Run("docker", func(t *testing.T) {
config := simulation.DefaultDockerAdapterConfig()
if !simulation.IsDockerAvailable(config.DaemonAddr) {
t.Skip("docker is not available, skipping test")
}
config.DockerImage = "ethersphere/swarm:edge"
adapter, err := simulation.NewDockerAdapter(config)
if err != nil {
t.Fatalf("could not create docker adapter: %v", err)
}
startSimulation(t, adapter, nodeCount)
})
// Test kubernetes adapter
t.Run("kubernetes", func(t *testing.T) {
config := simulation.DefaultKubernetesAdapterConfig()
if !simulation.IsKubernetesAvailable(config.KubeConfigPath) {
t.Skip("kubernetes is not available, skipping test")
}
config.Namespace = "simulation-test"
config.DockerImage = "ethersphere/swarm:edge"
adapter, err := simulation.NewKubernetesAdapter(config)
if err != nil {
t.Fatalf("could not create kubernetes adapter: %v", err)
}
startSimulation(t, adapter, nodeCount)
})
}
func startSimulation(t *testing.T, adapter simulation.Adapter, count int) {
sim := simulation.NewSimulation(adapter)
defer sim.StopAll()
// Common args used by all nodes
commonArgs := []string{
"--bzznetworkid", "599",
}
// Start a cluster with 'count' nodes and a bootnode
nodes, err := sim.CreateClusterWithBootnode("test", count, commonArgs)
if err != nil {
t.Fatal(err)
}
// Wait for all nodes to be considered healthy
err = sim.WaitForHealthyNetwork()
if err != nil {
t.Errorf("Failed to get healthy network: %v", err)
}
// Check hive output on the first node
client, err := sim.RPCClient(nodes[0].Info().ID)
if err != nil {
t.Errorf("Failed to get rpc client: %v", err)
}
var hive string
err = client.Call(&hive, "bzz_hive")
if err != nil {
t.Errorf("could not get hive info: %v", err)
}
snap, err := sim.Snapshot()
if err != nil {
t.Error(err)
}
b, err := json.Marshal(snap)
if err != nil {
t.Error(err)
}
fmt.Println(string(b))
fmt.Println(hive)
}

View File

@ -0,0 +1,641 @@
{
"defaultAdapter": {
"type": "docker",
"config": {
"image": "ethersphere/swarm:edge",
"daemonAddr": "unix:///var/run/docker.sock"
}
},
"nodes": [
{
"config": {
"id": "test-1",
"args": [
"--bzzkeyhex",
"675a1c7dbaa5ea1bbddd46ee1a06e55929175760c5824bb5a6992d0b96f84e9d",
"--nodekeyhex",
"1de361ad2978679213635f7f11e35dbd55a34979c931734ff9c3760cb5a3e6ad",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-11",
"args": [
"--bzzkeyhex",
"9c7f8bfdcfcc129baf677585703e6061189f9076e354d27f4b7f663e37fdc92b",
"--nodekeyhex",
"a8f2e98633c85a0b3a89facafbd35cb5d10c556bdf142daab63fb554a53715ec",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-2",
"args": [
"--bzzkeyhex",
"f60e0733d0cf9ae1e77bccd3256e709d9e552848d5ae19b1536beeac7157c514",
"--nodekeyhex",
"51135dc28378604cb7d7113f22114ac68f0dc235332649ac688fb1d13b622e3f",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-15",
"args": [
"--bzzkeyhex",
"5dfd72d5b92dabde0e58a4e6289be7c1e738c40dac8fafd151e56974f739a8f9",
"--nodekeyhex",
"70893f8a2c3fe1a24dcfb202a81e33dce03c3ded913d8fda92e6eb51c737e8de",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-10",
"args": [
"--bzzkeyhex",
"bbd17f109585e6df0a92953985059b791a7be2d01a973743a5e0100b7ab06715",
"--nodekeyhex",
"8592bf30d3a88dff49bdf57d583e0b235475151a958e6bf285520767ab7e7104",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-18",
"args": [
"--bzzkeyhex",
"2315b8bc42c2ba56ed4f88ec2a60a4b02bf3f8e94f2faac8dd4d642502c2056a",
"--nodekeyhex",
"975466b3530c2bf5940219d43525eadf5385df9f2defc1fa16b10d141f272b49",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-7",
"args": [
"--bzzkeyhex",
"494bceb4a9e564b1da2f00f5b6ff872dbfcfcbee4655e704e85ebfdd86bf29e5",
"--nodekeyhex",
"c677fdca73661ef5dc3b7773bbc52f7444b5ac9913230b7084a84f1a5f10b2cc",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-12",
"args": [
"--bzzkeyhex",
"dde10d9d208b0f70951e96941db0d9b245175f43ab5485cf0c8811d12f9b9cdb",
"--nodekeyhex",
"747ed88c92cdd8bf77aa0b09528038f457657ffb97e460895a7284b796da9dd4",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-9",
"args": [
"--bzzkeyhex",
"10b0fff107a590a424e5e74abdd119406ed096b7e600cbe04ff67584f9d55c51",
"--nodekeyhex",
"7ce5b908dfe70f5103948c413debc0fa03f50a6426b51da158438922abf3ea10",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-4",
"args": [
"--bzzkeyhex",
"3b0f49376815b1952f3c73fe6fd01a93e48d4bfac5f871c4712e092181549d91",
"--nodekeyhex",
"7bfe1dd5c1bbe83d9e29828e58e8cf9a431584a7d34c7bfb795a027a4adced8a",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-16",
"args": [
"--bzzkeyhex",
"46ed3a44c6fa46d4a6368b3d915134b185986ac7627a70b970f787c8896d6c6a",
"--nodekeyhex",
"e48066d163683429380f8b48fea282e25a184c9a50a5dbf24d85186469ca795c",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-19",
"args": [
"--bzzkeyhex",
"9314b3cc4d2cfa01fd044df334ec840f999f0ebb11f7a8a184786cef17703fc2",
"--nodekeyhex",
"4775f2c8a3cec55208c14d0a9b7fcdaf2447513142e69e03a3e8e2a759de0b55",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-14",
"args": [
"--bzzkeyhex",
"c54019ce7e3692db5f7d3531c6c00c96b9b744acf520ebd124194a5789c732d9",
"--nodekeyhex",
"94b5c4e9fd4496bdc3f1bd673cca9cbb2f4afe354a8d6ebb50d198798f0f4a45",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-13",
"args": [
"--bzzkeyhex",
"c7f8d404fee2b04242534a3486b1cf031ce6af150a075ba943d50bf1968dd85a",
"--nodekeyhex",
"24ae9cdc2ceea666fc588586b0e062721744562495e064fccb0ada19481f133d",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-17",
"args": [
"--bzzkeyhex",
"d48f7cb8ac5be805e700f1575cb644ea5cf05413dbdd9f969c43d1d54a9df29e",
"--nodekeyhex",
"8f85b38e6c6034d7c3c23d1391aaeae3eb3d680803b199cb716388de5fde3cf5",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-8",
"args": [
"--bzzkeyhex",
"ac7c28909be33eab4fccf5f39e808dd0a96ae412a3e7fe52f040ebb4d4d4d6f0",
"--nodekeyhex",
"bbef6fca5bc85f91d6315626fab3fdecdad8abfa3f38b216f98ee92489625a7c",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-5",
"args": [
"--bzzkeyhex",
"1a16fab4e3cbbc217db7dcb1bddb0ef12dd28c48c8a50bd4bb5550beaa80c7a0",
"--nodekeyhex",
"19234026cedfdfb10cf682f6491ff4c876411804949ac1b0dd5f1faee66f0bf3",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-3",
"args": [
"--bzzkeyhex",
"6410c2eadadac52be6a261a0e72060e5488f9165b24cc4c1764ad7a5ef6c0532",
"--nodekeyhex",
"6a3997bc8a3884797f18c8e4e345470db695d827a923975e5ce4dfb3c5d09d1e",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-0",
"args": [
"--bzzkeyhex",
"118ecf1dd2ebd700975af6de7dff806a3fcd41bc82987db96c45264694ec4276",
"--nodekeyhex",
"3422448fa8b3e6e41ea51a84262917d38d54e2c4345ec95d0586d741d31f91b5",
"--bzznetworkid",
"599"
]
}
},
{
"config": {
"id": "test-6",
"args": [
"--bzzkeyhex",
"66fa75cc080f0b5115c165004310378e98bb90cd652d4f9d92c71c489995942f",
"--nodekeyhex",
"e1a6f1bb9ed90cfa534dc8abb5088afa010acd7585837b3a8ea19c31c38da068",
"--bzznetworkid",
"599"
]
}
}
],
"connections": [
{
"from": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3",
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
},
{
"from": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3",
"to": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa"
},
{
"from": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3",
"to": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54"
},
{
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
},
{
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
"to": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8"
},
{
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
},
{
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
},
{
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
},
{
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
},
{
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
"to": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af"
},
{
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
},
{
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
},
{
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
},
{
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
"to": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2"
},
{
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
"to": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3"
},
{
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
"to": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd"
},
{
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
},
{
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
"to": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2"
},
{
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54",
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
},
{
"from": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54",
"to": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2"
},
{
"from": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa",
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
},
{
"from": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa",
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
},
{
"from": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa",
"to": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54"
},
{
"from": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
},
{
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
},
{
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
},
{
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
},
{
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
},
{
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946",
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
},
{
"from": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946",
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
},
{
"from": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
},
{
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
},
{
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
"to": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa"
},
{
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
"to": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2"
},
{
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd",
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
},
{
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
},
{
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
"to": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8"
},
{
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
},
{
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
},
{
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
"to": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091"
},
{
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
},
{
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
"to": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3"
},
{
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
},
{
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
"to": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa"
},
{
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
"to": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54"
},
{
"from": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2",
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
},
{
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
"to": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8"
},
{
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
},
{
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
"to": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265"
},
{
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
"to": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091"
},
{
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
},
{
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
},
{
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
"to": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3"
},
{
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
"to": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8"
},
{
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
},
{
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
},
{
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
},
{
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2",
"to": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa"
},
{
"from": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d",
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
},
{
"from": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d",
"to": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265"
},
{
"from": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d",
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
},
{
"from": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a",
"to": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af"
},
{
"from": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a",
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
},
{
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
"to": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2"
},
{
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
"to": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3"
},
{
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
},
{
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
},
{
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
"to": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2"
},
{
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
}
]
}

View File

@ -0,0 +1,55 @@
package snapshot
import (
"fmt"
"testing"
"github.com/ethersphere/swarm/simulation"
)
func TestDockerSnapshotFromFile(t *testing.T) {
snap, err := simulation.LoadSnapshotFromFile("docker.json")
if err != nil {
t.Fatal(err)
}
if !simulation.IsDockerAvailable(snap.DefaultAdapter.Config.(simulation.DockerAdapterConfig).DaemonAddr) {
t.Skip("docker is not available, skipping test")
}
sim, err := simulation.NewSimulationFromSnapshot(snap)
if err != nil {
t.Fatal(err)
}
defer func() {
err = sim.StopAll()
if err != nil {
t.Error(err)
}
}()
nodes := sim.GetAll()
if len(nodes) != len(snap.Nodes) {
t.Fatalf("Got %d . Expected %d nodes", len(nodes), len(snap.Nodes))
}
// Check hive output on the first node
node, err := sim.Get(simulation.NodeID("test-0"))
if err != nil {
t.Error(err)
}
client, err := sim.RPCClient(node.Info().ID)
if err != nil {
t.Errorf("Failed to get rpc client: %v", err)
}
var hive string
err = client.Call(&hive, "bzz_hive")
if err != nil {
t.Errorf("could not get hive info: %v", err)
}
fmt.Println(hive)
}

View File

@ -0,0 +1 @@
package snapshot

228
simulation/exec.go Normal file
View File

@ -0,0 +1,228 @@
package simulation
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"syscall"
"time"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm"
)
// ExecAdapter can manage local exec nodes
type ExecAdapter struct {
config ExecAdapterConfig
}
// ExecAdapterConfig is used to configure an ExecAdapter
type ExecAdapterConfig struct {
// Path to the executable
ExecutablePath string `json:"executable"`
// BaseDataDirectory stores all the nodes' data directories
BaseDataDirectory string `json:"basedir"`
}
// ExecNode is a node that is executed locally
type ExecNode struct {
adapter *ExecAdapter
config NodeConfig
cmd *exec.Cmd
info NodeInfo
}
// NewExecAdapter creates an ExecAdapter by receiving a ExecAdapterConfig
func NewExecAdapter(config ExecAdapterConfig) (*ExecAdapter, error) {
if _, err := os.Stat(config.BaseDataDirectory); os.IsNotExist(err) {
return nil, fmt.Errorf("'%s' directory does not exist", config.BaseDataDirectory)
}
if _, err := os.Stat(config.ExecutablePath); os.IsNotExist(err) {
return nil, fmt.Errorf("'%s' executable does not exist", config.ExecutablePath)
}
absExec, err := filepath.Abs(config.ExecutablePath)
if err != nil {
return nil, fmt.Errorf("could not get absolute path for %s: %v", config.ExecutablePath, err)
}
config.ExecutablePath = absExec
absDir, err := filepath.Abs(config.BaseDataDirectory)
if err != nil {
return nil, fmt.Errorf("could not get absolute path for %s: %v", config.BaseDataDirectory, err)
}
config.BaseDataDirectory = absDir
a := &ExecAdapter{
config: config,
}
return a, nil
}
// NewNode creates a new node
func (a ExecAdapter) NewNode(config NodeConfig) Node {
info := NodeInfo{
ID: config.ID,
}
node := &ExecNode{
config: config,
adapter: &a,
info: info,
}
return node
}
// Snapshot returns a snapshot of the adapter
func (a ExecAdapter) Snapshot() AdapterSnapshot {
return AdapterSnapshot{
Type: "exec",
Config: a.config,
}
}
// Info returns the node info
func (n *ExecNode) Info() NodeInfo {
return n.info
}
// Start starts the node
func (n *ExecNode) Start() error {
// Check if command already exists
if n.cmd != nil {
return fmt.Errorf("node %s is already running", n.config.ID)
}
// Create command line arguments
args := []string{filepath.Base(n.adapter.config.ExecutablePath)}
// Create data directory for this node
dir := n.dataDir()
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create node directory: %s", err)
}
// Configure data directory
args = append(args, "--datadir", dir)
// Configure IPC path
args = append(args, "--ipcpath", n.ipcPath())
// Automatically allocate ports
args = append(args, "--pprofport", "0")
args = append(args, "--bzzport", "0")
args = append(args, "--wsport", "0")
args = append(args, "--port", "0")
// Append user defined arguments
args = append(args, n.config.Args...)
// Start command
n.cmd = &exec.Cmd{
Path: n.adapter.config.ExecutablePath,
Args: args,
Dir: dir,
Env: n.config.Env,
Stdout: n.config.Stdout,
Stderr: n.config.Stderr,
}
if err := n.cmd.Start(); err != nil {
n.cmd = nil
return fmt.Errorf("error starting node %s: %s", n.config.ID, err)
}
// Wait for the node to start
var client *rpc.Client
var err error
defer func() {
if err != nil {
n.Stop()
}
}()
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
client, err = rpc.Dial(n.ipcPath())
if err == nil {
break
}
}
if client == nil {
return fmt.Errorf("could not establish rpc connection. node %s: %v", n.config.ID, err)
}
defer client.Close()
var swarminfo swarm.Info
err = client.Call(&swarminfo, "bzz_info")
if err != nil {
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
}
var p2pinfo p2p.NodeInfo
err = client.Call(&p2pinfo, "admin_nodeInfo")
if err != nil {
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
}
n.info = NodeInfo{
ID: n.config.ID,
Enode: p2pinfo.Enode,
BzzAddr: swarminfo.BzzKey,
RPCListen: n.ipcPath(),
HTTPListen: fmt.Sprintf("http://localhost:%s", swarminfo.Port),
}
return nil
}
// Stop stops the node
func (n *ExecNode) Stop() error {
if n.cmd == nil {
return nil
}
defer func() {
n.cmd = nil
}()
// Try to gracefully terminate the process
if err := n.cmd.Process.Signal(syscall.SIGTERM); err != nil {
return n.cmd.Process.Kill()
}
// Wait for the process to terminate or timeout
waitErr := make(chan error)
go func() {
waitErr <- n.cmd.Wait()
}()
select {
case err := <-waitErr:
return err
case <-time.After(20 * time.Second):
return n.cmd.Process.Kill()
}
}
// Snapshot returns a snapshot of the node
func (n *ExecNode) Snapshot() (NodeSnapshot, error) {
snap := NodeSnapshot{
Config: n.config,
}
adapterSnap := n.adapter.Snapshot()
snap.Adapter = &adapterSnap
return snap, nil
}
// ipcPath returns the path to the ipc socket
func (n *ExecNode) ipcPath() string {
ipcfile := "bzzd.ipc"
// On windows we can have to use pipes
if runtime.GOOS == "windows" {
return `\\.\pipe\` + ipcfile
}
return fmt.Sprintf("%s/%s", n.dataDir(), ipcfile)
}
// dataDir returns the path to the data directory that the node should use
func (n *ExecNode) dataDir() string {
return filepath.Join(n.adapter.config.BaseDataDirectory, string(n.config.ID))
}

94
simulation/exec_test.go Normal file
View File

@ -0,0 +1,94 @@
package simulation
import (
"encoding/hex"
"io/ioutil"
"os"
"testing"
"github.com/ethereum/go-ethereum/crypto"
)
func TestExecAdapter(t *testing.T) {
execPath := "../build/bin/swarm"
// Skip test if binary doesn't exist
if _, err := os.Stat(execPath); err != nil {
if os.IsNotExist(err) {
t.Skip("swarm binary not found. build it before running the test")
}
}
tmpdir, err := ioutil.TempDir("", "test-adapter-exec")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
adapter, err := NewExecAdapter(ExecAdapterConfig{
ExecutablePath: execPath,
BaseDataDirectory: tmpdir,
})
if err != nil {
t.Fatalf("could not create exec adapter: %v", err)
}
bzzkey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("could not generate key: %v", err)
}
bzzkeyhex := hex.EncodeToString(crypto.FromECDSA(bzzkey))
nodekey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("could not generate key: %v", err)
}
nodekeyhex := hex.EncodeToString(crypto.FromECDSA(nodekey))
args := []string{
"--bootnodes", "",
"--bzzkeyhex", bzzkeyhex,
"--nodekeyhex", nodekeyhex,
"--bzznetworkid", "499",
}
nodeconfig := NodeConfig{
ID: "node1",
Args: args,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
node := adapter.NewNode(nodeconfig)
info := node.Info()
if info.ID != "node1" {
t.Fatal("node id is different")
}
err = node.Start()
if err != nil {
t.Fatalf("node did not start: %v", err)
}
infoA := node.Info()
err = node.Stop()
if err != nil {
t.Fatalf("node didn't stop: %v", err)
}
err = node.Start()
if err != nil {
t.Fatalf("node didn't start again: %v", err)
}
infoB := node.Info()
if infoA.BzzAddr != infoB.BzzAddr {
t.Errorf("bzzaddr should be the same: %s - %s", infoA.Enode, infoB.Enode)
}
err = node.Stop()
if err != nil {
t.Fatalf("node didn't stop: %v", err)
}
}

467
simulation/kubernetes.go Normal file
View File

@ -0,0 +1,467 @@
package simulation
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm"
"github.com/ethersphere/swarm/log"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// KubernetesAdapter can manage nodes on a kubernetes cluster
type KubernetesAdapter struct {
client *kubernetes.Clientset
config KubernetesAdapterConfig
image string
proxy string
}
// KubernetesAdapterConfig is the configuration provided to a KubernetesAdapter
type KubernetesAdapterConfig struct {
// KubeConfigPath is the path to your kubernetes configuration path
KubeConfigPath string `json:"kubeConfigPath"`
// Namespace is the kubernetes namespaces where the pods should be running
Namespace string `json:"namespace"`
// BuildContext can be used to build a docker image
// from a Dockerfile and a context directory
BuildContext *KubernetesBuildContext `json:"build,omitempty"`
// DockerImage points to an existing docker image
// e.g. ethersphere/swarm:latest
DockerImage string `json:"image,omitempty"`
}
// KubernetesBuildContext defines the build context to build
// local docker images
type KubernetesBuildContext struct {
// Dockefile is the path to the dockerfile
Dockerfile string `json:"dockerfile"`
// Directory is the directory that will be used
// in the context of a docker build
Directory string `json:"dir"`
// Tag is used to tag the image
Tag string `json:"tag"`
// Registry is the image registry where the image will be pushed to
Registry string `json:"registry"`
// Username is the user used to push the image to the registry
Username string `json:"username"`
// Password is the password of the user that is used to push the image
// to the registry
Password string `json:"-"`
}
// ImageTag is the full image tag, including the registry
func (bc *KubernetesBuildContext) ImageTag() string {
return fmt.Sprintf("%s/%s", bc.Registry, bc.Tag)
}
// DefaultKubernetesAdapterConfig uses the default ~/.kube/config
// to discover the kubernetes clusters. It also uses the "default" namespace.
func DefaultKubernetesAdapterConfig() KubernetesAdapterConfig {
kubeconfig := filepath.Join(homeDir(), ".kube", "config")
return KubernetesAdapterConfig{
KubeConfigPath: kubeconfig,
Namespace: "default",
}
}
// IsKubernetesAvailable checks if a kubernetes configuration file exists
func IsKubernetesAvailable(kubeConfigPath string) bool {
k8scfg, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
if err != nil {
return false
}
_, err = kubernetes.NewForConfig(k8scfg)
return err == nil
}
// NewKubernetesAdapter creates a KubernetesAdpater by receiving a KubernetesAdapterConfig
func NewKubernetesAdapter(config KubernetesAdapterConfig) (*KubernetesAdapter, error) {
if config.DockerImage != "" && config.BuildContext != nil {
return nil, fmt.Errorf("only one can be defined: BuildContext (%v) or DockerImage(%s)",
config.BuildContext, config.DockerImage)
}
if config.DockerImage == "" && config.BuildContext == nil {
return nil, errors.New("required: Dockerfile or DockerImage")
}
// Define k8s client configuration
k8scfg, err := clientcmd.BuildConfigFromFlags("", config.KubeConfigPath)
if err != nil {
return nil, fmt.Errorf("could not start k8s client from config: %v", err)
}
// Create the clientset
clientset, err := kubernetes.NewForConfig(k8scfg)
if err != nil {
return nil, fmt.Errorf("could not create clientset: %v", err)
}
// Figure out which docker image should be used
image := config.DockerImage
// Build and push container image
if config.BuildContext != nil {
var err error
// Build image
image, err = buildImage(DockerBuildContext{
Dockerfile: config.BuildContext.Dockerfile,
Directory: config.BuildContext.Directory,
Tag: config.BuildContext.ImageTag(),
}, DefaultDockerAdapterConfig().DaemonAddr)
if err != nil {
return nil, fmt.Errorf("could not build the docker image: %v", err)
}
// Push image
dockerClient, err := client.NewClientWithOpts(
client.WithHost(client.DefaultDockerHost),
client.WithAPIVersionNegotiation(),
)
if err != nil {
return nil, fmt.Errorf("could not create the docker client: %v", err)
}
authConfig := types.AuthConfig{
Username: config.BuildContext.Username,
Password: config.BuildContext.Password,
}
encodedJSON, err := json.Marshal(authConfig)
if err != nil {
return nil, errors.New("failed marshaling the authentication parameters")
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
out, err := dockerClient.ImagePush(
context.Background(),
config.BuildContext.ImageTag(),
types.ImagePushOptions{
RegistryAuth: authStr,
})
if err != nil {
return nil, fmt.Errorf("failed to push image: %v", err)
}
defer out.Close()
if _, err := io.Copy(os.Stdout, out); err != nil && err != io.EOF {
log.Error("Error pushing docker image", "err", err)
}
}
// Setup proxy to access pods
server, err := newProxyServer(k8scfg)
if err != nil {
return nil, fmt.Errorf("failed to create proxy: %v", err)
}
l, err := server.Listen("127.0.0.1", 0)
if err != nil {
return nil, fmt.Errorf("failed to start proxy: %v", err)
}
go func() {
if err := server.ServeOnListener(l); err != nil {
log.Error("Kubernetes dapater proxy failed:", "err", err.Error())
}
}()
// Return adapter
return &KubernetesAdapter{
client: clientset,
image: image,
config: config,
proxy: l.Addr().String(),
}, nil
}
// NewNode creates a new node
func (a KubernetesAdapter) NewNode(config NodeConfig) Node {
info := NodeInfo{
ID: config.ID,
}
node := &KubernetesNode{
config: config,
adapter: &a,
info: info,
}
return node
}
// Snapshot returns a snapshot of the Adapter
func (a KubernetesAdapter) Snapshot() AdapterSnapshot {
return AdapterSnapshot{
Type: "kubernetes",
Config: a.config,
}
}
// KubernetesNode is a node that was started via the KubernetesAdapter
type KubernetesNode struct {
config NodeConfig
adapter *KubernetesAdapter
info NodeInfo
}
// Info returns the node info
func (n *KubernetesNode) Info() NodeInfo {
return n.info
}
// Start starts the node
func (n *KubernetesNode) Start() error {
// Define arguments
args := []string{}
// Append user defined arguments
args = append(args, n.config.Args...)
// Append network ports arguments
args = append(args, "--pprofport", strconv.Itoa(dockerPProfPort))
args = append(args, "--bzzport", strconv.Itoa(dockerHTTPPort))
args = append(args, "--ws")
// TODO: Can we get the APIs from somewhere instead of hardcoding them here?
args = append(args, "--wsapi", "admin,net,debug,bzz,accounting,hive")
args = append(args, "--wsport", strconv.Itoa(dockerWebsocketPort))
args = append(args, "--wsaddr", "0.0.0.0")
args = append(args, "--wsorigins", "*")
args = append(args, "--port", strconv.Itoa(dockerP2PPort))
args = append(args, "--nat", "ip:$(POD_IP)")
// Build environment variables
env := []v1.EnvVar{
{
// POD_IP is useful for setting the NAT config: e.g. `--nat ip:$POD_IP`
Name: "POD_IP",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "status.podIP",
},
},
},
}
for _, e := range n.config.Env {
var name, value string
s := strings.SplitN(e, "=", 1)
name = s[0]
if len(s) > 1 {
value = s[1]
}
env = append(env, v1.EnvVar{
Name: name,
Value: value,
})
}
adapter := n.adapter
// Create Kubernetes Pod
podRequest := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: n.podName(),
Labels: map[string]string{
"app": "simulation",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: n.podName(),
Image: adapter.image,
Args: args,
Env: env,
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
},
},
}
pod, err := adapter.client.CoreV1().Pods(adapter.config.Namespace).Create(podRequest)
if err != nil {
return fmt.Errorf("failed to create pod: %v", err)
}
// Wait for pod
start := time.Now()
for {
log.Debug("Waiting for pod", "pod", n.podName())
pod, err := adapter.client.CoreV1().Pods(adapter.config.Namespace).Get(n.podName(), metav1.GetOptions{})
if err != nil {
time.Sleep(100 * time.Millisecond)
continue
}
if pod.Status.Phase == v1.PodRunning {
break
}
if time.Since(start) > 5*time.Minute {
return errors.New("timeout waiting for pod")
}
time.Sleep(500 * time.Millisecond)
}
// Get logs
logOpts := &v1.PodLogOptions{
Container: n.podName(),
Follow: true,
Previous: false,
}
req := adapter.client.CoreV1().Pods(adapter.config.Namespace).GetLogs(n.podName(), logOpts)
readCloser, err := req.Stream()
if err != nil {
return fmt.Errorf("could not get logs: %v", err)
}
go func() {
defer readCloser.Close()
if _, err := io.Copy(n.config.Stderr, readCloser); err != nil && err != io.EOF {
log.Error("Error writing pod logs", "pod", pod.Name, "err", err)
}
}()
// Wait for the node to start
var client *rpc.Client
wsAddr := fmt.Sprintf("ws://%s/api/v1/namespaces/%s/pods/%s:%d/proxy",
adapter.proxy, adapter.config.Namespace, n.podName(), dockerWebsocketPort)
for start := time.Now(); time.Since(start) < 30*time.Second; time.Sleep(50 * time.Millisecond) {
client, err = rpc.Dial(wsAddr)
if err == nil {
break
}
}
if client == nil {
return fmt.Errorf("could not establish rpc connection. node %s: %v", n.config.ID, err)
}
defer client.Close()
var swarminfo swarm.Info
err = client.Call(&swarminfo, "bzz_info")
if err != nil {
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
}
var p2pinfo p2p.NodeInfo
err = client.Call(&p2pinfo, "admin_nodeInfo")
if err != nil {
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
}
n.info = NodeInfo{
ID: n.config.ID,
Enode: p2pinfo.Enode,
BzzAddr: swarminfo.BzzKey,
RPCListen: wsAddr,
HTTPListen: fmt.Sprintf("http://%s/api/v1/namespaces/%s/pods/%s:%d/proxy",
adapter.proxy, adapter.config.Namespace, n.podName(), dockerHTTPPort),
PprofListen: fmt.Sprintf("http://%s/api/v1/namespaces/%s/pods/%s:%d/proxy",
adapter.proxy, adapter.config.Namespace, n.podName(), dockerPProfPort),
}
return nil
}
// Stop stops the node
func (n *KubernetesNode) Stop() error {
adapter := n.adapter
gracePeriod := int64(30)
deleteOpts := &metav1.DeleteOptions{
GracePeriodSeconds: &gracePeriod,
}
err := adapter.client.CoreV1().Pods(adapter.config.Namespace).Delete(n.podName(), deleteOpts)
if err != nil {
return fmt.Errorf("could not delete pod: %v", err)
}
return nil
}
// Snapshot returns a snapshot of the node
func (n *KubernetesNode) Snapshot() (NodeSnapshot, error) {
snap := NodeSnapshot{
Config: n.config,
}
adapterSnap := n.adapter.Snapshot()
snap.Adapter = &adapterSnap
return snap, nil
}
func (n *KubernetesNode) podName() string {
return fmt.Sprintf("sim-k8s-%s", n.config.ID)
}
func homeDir() string {
if h := os.Getenv("HOME"); h != "" {
return h
}
return os.Getenv("USERPROFILE") // windows
}
// proxyServer is a http.Handler which proxies Kubernetes APIs to remote API server.
type proxyServer struct {
handler http.Handler
}
// Listen is a simple wrapper around net.Listen.
func (s *proxyServer) Listen(address string, port int) (net.Listener, error) {
return net.Listen("tcp", fmt.Sprintf("%s:%d", address, port))
}
// ServeOnListener starts the server using given listener, loops forever.
func (s *proxyServer) ServeOnListener(l net.Listener) error {
server := http.Server{
Handler: s.handler,
}
return server.Serve(l)
}
func (s *proxyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
s.handler.ServeHTTP(rw, req)
}
// newProxyServer creates a proxy server that can be used to proxy to the kubernetes API
func newProxyServer(cfg *rest.Config) (*proxyServer, error) {
target, err := url.Parse(cfg.Host)
if err != nil {
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(target)
transport, err := rest.TransportFor(cfg)
if err != nil {
return nil, err
}
proxy.Transport = transport
return &proxyServer{
handler: proxy,
}, nil
}

604
simulation/simulation.go Normal file
View File

@ -0,0 +1,604 @@
package simulation
import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm/log"
"github.com/ethersphere/swarm/network"
"golang.org/x/sync/errgroup"
)
type nodeMap struct {
sync.RWMutex
internal map[NodeID]Node
}
func newNodeMap() *nodeMap {
return &nodeMap{
internal: make(map[NodeID]Node),
}
}
func (nm *nodeMap) Load(key NodeID) (value Node, ok bool) {
nm.RLock()
result, ok := nm.internal[key]
nm.RUnlock()
return result, ok
}
func (nm *nodeMap) LoadAll() []Node {
nm.RLock()
v := []Node{}
for _, node := range nm.internal {
v = append(v, node)
}
nm.RUnlock()
return v
}
func (nm *nodeMap) Store(key NodeID, value Node) {
nm.Lock()
nm.internal[key] = value
nm.Unlock()
}
// Simulation is used to simulate a network of nodes
type Simulation struct {
adapter Adapter
nodes *nodeMap
}
// NewSimulation creates a new simulation given an adapter
func NewSimulation(adapter Adapter) *Simulation {
sim := &Simulation{
adapter: adapter,
nodes: newNodeMap(),
}
return sim
}
func getAdapterFromSnapshotConfig(snapshot *AdapterSnapshot) (Adapter, error) {
if snapshot == nil {
return nil, errors.New("snapshot can't be nil")
}
var adapter Adapter
var err error
switch t := snapshot.Type; t {
case "exec":
adapter, err = NewExecAdapter(snapshot.Config.(ExecAdapterConfig))
case "docker":
adapter, err = NewDockerAdapter(snapshot.Config.(DockerAdapterConfig))
case "kubernetes":
adapter, err = NewKubernetesAdapter(snapshot.Config.(KubernetesAdapterConfig))
default:
return nil, fmt.Errorf("unknown adapter type: %s", t)
}
if err != nil {
return nil, fmt.Errorf("could not initialize %s adapter: %v", snapshot.Type, err)
}
return adapter, nil
}
// NewSimulationFromSnapshot creates a simulation from a snapshot
func NewSimulationFromSnapshot(snapshot *Snapshot) (*Simulation, error) {
// Create adapter
adapter, err := getAdapterFromSnapshotConfig(snapshot.DefaultAdapter)
if err != nil {
return nil, err
}
sim := &Simulation{
adapter: adapter,
nodes: newNodeMap(),
}
// Loop over nodes and add them
for _, n := range snapshot.Nodes {
if n.Adapter == nil {
if err := sim.Init(n.Config); err != nil {
return sim, fmt.Errorf("failed to initialize node %v", err)
}
} else {
adapter, err := getAdapterFromSnapshotConfig(n.Adapter)
if err != nil {
return sim, fmt.Errorf("could not read adapter configureation for node %s: %v", n.Config.ID, err)
}
if err := sim.InitWithAdapter(n.Config, adapter); err != nil {
return sim, fmt.Errorf("failed to initialize node %s: %v", n.Config.ID, err)
}
}
}
// Start all nodes
err = sim.StartAll()
if err != nil {
return sim, err
}
// Establish connections
m := make(map[string]Node)
for _, n := range sim.GetAll() {
enode := removeNetworkAddressFromEnode(n.Info().Enode)
m[enode] = n
}
for _, con := range snapshot.Connections {
from, ok := m[con.From]
if !ok {
return sim, fmt.Errorf("no node found with enode: %s", con.From)
}
to, ok := m[con.To]
if !ok {
return sim, fmt.Errorf("no node found with enode: %s", con.To)
}
client, err := sim.RPCClient(from.Info().ID)
if err != nil {
return sim, err
}
defer client.Close()
if err := client.Call(nil, "admin_addPeer", to.Info().Enode); err != nil {
return sim, err
}
}
return sim, nil
}
func (s *AdapterSnapshot) detectConfigurationType() error {
adapterconfig, err := json.Marshal(s.Config)
if err != nil {
return err
}
switch t := s.Type; t {
case "exec":
var config ExecAdapterConfig
err := json.Unmarshal(adapterconfig, &config)
if err != nil {
return err
}
s.Config = config
case "docker":
var config DockerAdapterConfig
err := json.Unmarshal(adapterconfig, &config)
if err != nil {
return err
}
s.Config = config
case "kubernetes":
var config KubernetesAdapterConfig
err := json.Unmarshal(adapterconfig, &config)
if err != nil {
return err
}
s.Config = config
default:
return fmt.Errorf("unknown adapter type: %s", t)
}
return nil
}
func unmarshalSnapshot(data []byte, snapshot *Snapshot) error {
err := json.Unmarshal(data, snapshot)
if err != nil {
return err
}
// snapshot.Adapter.Config will be of type map[string]interface{}
// we have to unmarshal it to the correct adapter configuration struct
if err := snapshot.DefaultAdapter.detectConfigurationType(); err != nil {
return err
}
for _, n := range snapshot.Nodes {
if n.Adapter != nil {
if err := n.Adapter.detectConfigurationType(); err != nil {
return err
}
}
}
return nil
}
// LoadSnapshotFromFile loads a snapshot from a given JSON file
func LoadSnapshotFromFile(filePath string) (*Snapshot, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer f.Close()
bytes, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
var snapshot Snapshot
err = unmarshalSnapshot(bytes, &snapshot)
if err != nil {
return nil, err
}
return &snapshot, nil
}
// Get returns a node by ID
func (s *Simulation) Get(id NodeID) (Node, error) {
node, ok := s.nodes.Load(id)
if !ok {
return nil, fmt.Errorf("a node with id %s does not exist", id)
}
return node, nil
}
// GetAll returns all nodes
func (s *Simulation) GetAll() []Node {
return s.nodes.LoadAll()
}
// DefaultAdapter returns the default adapter that the simulation was initialized with
func (s *Simulation) DefaultAdapter() Adapter {
return s.adapter
}
// Init initializes a node with the NodeConfig with the default Adapter
func (s *Simulation) Init(config NodeConfig) error {
return s.InitWithAdapter(config, s.DefaultAdapter())
}
// InitWithAdapter initializes a node with the NodeConfig and the given Adapter
func (s *Simulation) InitWithAdapter(config NodeConfig, adapter Adapter) error {
if _, ok := s.nodes.Load(config.ID); ok {
return fmt.Errorf("a node with id %s already exists", config.ID)
}
node := adapter.NewNode(config)
s.nodes.Store(config.ID, node)
return nil
}
// Start starts a node by ID
func (s *Simulation) Start(id NodeID) error {
node, ok := s.nodes.Load(id)
if !ok {
return fmt.Errorf("a node with id %s does not exist", id)
}
if err := node.Start(); err != nil {
return fmt.Errorf("could not start node: %v", err)
}
return nil
}
// Stop stops a node by ID
func (s *Simulation) Stop(id NodeID) error {
node, ok := s.nodes.Load(id)
if !ok {
return fmt.Errorf("a node with id %s does not exist", id)
}
if err := node.Stop(); err != nil {
return fmt.Errorf("could not stop node: %v", err)
}
return nil
}
// StartAll starts all nodes
func (s *Simulation) StartAll() error {
g, _ := errgroup.WithContext(context.Background())
for _, node := range s.nodes.LoadAll() {
g.Go(node.Start)
}
return g.Wait()
}
// StopAll stops all nodes
func (s *Simulation) StopAll() error {
g, _ := errgroup.WithContext(context.Background())
for _, node := range s.nodes.LoadAll() {
g.Go(node.Stop)
}
return g.Wait()
}
// RPCClient returns an RPC Client for a given node
func (s *Simulation) RPCClient(id NodeID) (*rpc.Client, error) {
node, ok := s.nodes.Load(id)
if !ok {
return nil, fmt.Errorf("a node with id %s does not exist", id)
}
info := node.Info()
var client *rpc.Client
var err error
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
client, err = rpc.Dial(info.RPCListen)
if err == nil {
break
}
}
if client == nil {
return nil, fmt.Errorf("could not establish rpc connection: %v", err)
}
return client, nil
}
// HTTPBaseAddr returns the address for the HTTP API
func (s *Simulation) HTTPBaseAddr(id NodeID) (string, error) {
node, ok := s.nodes.Load(id)
if !ok {
return "", fmt.Errorf("a node with id %s does not exist", id)
}
info := node.Info()
return info.HTTPListen, nil
}
// Snapshot returns a snapshot of the simulation
func (s *Simulation) Snapshot() (*Snapshot, error) {
snap := Snapshot{}
// Default adapter snapshot
asnap := s.DefaultAdapter().Snapshot()
snap.DefaultAdapter = &asnap
// Nodes snapshot
nodes := s.GetAll()
snap.Nodes = make([]NodeSnapshot, len(nodes))
snap.Connections = []ConnectionSnapshot{}
for idx, n := range nodes {
ns, err := n.Snapshot()
if err != nil {
return nil, fmt.Errorf("failed to get nodes snapshot %s: %v", n.Info().ID, err)
}
// Don't need to specify the node's adapter snapshot if it's
// the same as the default adapters snapshot
if reflect.DeepEqual(asnap, *ns.Adapter) {
ns.Adapter = nil
}
snap.Nodes[idx] = ns
// Get connections
client, err := s.RPCClient(n.Info().ID)
if err != nil {
return nil, err
}
defer client.Close()
var peers []*p2p.PeerInfo
err = client.Call(&peers, "admin_peers")
if err != nil {
return nil, err
}
for _, p := range peers {
// Only care about outbound connections
if !p.Network.Inbound {
snap.Connections = append(snap.Connections, ConnectionSnapshot{
// we need to remove network addresses from enodes
// because they will change between simulations
From: removeNetworkAddressFromEnode(n.Info().Enode),
To: removeNetworkAddressFromEnode(p.Enode),
})
}
}
}
return &snap, nil
}
// AddBootnode adds and starts a bootnode with the given id and arguments
func (s *Simulation) AddBootnode(id NodeID, args []string) (Node, error) {
a := []string{
"--bootnode-mode",
"--bootnodes", "",
}
a = append(a, args...)
return s.AddNode(id, a)
}
// AddNode adds and starts a node with the given id and arguments
func (s *Simulation) AddNode(id NodeID, args []string) (Node, error) {
bzzkey, err := randomHexKey()
if err != nil {
return nil, err
}
nodekey, err := randomHexKey()
if err != nil {
return nil, err
}
a := []string{
"--bzzkeyhex", bzzkey,
"--nodekeyhex", nodekey,
}
a = append(a, args...)
cfg := NodeConfig{
ID: id,
Args: a,
// TODO: Figure out how to handle logs when using AddNode(...)
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
err = s.Init(cfg)
if err != nil {
return nil, err
}
err = s.Start(id)
if err != nil {
return nil, err
}
node, err := s.Get(id)
if err != nil {
return nil, err
}
return node, nil
}
// AddNodes adds and starts 'count' nodes with a given ID prefix, arguments.
// If the idPrefix is "node" and count is 3 then the following nodes will be
// created: node-0, node-1, node-2
func (s *Simulation) AddNodes(idPrefix string, count int, args []string) ([]Node, error) {
g, _ := errgroup.WithContext(context.Background())
idFormat := "%s-%d"
for i := 0; i < count; i++ {
id := NodeID(fmt.Sprintf(idFormat, idPrefix, i))
g.Go(func() error {
node, err := s.AddNode(id, args)
if err != nil {
log.Warn("Failed to add node", "id", id, "err", err.Error())
} else {
log.Info("Added node", "id", id, "enode", node.Info().Enode)
}
return err
})
}
err := g.Wait()
if err != nil {
return nil, err
}
nodes := make([]Node, count)
for i := 0; i < count; i++ {
id := NodeID(fmt.Sprintf(idFormat, idPrefix, i))
nodes[i], err = s.Get(id)
if err != nil {
return nil, err
}
}
return nodes, nil
}
// CreateClusterWithBootnode adds and starts a bootnode. Afterwards it will add and start 'count' nodes that connect
// to the bootnode. All nodes can be provided by custom arguments.
// If the idPrefix is "node" and count is 3 then you will have the following nodes created:
// node-bootnode, node-0, node-1, node-2.
// The bootnode will be the first node on the returned Node slice.
func (s *Simulation) CreateClusterWithBootnode(idPrefix string, count int, args []string) ([]Node, error) {
bootnode, err := s.AddBootnode(NodeID(fmt.Sprintf("%s-bootnode", idPrefix)), args)
if err != nil {
return nil, err
}
nodeArgs := []string{
"--bootnodes", bootnode.Info().Enode,
}
nodeArgs = append(nodeArgs, args...)
n, err := s.AddNodes(idPrefix, count, nodeArgs)
if err != nil {
return nil, err
}
nodes := []Node{bootnode}
nodes = append(nodes, n...)
return nodes, nil
}
// WaitForHealthyNetwork will block until all the nodes are considered
// to have a healthy kademlia table
func (s *Simulation) WaitForHealthyNetwork() error {
nodes := s.GetAll()
// Generate RPC clients
var clients struct {
RPC []*rpc.Client
mu sync.Mutex
}
clients.RPC = make([]*rpc.Client, len(nodes))
g, _ := errgroup.WithContext(context.Background())
for idx, node := range nodes {
node := node
idx := idx
g.Go(func() error {
id := node.Info().ID
client, err := s.RPCClient(id)
if err != nil {
return err
}
clients.mu.Lock()
clients.RPC[idx] = client
clients.mu.Unlock()
return nil
})
}
if err := g.Wait(); err != nil {
return err
}
for _, c := range clients.RPC {
defer c.Close()
}
// Generate addresses for PotMap
addrs := [][]byte{}
for _, node := range nodes {
byteaddr, err := hexutil.Decode(node.Info().BzzAddr)
if err != nil {
return err
}
addrs = append(addrs, byteaddr)
}
ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs)
log.Info("Waiting for healthy kademlia...")
// Check for healthInfo on all nodes
for {
g, _ = errgroup.WithContext(context.Background())
for i := 0; i < len(nodes)-1; i++ {
i := i
g.Go(func() error {
log.Debug("Checking hive_getHealthInfo", "node", nodes[i].Info().ID)
healthy := &network.Health{}
if err := clients.RPC[i].Call(&healthy, "hive_getHealthInfo", ppmap[nodes[i].Info().BzzAddr[2:]]); err != nil {
return err
}
if !healthy.Healthy() {
return fmt.Errorf("node %s is not healthy", nodes[i].Info().ID)
}
return nil
})
}
err := g.Wait()
if err == nil {
break
}
log.Info("Not healthy yet...", "msg", err.Error())
time.Sleep(500 * time.Millisecond)
}
log.Info("Healthy kademlia on all nodes")
return nil
}
func randomHexKey() (string, error) {
key, err := crypto.GenerateKey()
if err != nil {
return "", err
}
keyhex := hex.EncodeToString(crypto.FromECDSA(key))
return keyhex, nil
}
func removeNetworkAddressFromEnode(enode string) string {
if idx := strings.Index(enode, "@"); idx != -1 {
return enode[:idx]
}
return enode
}

81
simulation/types.go Normal file
View File

@ -0,0 +1,81 @@
package simulation
import (
"io"
)
// Node is a node within a simulation
type Node interface {
Info() NodeInfo
// Start starts the node
Start() error
// Stop stops the node
Stop() error
// Snapshot returns a snapshot of the node
Snapshot() (NodeSnapshot, error)
}
// Adapter can handle Node creation
type Adapter interface {
// NewNode creates a new node based on the NodeConfig
NewNode(config NodeConfig) Node
// Snapshot returns a snapshot of the adapter
Snapshot() AdapterSnapshot
}
// NodeID is the node identifier within a simulation. This can be an arbitrary string.
type NodeID string
// NodeConfig is the configuration of a specific node
type NodeConfig struct {
// Arbitrary string used to identify a node
ID NodeID `json:"id"`
// Command line arguments
Args []string `json:"args"`
// Environment variables
Env []string `json:"env,omitempty"`
// Stdout and Stderr specify the nodes' standard output and error
Stdout io.Writer `json:"-"`
Stderr io.Writer `json:"-"`
}
// NodeInfo contains the nodes information and connections strings
type NodeInfo struct {
ID NodeID
Enode string
BzzAddr string
RPCListen string // RPC listener address. Should be a valid ipc or websocket path
HTTPListen string // HTTP listener address: e.g. http://localhost:8500
PprofListen string // PProf listener address: e.g http://localhost:6060
}
// Snapshot is a snapshot of a simulation. It contains snapshots of:
// - the default adapter that the simulation was initialized with
// - the list of nodes that were created within the simulation
// - the list of connections between nodes
type Snapshot struct {
DefaultAdapter *AdapterSnapshot `json:"defaultAdapter"`
Nodes []NodeSnapshot `json:"nodes"`
Connections []ConnectionSnapshot `json:"connections"`
}
// NodeSnapshot is a snapshot of the node, it contains the node configuration and an adapter snapshot
type NodeSnapshot struct {
Config NodeConfig `json:"config"`
Adapter *AdapterSnapshot `json:"adapter,omitempty"`
}
// ConnectionSnapshot is a snapshot of a connection between peers
type ConnectionSnapshot struct {
From string `json:"from"`
To string `json:"to"`
}
// AdapterSnapshot is a snapshot of the configuration of an adapter
// - The type can be an arbitrary strings, e.g. "exec", "docker", etc.
// - The config will depend on the type, as every adapter has different configuration options
type AdapterSnapshot struct {
Type string `json:"type"`
Config interface{} `json:"config"`
}

View File

@ -25,7 +25,9 @@ import (
"io"
"math/big"
"net"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
"unicode"
@ -383,12 +385,21 @@ func (s *Swarm) Start(srv *p2p.Server) error {
server := httpapi.NewServer(s.api, s.config.Cors)
if s.config.Cors != "" {
log.Debug("Swarm HTTP proxy CORS headers", "allowedOrigins", s.config.Cors)
log.Info("Swarm HTTP proxy CORS headers", "allowedOrigins", s.config.Cors)
}
log.Debug("Starting Swarm HTTP proxy", "port", s.config.Port)
go func() {
err := server.ListenAndServe(addr)
// We need to use net.Listen because the addr could be on port '0',
// which means that the OS will allocate a port for us
listener, err := net.Listen("tcp", addr)
if err != nil {
log.Error("Could not open a port for Swarm HTTP proxy", "err", err.Error())
return
}
s.config.Port = strconv.Itoa(listener.Addr().(*net.TCPAddr).Port)
log.Info("Starting Swarm HTTP proxy", "port", s.config.Port)
err = http.Serve(listener, server)
if err != nil {
log.Error("Could not start Swarm HTTP proxy", "err", err.Error())
}
@ -481,7 +492,6 @@ func (s *Swarm) Protocols() (protos []p2p.Protocol) {
// implements node.Service
// APIs returns the RPC API descriptors the Swarm implementation offers
func (s *Swarm) APIs() []rpc.API {
apis := []rpc.API{
// public APIs
{

58
testutil/node.go Normal file
View File

@ -0,0 +1,58 @@
// Copyright 2019 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package testutil
import (
"net"
"testing"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethersphere/swarm/chunk"
"github.com/ethersphere/swarm/network"
)
// NodeConfigAtPo brute forces a node config to create a node that has an overlay address at the provided po in relation to the given baseaddr
func NodeConfigAtPo(t *testing.T, baseaddr []byte, po int) *adapters.NodeConfig {
foundPo := -1
var conf *adapters.NodeConfig
for foundPo != po {
conf = adapters.RandomNodeConfig()
ip := net.IPv4(127, 0, 0, 1)
enrIP := enr.IP(ip)
conf.Record.Set(&enrIP)
enrTCPPort := enr.TCP(conf.Port)
conf.Record.Set(&enrTCPPort)
enrUDPPort := enr.UDP(0)
conf.Record.Set(&enrUDPPort)
err := enode.SignV4(&conf.Record, conf.PrivateKey)
if err != nil {
t.Fatalf("unable to generate ENR: %v", err)
}
nod, err := enode.New(enode.V4ID{}, &conf.Record)
if err != nil {
t.Fatalf("unable to create enode: %v", err)
}
n := network.NewAddr(nod)
foundPo = chunk.Proximity(baseaddr, n.Over())
}
return conf
}

21
vendor/github.com/Azure/go-ansiterm/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Microsoft Corporation
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.

12
vendor/github.com/Azure/go-ansiterm/README.md generated vendored Normal file
View File

@ -0,0 +1,12 @@
# go-ansiterm
This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent.
For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position.
The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
See parser_test.go for examples exercising the state machine and generating appropriate function calls.
-----
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.

188
vendor/github.com/Azure/go-ansiterm/constants.go generated vendored Normal file
View File

@ -0,0 +1,188 @@
package ansiterm
const LogEnv = "DEBUG_TERMINAL"
// ANSI constants
// References:
// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
// -- http://en.wikipedia.org/wiki/ANSI_escape_code
// -- http://vt100.net/emu/dec_ansi_parser
// -- http://vt100.net/emu/vt500_parser.svg
// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
// -- http://www.inwap.com/pdp10/ansicode.txt
const (
// ECMA-48 Set Graphics Rendition
// Note:
// -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
// -- Fonts could possibly be supported via SetCurrentConsoleFontEx
// -- Windows does not expose the per-window cursor (i.e., caret) blink times
ANSI_SGR_RESET = 0
ANSI_SGR_BOLD = 1
ANSI_SGR_DIM = 2
_ANSI_SGR_ITALIC = 3
ANSI_SGR_UNDERLINE = 4
_ANSI_SGR_BLINKSLOW = 5
_ANSI_SGR_BLINKFAST = 6
ANSI_SGR_REVERSE = 7
_ANSI_SGR_INVISIBLE = 8
_ANSI_SGR_LINETHROUGH = 9
_ANSI_SGR_FONT_00 = 10
_ANSI_SGR_FONT_01 = 11
_ANSI_SGR_FONT_02 = 12
_ANSI_SGR_FONT_03 = 13
_ANSI_SGR_FONT_04 = 14
_ANSI_SGR_FONT_05 = 15
_ANSI_SGR_FONT_06 = 16
_ANSI_SGR_FONT_07 = 17
_ANSI_SGR_FONT_08 = 18
_ANSI_SGR_FONT_09 = 19
_ANSI_SGR_FONT_10 = 20
_ANSI_SGR_DOUBLEUNDERLINE = 21
ANSI_SGR_BOLD_DIM_OFF = 22
_ANSI_SGR_ITALIC_OFF = 23
ANSI_SGR_UNDERLINE_OFF = 24
_ANSI_SGR_BLINK_OFF = 25
_ANSI_SGR_RESERVED_00 = 26
ANSI_SGR_REVERSE_OFF = 27
_ANSI_SGR_INVISIBLE_OFF = 28
_ANSI_SGR_LINETHROUGH_OFF = 29
ANSI_SGR_FOREGROUND_BLACK = 30
ANSI_SGR_FOREGROUND_RED = 31
ANSI_SGR_FOREGROUND_GREEN = 32
ANSI_SGR_FOREGROUND_YELLOW = 33
ANSI_SGR_FOREGROUND_BLUE = 34
ANSI_SGR_FOREGROUND_MAGENTA = 35
ANSI_SGR_FOREGROUND_CYAN = 36
ANSI_SGR_FOREGROUND_WHITE = 37
_ANSI_SGR_RESERVED_01 = 38
ANSI_SGR_FOREGROUND_DEFAULT = 39
ANSI_SGR_BACKGROUND_BLACK = 40
ANSI_SGR_BACKGROUND_RED = 41
ANSI_SGR_BACKGROUND_GREEN = 42
ANSI_SGR_BACKGROUND_YELLOW = 43
ANSI_SGR_BACKGROUND_BLUE = 44
ANSI_SGR_BACKGROUND_MAGENTA = 45
ANSI_SGR_BACKGROUND_CYAN = 46
ANSI_SGR_BACKGROUND_WHITE = 47
_ANSI_SGR_RESERVED_02 = 48
ANSI_SGR_BACKGROUND_DEFAULT = 49
// 50 - 65: Unsupported
ANSI_MAX_CMD_LENGTH = 4096
MAX_INPUT_EVENTS = 128
DEFAULT_WIDTH = 80
DEFAULT_HEIGHT = 24
ANSI_BEL = 0x07
ANSI_BACKSPACE = 0x08
ANSI_TAB = 0x09
ANSI_LINE_FEED = 0x0A
ANSI_VERTICAL_TAB = 0x0B
ANSI_FORM_FEED = 0x0C
ANSI_CARRIAGE_RETURN = 0x0D
ANSI_ESCAPE_PRIMARY = 0x1B
ANSI_ESCAPE_SECONDARY = 0x5B
ANSI_OSC_STRING_ENTRY = 0x5D
ANSI_COMMAND_FIRST = 0x40
ANSI_COMMAND_LAST = 0x7E
DCS_ENTRY = 0x90
CSI_ENTRY = 0x9B
OSC_STRING = 0x9D
ANSI_PARAMETER_SEP = ";"
ANSI_CMD_G0 = '('
ANSI_CMD_G1 = ')'
ANSI_CMD_G2 = '*'
ANSI_CMD_G3 = '+'
ANSI_CMD_DECPNM = '>'
ANSI_CMD_DECPAM = '='
ANSI_CMD_OSC = ']'
ANSI_CMD_STR_TERM = '\\'
KEY_CONTROL_PARAM_2 = ";2"
KEY_CONTROL_PARAM_3 = ";3"
KEY_CONTROL_PARAM_4 = ";4"
KEY_CONTROL_PARAM_5 = ";5"
KEY_CONTROL_PARAM_6 = ";6"
KEY_CONTROL_PARAM_7 = ";7"
KEY_CONTROL_PARAM_8 = ";8"
KEY_ESC_CSI = "\x1B["
KEY_ESC_N = "\x1BN"
KEY_ESC_O = "\x1BO"
FILL_CHARACTER = ' '
)
func getByteRange(start byte, end byte) []byte {
bytes := make([]byte, 0, 32)
for i := start; i <= end; i++ {
bytes = append(bytes, byte(i))
}
return bytes
}
var toGroundBytes = getToGroundBytes()
var executors = getExecuteBytes()
// SPACE 20+A0 hex Always and everywhere a blank space
// Intermediate 20-2F hex !"#$%&'()*+,-./
var intermeds = getByteRange(0x20, 0x2F)
// Parameters 30-3F hex 0123456789:;<=>?
// CSI Parameters 30-39, 3B hex 0123456789;
var csiParams = getByteRange(0x30, 0x3F)
var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
var upperCase = getByteRange(0x40, 0x5F)
// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
var lowerCase = getByteRange(0x60, 0x7E)
// Alphabetics 40-7E hex (all of upper and lower case)
var alphabetics = append(upperCase, lowerCase...)
var printables = getByteRange(0x20, 0x7F)
var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
var escapeToGroundBytes = getEscapeToGroundBytes()
// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
// byte ranges below
func getEscapeToGroundBytes() []byte {
escapeToGroundBytes := getByteRange(0x30, 0x4F)
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
return escapeToGroundBytes
}
func getExecuteBytes() []byte {
executeBytes := getByteRange(0x00, 0x17)
executeBytes = append(executeBytes, 0x19)
executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
return executeBytes
}
func getToGroundBytes() []byte {
groundBytes := []byte{0x18}
groundBytes = append(groundBytes, 0x1A)
groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
groundBytes = append(groundBytes, 0x99)
groundBytes = append(groundBytes, 0x9A)
groundBytes = append(groundBytes, 0x9C)
return groundBytes
}
// Delete 7F hex Always and everywhere ignored
// C1 Control 80-9F hex 32 additional control characters
// G1 Displayable A1-FE hex 94 additional displayable characters
// Special A0+FF hex Same as SPACE and DELETE

7
vendor/github.com/Azure/go-ansiterm/context.go generated vendored Normal file
View File

@ -0,0 +1,7 @@
package ansiterm
type ansiContext struct {
currentChar byte
paramBuffer []byte
interBuffer []byte
}

49
vendor/github.com/Azure/go-ansiterm/csi_entry_state.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package ansiterm
type csiEntryState struct {
baseState
}
func (csiState csiEntryState) Handle(b byte) (s state, e error) {
csiState.parser.logf("CsiEntry::Handle %#x", b)
nextState, err := csiState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(alphabetics, b):
return csiState.parser.ground, nil
case sliceContains(csiCollectables, b):
return csiState.parser.csiParam, nil
case sliceContains(executors, b):
return csiState, csiState.parser.execute()
}
return csiState, nil
}
func (csiState csiEntryState) Transition(s state) error {
csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
csiState.baseState.Transition(s)
switch s {
case csiState.parser.ground:
return csiState.parser.csiDispatch()
case csiState.parser.csiParam:
switch {
case sliceContains(csiParams, csiState.parser.context.currentChar):
csiState.parser.collectParam()
case sliceContains(intermeds, csiState.parser.context.currentChar):
csiState.parser.collectInter()
}
}
return nil
}
func (csiState csiEntryState) Enter() error {
csiState.parser.clear()
return nil
}

38
vendor/github.com/Azure/go-ansiterm/csi_param_state.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
package ansiterm
type csiParamState struct {
baseState
}
func (csiState csiParamState) Handle(b byte) (s state, e error) {
csiState.parser.logf("CsiParam::Handle %#x", b)
nextState, err := csiState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(alphabetics, b):
return csiState.parser.ground, nil
case sliceContains(csiCollectables, b):
csiState.parser.collectParam()
return csiState, nil
case sliceContains(executors, b):
return csiState, csiState.parser.execute()
}
return csiState, nil
}
func (csiState csiParamState) Transition(s state) error {
csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
csiState.baseState.Transition(s)
switch s {
case csiState.parser.ground:
return csiState.parser.csiDispatch()
}
return nil
}

View File

@ -0,0 +1,36 @@
package ansiterm
type escapeIntermediateState struct {
baseState
}
func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
escState.parser.logf("escapeIntermediateState::Handle %#x", b)
nextState, err := escState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(intermeds, b):
return escState, escState.parser.collectInter()
case sliceContains(executors, b):
return escState, escState.parser.execute()
case sliceContains(escapeIntermediateToGroundBytes, b):
return escState.parser.ground, nil
}
return escState, nil
}
func (escState escapeIntermediateState) Transition(s state) error {
escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
escState.baseState.Transition(s)
switch s {
case escState.parser.ground:
return escState.parser.escDispatch()
}
return nil
}

47
vendor/github.com/Azure/go-ansiterm/escape_state.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package ansiterm
type escapeState struct {
baseState
}
func (escState escapeState) Handle(b byte) (s state, e error) {
escState.parser.logf("escapeState::Handle %#x", b)
nextState, err := escState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case b == ANSI_ESCAPE_SECONDARY:
return escState.parser.csiEntry, nil
case b == ANSI_OSC_STRING_ENTRY:
return escState.parser.oscString, nil
case sliceContains(executors, b):
return escState, escState.parser.execute()
case sliceContains(escapeToGroundBytes, b):
return escState.parser.ground, nil
case sliceContains(intermeds, b):
return escState.parser.escapeIntermediate, nil
}
return escState, nil
}
func (escState escapeState) Transition(s state) error {
escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name())
escState.baseState.Transition(s)
switch s {
case escState.parser.ground:
return escState.parser.escDispatch()
case escState.parser.escapeIntermediate:
return escState.parser.collectInter()
}
return nil
}
func (escState escapeState) Enter() error {
escState.parser.clear()
return nil
}

90
vendor/github.com/Azure/go-ansiterm/event_handler.go generated vendored Normal file
View File

@ -0,0 +1,90 @@
package ansiterm
type AnsiEventHandler interface {
// Print
Print(b byte) error
// Execute C0 commands
Execute(b byte) error
// CUrsor Up
CUU(int) error
// CUrsor Down
CUD(int) error
// CUrsor Forward
CUF(int) error
// CUrsor Backward
CUB(int) error
// Cursor to Next Line
CNL(int) error
// Cursor to Previous Line
CPL(int) error
// Cursor Horizontal position Absolute
CHA(int) error
// Vertical line Position Absolute
VPA(int) error
// CUrsor Position
CUP(int, int) error
// Horizontal and Vertical Position (depends on PUM)
HVP(int, int) error
// Text Cursor Enable Mode
DECTCEM(bool) error
// Origin Mode
DECOM(bool) error
// 132 Column Mode
DECCOLM(bool) error
// Erase in Display
ED(int) error
// Erase in Line
EL(int) error
// Insert Line
IL(int) error
// Delete Line
DL(int) error
// Insert Character
ICH(int) error
// Delete Character
DCH(int) error
// Set Graphics Rendition
SGR([]int) error
// Pan Down
SU(int) error
// Pan Up
SD(int) error
// Device Attributes
DA([]string) error
// Set Top and Bottom Margins
DECSTBM(int, int) error
// Index
IND() error
// Reverse Index
RI() error
// Flush updates from previous commands
Flush() error
}

24
vendor/github.com/Azure/go-ansiterm/ground_state.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
package ansiterm
type groundState struct {
baseState
}
func (gs groundState) Handle(b byte) (s state, e error) {
gs.parser.context.currentChar = b
nextState, err := gs.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(printables, b):
return gs, gs.parser.print()
case sliceContains(executors, b):
return gs, gs.parser.execute()
}
return gs, nil
}

View File

@ -0,0 +1,31 @@
package ansiterm
type oscStringState struct {
baseState
}
func (oscState oscStringState) Handle(b byte) (s state, e error) {
oscState.parser.logf("OscString::Handle %#x", b)
nextState, err := oscState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case isOscStringTerminator(b):
return oscState.parser.ground, nil
}
return oscState, nil
}
// See below for OSC string terminators for linux
// http://man7.org/linux/man-pages/man4/console_codes.4.html
func isOscStringTerminator(b byte) bool {
if b == ANSI_BEL || b == 0x5C {
return true
}
return false
}

151
vendor/github.com/Azure/go-ansiterm/parser.go generated vendored Normal file
View File

@ -0,0 +1,151 @@
package ansiterm
import (
"errors"
"log"
"os"
)
type AnsiParser struct {
currState state
eventHandler AnsiEventHandler
context *ansiContext
csiEntry state
csiParam state
dcsEntry state
escape state
escapeIntermediate state
error state
ground state
oscString state
stateMap []state
logf func(string, ...interface{})
}
type Option func(*AnsiParser)
func WithLogf(f func(string, ...interface{})) Option {
return func(ap *AnsiParser) {
ap.logf = f
}
}
func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
ap := &AnsiParser{
eventHandler: evtHandler,
context: &ansiContext{},
}
for _, o := range opts {
o(ap)
}
if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
logFile, _ := os.Create("ansiParser.log")
logger := log.New(logFile, "", log.LstdFlags)
if ap.logf != nil {
l := ap.logf
ap.logf = func(s string, v ...interface{}) {
l(s, v...)
logger.Printf(s, v...)
}
} else {
ap.logf = logger.Printf
}
}
if ap.logf == nil {
ap.logf = func(string, ...interface{}) {}
}
ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
ap.error = errorState{baseState{name: "Error", parser: ap}}
ap.ground = groundState{baseState{name: "Ground", parser: ap}}
ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}
ap.stateMap = []state{
ap.csiEntry,
ap.csiParam,
ap.dcsEntry,
ap.escape,
ap.escapeIntermediate,
ap.error,
ap.ground,
ap.oscString,
}
ap.currState = getState(initialState, ap.stateMap)
ap.logf("CreateParser: parser %p", ap)
return ap
}
func getState(name string, states []state) state {
for _, el := range states {
if el.Name() == name {
return el
}
}
return nil
}
func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
for i, b := range bytes {
if err := ap.handle(b); err != nil {
return i, err
}
}
return len(bytes), ap.eventHandler.Flush()
}
func (ap *AnsiParser) handle(b byte) error {
ap.context.currentChar = b
newState, err := ap.currState.Handle(b)
if err != nil {
return err
}
if newState == nil {
ap.logf("WARNING: newState is nil")
return errors.New("New state of 'nil' is invalid.")
}
if newState != ap.currState {
if err := ap.changeState(newState); err != nil {
return err
}
}
return nil
}
func (ap *AnsiParser) changeState(newState state) error {
ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
// Exit old state
if err := ap.currState.Exit(); err != nil {
ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
return err
}
// Perform transition action
if err := ap.currState.Transition(newState); err != nil {
ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
return err
}
// Enter new state
if err := newState.Enter(); err != nil {
ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
return err
}
ap.currState = newState
return nil
}

View File

@ -0,0 +1,99 @@
package ansiterm
import (
"strconv"
)
func parseParams(bytes []byte) ([]string, error) {
paramBuff := make([]byte, 0, 0)
params := []string{}
for _, v := range bytes {
if v == ';' {
if len(paramBuff) > 0 {
// Completed parameter, append it to the list
s := string(paramBuff)
params = append(params, s)
paramBuff = make([]byte, 0, 0)
}
} else {
paramBuff = append(paramBuff, v)
}
}
// Last parameter may not be terminated with ';'
if len(paramBuff) > 0 {
s := string(paramBuff)
params = append(params, s)
}
return params, nil
}
func parseCmd(context ansiContext) (string, error) {
return string(context.currentChar), nil
}
func getInt(params []string, dflt int) int {
i := getInts(params, 1, dflt)[0]
return i
}
func getInts(params []string, minCount int, dflt int) []int {
ints := []int{}
for _, v := range params {
i, _ := strconv.Atoi(v)
// Zero is mapped to the default value in VT100.
if i == 0 {
i = dflt
}
ints = append(ints, i)
}
if len(ints) < minCount {
remaining := minCount - len(ints)
for i := 0; i < remaining; i++ {
ints = append(ints, dflt)
}
}
return ints
}
func (ap *AnsiParser) modeDispatch(param string, set bool) error {
switch param {
case "?3":
return ap.eventHandler.DECCOLM(set)
case "?6":
return ap.eventHandler.DECOM(set)
case "?25":
return ap.eventHandler.DECTCEM(set)
}
return nil
}
func (ap *AnsiParser) hDispatch(params []string) error {
if len(params) == 1 {
return ap.modeDispatch(params[0], true)
}
return nil
}
func (ap *AnsiParser) lDispatch(params []string) error {
if len(params) == 1 {
return ap.modeDispatch(params[0], false)
}
return nil
}
func getEraseParam(params []string) int {
param := getInt(params, 0)
if param < 0 || 3 < param {
param = 0
}
return param
}

119
vendor/github.com/Azure/go-ansiterm/parser_actions.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
package ansiterm
func (ap *AnsiParser) collectParam() error {
currChar := ap.context.currentChar
ap.logf("collectParam %#x", currChar)
ap.context.paramBuffer = append(ap.context.paramBuffer, currChar)
return nil
}
func (ap *AnsiParser) collectInter() error {
currChar := ap.context.currentChar
ap.logf("collectInter %#x", currChar)
ap.context.paramBuffer = append(ap.context.interBuffer, currChar)
return nil
}
func (ap *AnsiParser) escDispatch() error {
cmd, _ := parseCmd(*ap.context)
intermeds := ap.context.interBuffer
ap.logf("escDispatch currentChar: %#x", ap.context.currentChar)
ap.logf("escDispatch: %v(%v)", cmd, intermeds)
switch cmd {
case "D": // IND
return ap.eventHandler.IND()
case "E": // NEL, equivalent to CRLF
err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
if err == nil {
err = ap.eventHandler.Execute(ANSI_LINE_FEED)
}
return err
case "M": // RI
return ap.eventHandler.RI()
}
return nil
}
func (ap *AnsiParser) csiDispatch() error {
cmd, _ := parseCmd(*ap.context)
params, _ := parseParams(ap.context.paramBuffer)
ap.logf("Parsed params: %v with length: %d", params, len(params))
ap.logf("csiDispatch: %v(%v)", cmd, params)
switch cmd {
case "@":
return ap.eventHandler.ICH(getInt(params, 1))
case "A":
return ap.eventHandler.CUU(getInt(params, 1))
case "B":
return ap.eventHandler.CUD(getInt(params, 1))
case "C":
return ap.eventHandler.CUF(getInt(params, 1))
case "D":
return ap.eventHandler.CUB(getInt(params, 1))
case "E":
return ap.eventHandler.CNL(getInt(params, 1))
case "F":
return ap.eventHandler.CPL(getInt(params, 1))
case "G":
return ap.eventHandler.CHA(getInt(params, 1))
case "H":
ints := getInts(params, 2, 1)
x, y := ints[0], ints[1]
return ap.eventHandler.CUP(x, y)
case "J":
param := getEraseParam(params)
return ap.eventHandler.ED(param)
case "K":
param := getEraseParam(params)
return ap.eventHandler.EL(param)
case "L":
return ap.eventHandler.IL(getInt(params, 1))
case "M":
return ap.eventHandler.DL(getInt(params, 1))
case "P":
return ap.eventHandler.DCH(getInt(params, 1))
case "S":
return ap.eventHandler.SU(getInt(params, 1))
case "T":
return ap.eventHandler.SD(getInt(params, 1))
case "c":
return ap.eventHandler.DA(params)
case "d":
return ap.eventHandler.VPA(getInt(params, 1))
case "f":
ints := getInts(params, 2, 1)
x, y := ints[0], ints[1]
return ap.eventHandler.HVP(x, y)
case "h":
return ap.hDispatch(params)
case "l":
return ap.lDispatch(params)
case "m":
return ap.eventHandler.SGR(getInts(params, 1, 0))
case "r":
ints := getInts(params, 2, 1)
top, bottom := ints[0], ints[1]
return ap.eventHandler.DECSTBM(top, bottom)
default:
ap.logf("ERROR: Unsupported CSI command: '%s', with full context: %v", cmd, ap.context)
return nil
}
}
func (ap *AnsiParser) print() error {
return ap.eventHandler.Print(ap.context.currentChar)
}
func (ap *AnsiParser) clear() error {
ap.context = &ansiContext{}
return nil
}
func (ap *AnsiParser) execute() error {
return ap.eventHandler.Execute(ap.context.currentChar)
}

71
vendor/github.com/Azure/go-ansiterm/states.go generated vendored Normal file
View File

@ -0,0 +1,71 @@
package ansiterm
type stateID int
type state interface {
Enter() error
Exit() error
Handle(byte) (state, error)
Name() string
Transition(state) error
}
type baseState struct {
name string
parser *AnsiParser
}
func (base baseState) Enter() error {
return nil
}
func (base baseState) Exit() error {
return nil
}
func (base baseState) Handle(b byte) (s state, e error) {
switch {
case b == CSI_ENTRY:
return base.parser.csiEntry, nil
case b == DCS_ENTRY:
return base.parser.dcsEntry, nil
case b == ANSI_ESCAPE_PRIMARY:
return base.parser.escape, nil
case b == OSC_STRING:
return base.parser.oscString, nil
case sliceContains(toGroundBytes, b):
return base.parser.ground, nil
}
return nil, nil
}
func (base baseState) Name() string {
return base.name
}
func (base baseState) Transition(s state) error {
if s == base.parser.ground {
execBytes := []byte{0x18}
execBytes = append(execBytes, 0x1A)
execBytes = append(execBytes, getByteRange(0x80, 0x8F)...)
execBytes = append(execBytes, getByteRange(0x91, 0x97)...)
execBytes = append(execBytes, 0x99)
execBytes = append(execBytes, 0x9A)
if sliceContains(execBytes, base.parser.context.currentChar) {
return base.parser.execute()
}
}
return nil
}
type dcsEntryState struct {
baseState
}
type errorState struct {
baseState
}

21
vendor/github.com/Azure/go-ansiterm/utilities.go generated vendored Normal file
View File

@ -0,0 +1,21 @@
package ansiterm
import (
"strconv"
)
func sliceContains(bytes []byte, b byte) bool {
for _, v := range bytes {
if v == b {
return true
}
}
return false
}
func convertBytesToInteger(bytes []byte) int {
s := string(bytes)
i, _ := strconv.Atoi(s)
return i
}

182
vendor/github.com/Azure/go-ansiterm/winterm/ansi.go generated vendored Normal file
View File

@ -0,0 +1,182 @@
// +build windows
package winterm
import (
"fmt"
"os"
"strconv"
"strings"
"syscall"
"github.com/Azure/go-ansiterm"
)
// Windows keyboard constants
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx.
const (
VK_PRIOR = 0x21 // PAGE UP key
VK_NEXT = 0x22 // PAGE DOWN key
VK_END = 0x23 // END key
VK_HOME = 0x24 // HOME key
VK_LEFT = 0x25 // LEFT ARROW key
VK_UP = 0x26 // UP ARROW key
VK_RIGHT = 0x27 // RIGHT ARROW key
VK_DOWN = 0x28 // DOWN ARROW key
VK_SELECT = 0x29 // SELECT key
VK_PRINT = 0x2A // PRINT key
VK_EXECUTE = 0x2B // EXECUTE key
VK_SNAPSHOT = 0x2C // PRINT SCREEN key
VK_INSERT = 0x2D // INS key
VK_DELETE = 0x2E // DEL key
VK_HELP = 0x2F // HELP key
VK_F1 = 0x70 // F1 key
VK_F2 = 0x71 // F2 key
VK_F3 = 0x72 // F3 key
VK_F4 = 0x73 // F4 key
VK_F5 = 0x74 // F5 key
VK_F6 = 0x75 // F6 key
VK_F7 = 0x76 // F7 key
VK_F8 = 0x77 // F8 key
VK_F9 = 0x78 // F9 key
VK_F10 = 0x79 // F10 key
VK_F11 = 0x7A // F11 key
VK_F12 = 0x7B // F12 key
RIGHT_ALT_PRESSED = 0x0001
LEFT_ALT_PRESSED = 0x0002
RIGHT_CTRL_PRESSED = 0x0004
LEFT_CTRL_PRESSED = 0x0008
SHIFT_PRESSED = 0x0010
NUMLOCK_ON = 0x0020
SCROLLLOCK_ON = 0x0040
CAPSLOCK_ON = 0x0080
ENHANCED_KEY = 0x0100
)
type ansiCommand struct {
CommandBytes []byte
Command string
Parameters []string
IsSpecial bool
}
func newAnsiCommand(command []byte) *ansiCommand {
if isCharacterSelectionCmdChar(command[1]) {
// Is Character Set Selection commands
return &ansiCommand{
CommandBytes: command,
Command: string(command),
IsSpecial: true,
}
}
// last char is command character
lastCharIndex := len(command) - 1
ac := &ansiCommand{
CommandBytes: command,
Command: string(command[lastCharIndex]),
IsSpecial: false,
}
// more than a single escape
if lastCharIndex != 0 {
start := 1
// skip if double char escape sequence
if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
start++
}
// convert this to GetNextParam method
ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
}
return ac
}
func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
if index < 0 || index >= len(ac.Parameters) {
return defaultValue
}
param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
if err != nil {
return defaultValue
}
return int16(param)
}
func (ac *ansiCommand) String() string {
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
bytesToHex(ac.CommandBytes),
ac.Command,
strings.Join(ac.Parameters, "\",\""))
}
// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
func isAnsiCommandChar(b byte) bool {
switch {
case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
return true
case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
// non-CSI escape sequence terminator
return true
case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL:
// String escape sequence terminator
return true
}
return false
}
func isXtermOscSequence(command []byte, current byte) bool {
return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
}
func isCharacterSelectionCmdChar(b byte) bool {
return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3)
}
// bytesToHex converts a slice of bytes to a human-readable string.
func bytesToHex(b []byte) string {
hex := make([]string, len(b))
for i, ch := range b {
hex[i] = fmt.Sprintf("%X", ch)
}
return strings.Join(hex, "")
}
// ensureInRange adjusts the passed value, if necessary, to ensure it is within
// the passed min / max range.
func ensureInRange(n int16, min int16, max int16) int16 {
if n < min {
return min
} else if n > max {
return max
} else {
return n
}
}
func GetStdFile(nFile int) (*os.File, uintptr) {
var file *os.File
switch nFile {
case syscall.STD_INPUT_HANDLE:
file = os.Stdin
case syscall.STD_OUTPUT_HANDLE:
file = os.Stdout
case syscall.STD_ERROR_HANDLE:
file = os.Stderr
default:
panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
}
fd, err := syscall.GetStdHandle(nFile)
if err != nil {
panic(fmt.Errorf("Invalid standard handle identifier: %v -- %v", nFile, err))
}
return file, uintptr(fd)
}

327
vendor/github.com/Azure/go-ansiterm/winterm/api.go generated vendored Normal file
View File

@ -0,0 +1,327 @@
// +build windows
package winterm
import (
"fmt"
"syscall"
"unsafe"
)
//===========================================================================================================
// IMPORTANT NOTE:
//
// The methods below make extensive use of the "unsafe" package to obtain the required pointers.
// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack
// variables) the pointers reference *before* the API completes.
//
// As a result, in those cases, the code must hint that the variables remain in active by invoking the
// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer
// require unsafe pointers.
//
// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform
// the garbage collector the variables remain in use if:
//
// -- The value is not a pointer (e.g., int32, struct)
// -- The value is not referenced by the method after passing the pointer to Windows
//
// See http://golang.org/doc/go1.3.
//===========================================================================================================
var (
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo")
setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo")
setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition")
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo")
writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW")
readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW")
waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject")
)
// Windows Console constants
const (
// Console modes
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
ENABLE_PROCESSED_INPUT = 0x0001
ENABLE_LINE_INPUT = 0x0002
ENABLE_ECHO_INPUT = 0x0004
ENABLE_WINDOW_INPUT = 0x0008
ENABLE_MOUSE_INPUT = 0x0010
ENABLE_INSERT_MODE = 0x0020
ENABLE_QUICK_EDIT_MODE = 0x0040
ENABLE_EXTENDED_FLAGS = 0x0080
ENABLE_AUTO_POSITION = 0x0100
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
// Character attributes
// Note:
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
// Clearing all foreground or background colors results in black; setting all creates white.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
FOREGROUND_BLUE uint16 = 0x0001
FOREGROUND_GREEN uint16 = 0x0002
FOREGROUND_RED uint16 = 0x0004
FOREGROUND_INTENSITY uint16 = 0x0008
FOREGROUND_MASK uint16 = 0x000F
BACKGROUND_BLUE uint16 = 0x0010
BACKGROUND_GREEN uint16 = 0x0020
BACKGROUND_RED uint16 = 0x0040
BACKGROUND_INTENSITY uint16 = 0x0080
BACKGROUND_MASK uint16 = 0x00F0
COMMON_LVB_MASK uint16 = 0xFF00
COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
COMMON_LVB_UNDERSCORE uint16 = 0x8000
// Input event types
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
KEY_EVENT = 0x0001
MOUSE_EVENT = 0x0002
WINDOW_BUFFER_SIZE_EVENT = 0x0004
MENU_EVENT = 0x0008
FOCUS_EVENT = 0x0010
// WaitForSingleObject return codes
WAIT_ABANDONED = 0x00000080
WAIT_FAILED = 0xFFFFFFFF
WAIT_SIGNALED = 0x0000000
WAIT_TIMEOUT = 0x00000102
// WaitForSingleObject wait duration
WAIT_INFINITE = 0xFFFFFFFF
WAIT_ONE_SECOND = 1000
WAIT_HALF_SECOND = 500
WAIT_QUARTER_SECOND = 250
)
// Windows API Console types
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
type (
CHAR_INFO struct {
UnicodeChar uint16
Attributes uint16
}
CONSOLE_CURSOR_INFO struct {
Size uint32
Visible int32
}
CONSOLE_SCREEN_BUFFER_INFO struct {
Size COORD
CursorPosition COORD
Attributes uint16
Window SMALL_RECT
MaximumWindowSize COORD
}
COORD struct {
X int16
Y int16
}
SMALL_RECT struct {
Left int16
Top int16
Right int16
Bottom int16
}
// INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
INPUT_RECORD struct {
EventType uint16
KeyEvent KEY_EVENT_RECORD
}
KEY_EVENT_RECORD struct {
KeyDown int32
RepeatCount uint16
VirtualKeyCode uint16
VirtualScanCode uint16
UnicodeChar uint16
ControlKeyState uint32
}
WINDOW_BUFFER_SIZE struct {
Size COORD
}
)
// boolToBOOL converts a Go bool into a Windows int32.
func boolToBOOL(f bool) int32 {
if f {
return int32(1)
} else {
return int32(0)
}
}
// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
return checkError(r1, r2, err)
}
// SetConsoleCursorInfo sets the size and visiblity of the console cursor.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
return checkError(r1, r2, err)
}
// SetConsoleCursorPosition location of the console cursor.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
use(coord)
return checkError(r1, r2, err)
}
// GetConsoleMode gets the console mode for given file descriptor
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
func GetConsoleMode(handle uintptr) (mode uint32, err error) {
err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
return mode, err
}
// SetConsoleMode sets the console mode for given file descriptor
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
func SetConsoleMode(handle uintptr, mode uint32) error {
r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
use(mode)
return checkError(r1, r2, err)
}
// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
info := CONSOLE_SCREEN_BUFFER_INFO{}
err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
if err != nil {
return nil, err
}
return &info, nil
}
func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
use(scrollRect)
use(clipRect)
use(destOrigin)
use(char)
return checkError(r1, r2, err)
}
// SetConsoleScreenBufferSize sets the size of the console screen buffer.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
use(coord)
return checkError(r1, r2, err)
}
// SetConsoleTextAttribute sets the attributes of characters written to the
// console screen buffer by the WriteFile or WriteConsole function.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
use(attribute)
return checkError(r1, r2, err)
}
// SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
// Note that the size and location must be within and no larger than the backing console screen buffer.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
use(isAbsolute)
use(rect)
return checkError(r1, r2, err)
}
// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
use(buffer)
use(bufferSize)
use(bufferCoord)
return checkError(r1, r2, err)
}
// ReadConsoleInput reads (and removes) data from the console input buffer.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
use(buffer)
return checkError(r1, r2, err)
}
// WaitForSingleObject waits for the passed handle to be signaled.
// It returns true if the handle was signaled; false otherwise.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
switch r1 {
case WAIT_ABANDONED, WAIT_TIMEOUT:
return false, nil
case WAIT_SIGNALED:
return true, nil
}
use(msWait)
return false, err
}
// String helpers
func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
}
func (coord COORD) String() string {
return fmt.Sprintf("%v,%v", coord.X, coord.Y)
}
func (rect SMALL_RECT) String() string {
return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
}
// checkError evaluates the results of a Windows API call and returns the error if it failed.
func checkError(r1, r2 uintptr, err error) error {
// Windows APIs return non-zero to indicate success
if r1 != 0 {
return nil
}
// Return the error if provided, otherwise default to EINVAL
if err != nil {
return err
}
return syscall.EINVAL
}
// coordToPointer converts a COORD into a uintptr (by fooling the type system).
func coordToPointer(c COORD) uintptr {
// Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
return uintptr(*((*uint32)(unsafe.Pointer(&c))))
}
// use is a no-op, but the compiler cannot see that it is.
// Calling use(p) ensures that p is kept live until that point.
func use(p interface{}) {}

View File

@ -0,0 +1,100 @@
// +build windows
package winterm
import "github.com/Azure/go-ansiterm"
const (
FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
)
// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
// request represented by the passed ANSI mode.
func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) {
switch ansiMode {
// Mode styles
case ansiterm.ANSI_SGR_BOLD:
windowsMode = windowsMode | FOREGROUND_INTENSITY
case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF:
windowsMode &^= FOREGROUND_INTENSITY
case ansiterm.ANSI_SGR_UNDERLINE:
windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
case ansiterm.ANSI_SGR_REVERSE:
inverted = true
case ansiterm.ANSI_SGR_REVERSE_OFF:
inverted = false
case ansiterm.ANSI_SGR_UNDERLINE_OFF:
windowsMode &^= COMMON_LVB_UNDERSCORE
// Foreground colors
case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT:
windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
case ansiterm.ANSI_SGR_FOREGROUND_BLACK:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
case ansiterm.ANSI_SGR_FOREGROUND_RED:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
case ansiterm.ANSI_SGR_FOREGROUND_GREEN:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
case ansiterm.ANSI_SGR_FOREGROUND_YELLOW:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
case ansiterm.ANSI_SGR_FOREGROUND_BLUE:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
case ansiterm.ANSI_SGR_FOREGROUND_CYAN:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
case ansiterm.ANSI_SGR_FOREGROUND_WHITE:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
// Background colors
case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT:
// Black with no intensity
windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
case ansiterm.ANSI_SGR_BACKGROUND_BLACK:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
case ansiterm.ANSI_SGR_BACKGROUND_RED:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
case ansiterm.ANSI_SGR_BACKGROUND_GREEN:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
case ansiterm.ANSI_SGR_BACKGROUND_YELLOW:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
case ansiterm.ANSI_SGR_BACKGROUND_BLUE:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
case ansiterm.ANSI_SGR_BACKGROUND_CYAN:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
case ansiterm.ANSI_SGR_BACKGROUND_WHITE:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
}
return windowsMode, inverted
}
// invertAttributes inverts the foreground and background colors of a Windows attributes value
func invertAttributes(windowsMode uint16) uint16 {
return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
}

View File

@ -0,0 +1,101 @@
// +build windows
package winterm
const (
horizontal = iota
vertical
)
func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
if h.originMode {
sr := h.effectiveSr(info.Window)
return SMALL_RECT{
Top: sr.top,
Bottom: sr.bottom,
Left: 0,
Right: info.Size.X - 1,
}
} else {
return SMALL_RECT{
Top: info.Window.Top,
Bottom: info.Window.Bottom,
Left: 0,
Right: info.Size.X - 1,
}
}
}
// setCursorPosition sets the cursor to the specified position, bounded to the screen size
func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
position.X = ensureInRange(position.X, window.Left, window.Right)
position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
err := SetConsoleCursorPosition(h.fd, position)
if err != nil {
return err
}
h.logf("Cursor position set: (%d, %d)", position.X, position.Y)
return err
}
func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error {
return h.moveCursor(vertical, param)
}
func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error {
return h.moveCursor(horizontal, param)
}
func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
position := info.CursorPosition
switch moveMode {
case horizontal:
position.X += int16(param)
case vertical:
position.Y += int16(param)
}
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
position := info.CursorPosition
position.X = 0
position.Y += int16(param)
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
position := info.CursorPosition
position.X = int16(param) - 1
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,84 @@
// +build windows
package winterm
import "github.com/Azure/go-ansiterm"
func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error {
// Ignore an invalid (negative area) request
if toCoord.Y < fromCoord.Y {
return nil
}
var err error
var coordStart = COORD{}
var coordEnd = COORD{}
xCurrent, yCurrent := fromCoord.X, fromCoord.Y
xEnd, yEnd := toCoord.X, toCoord.Y
// Clear any partial initial line
if xCurrent > 0 {
coordStart.X, coordStart.Y = xCurrent, yCurrent
coordEnd.X, coordEnd.Y = xEnd, yCurrent
err = h.clearRect(attributes, coordStart, coordEnd)
if err != nil {
return err
}
xCurrent = 0
yCurrent += 1
}
// Clear intervening rectangular section
if yCurrent < yEnd {
coordStart.X, coordStart.Y = xCurrent, yCurrent
coordEnd.X, coordEnd.Y = xEnd, yEnd-1
err = h.clearRect(attributes, coordStart, coordEnd)
if err != nil {
return err
}
xCurrent = 0
yCurrent = yEnd
}
// Clear remaining partial ending line
coordStart.X, coordStart.Y = xCurrent, yCurrent
coordEnd.X, coordEnd.Y = xEnd, yEnd
err = h.clearRect(attributes, coordStart, coordEnd)
if err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
width := toCoord.X - fromCoord.X + 1
height := toCoord.Y - fromCoord.Y + 1
size := uint32(width) * uint32(height)
if size <= 0 {
return nil
}
buffer := make([]CHAR_INFO, size)
char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes}
for i := 0; i < int(size); i++ {
buffer[i] = char
}
err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &region)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,118 @@
// +build windows
package winterm
// effectiveSr gets the current effective scroll region in buffer coordinates
func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom)
bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
if top >= bottom {
top = window.Top
bottom = window.Bottom
}
return scrollRegion{top: top, bottom: bottom}
}
func (h *windowsAnsiEventHandler) scrollUp(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
sr := h.effectiveSr(info.Window)
return h.scroll(param, sr, info)
}
func (h *windowsAnsiEventHandler) scrollDown(param int) error {
return h.scrollUp(-param)
}
func (h *windowsAnsiEventHandler) deleteLines(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
start := info.CursorPosition.Y
sr := h.effectiveSr(info.Window)
// Lines cannot be inserted or deleted outside the scrolling region.
if start >= sr.top && start <= sr.bottom {
sr.top = start
return h.scroll(param, sr, info)
} else {
return nil
}
}
func (h *windowsAnsiEventHandler) insertLines(param int) error {
return h.deleteLines(-param)
}
// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
h.logf("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
h.logf("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
// Copy from and clip to the scroll region (full buffer width)
scrollRect := SMALL_RECT{
Top: sr.top,
Bottom: sr.bottom,
Left: 0,
Right: info.Size.X - 1,
}
// Origin to which area should be copied
destOrigin := COORD{
X: 0,
Y: sr.top - int16(param),
}
char := CHAR_INFO{
UnicodeChar: ' ',
Attributes: h.attributes,
}
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
return h.scrollLine(param, info.CursorPosition, info)
}
func (h *windowsAnsiEventHandler) insertCharacters(param int) error {
return h.deleteCharacters(-param)
}
// scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
// Copy from and clip to the scroll region (full buffer width)
scrollRect := SMALL_RECT{
Top: position.Y,
Bottom: position.Y,
Left: position.X,
Right: info.Size.X - 1,
}
// Origin to which area should be copied
destOrigin := COORD{
X: position.X - int16(columns),
Y: position.Y,
}
char := CHAR_INFO{
UnicodeChar: ' ',
Attributes: h.attributes,
}
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,9 @@
// +build windows
package winterm
// AddInRange increments a value by the passed quantity while ensuring the values
// always remain within the supplied min / max range.
func addInRange(n int16, increment int16, min int16, max int16) int16 {
return ensureInRange(n+increment, min, max)
}

View File

@ -0,0 +1,743 @@
// +build windows
package winterm
import (
"bytes"
"log"
"os"
"strconv"
"github.com/Azure/go-ansiterm"
)
type windowsAnsiEventHandler struct {
fd uintptr
file *os.File
infoReset *CONSOLE_SCREEN_BUFFER_INFO
sr scrollRegion
buffer bytes.Buffer
attributes uint16
inverted bool
wrapNext bool
drewMarginByte bool
originMode bool
marginByte byte
curInfo *CONSOLE_SCREEN_BUFFER_INFO
curPos COORD
logf func(string, ...interface{})
}
type Option func(*windowsAnsiEventHandler)
func WithLogf(f func(string, ...interface{})) Option {
return func(w *windowsAnsiEventHandler) {
w.logf = f
}
}
func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler {
infoReset, err := GetConsoleScreenBufferInfo(fd)
if err != nil {
return nil
}
h := &windowsAnsiEventHandler{
fd: fd,
file: file,
infoReset: infoReset,
attributes: infoReset.Attributes,
}
for _, o := range opts {
o(h)
}
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
logFile, _ := os.Create("winEventHandler.log")
logger := log.New(logFile, "", log.LstdFlags)
if h.logf != nil {
l := h.logf
h.logf = func(s string, v ...interface{}) {
l(s, v...)
logger.Printf(s, v...)
}
} else {
h.logf = logger.Printf
}
}
if h.logf == nil {
h.logf = func(string, ...interface{}) {}
}
return h
}
type scrollRegion struct {
top int16
bottom int16
}
// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
// current cursor position and scroll region settings, in which case it returns
// true. If no special handling is necessary, then it does nothing and returns
// false.
//
// In the false case, the caller should ensure that a carriage return
// and line feed are inserted or that the text is otherwise wrapped.
func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
if h.wrapNext {
if err := h.Flush(); err != nil {
return false, err
}
h.clearWrap()
}
pos, info, err := h.getCurrentInfo()
if err != nil {
return false, err
}
sr := h.effectiveSr(info.Window)
if pos.Y == sr.bottom {
// Scrolling is necessary. Let Windows automatically scroll if the scrolling region
// is the full window.
if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
if includeCR {
pos.X = 0
h.updatePos(pos)
}
return false, nil
}
// A custom scroll region is active. Scroll the window manually to simulate
// the LF.
if err := h.Flush(); err != nil {
return false, err
}
h.logf("Simulating LF inside scroll region")
if err := h.scrollUp(1); err != nil {
return false, err
}
if includeCR {
pos.X = 0
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return false, err
}
}
return true, nil
} else if pos.Y < info.Window.Bottom {
// Let Windows handle the LF.
pos.Y++
if includeCR {
pos.X = 0
}
h.updatePos(pos)
return false, nil
} else {
// The cursor is at the bottom of the screen but outside the scroll
// region. Skip the LF.
h.logf("Simulating LF outside scroll region")
if includeCR {
if err := h.Flush(); err != nil {
return false, err
}
pos.X = 0
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return false, err
}
}
return true, nil
}
}
// executeLF executes a LF without a CR.
func (h *windowsAnsiEventHandler) executeLF() error {
handled, err := h.simulateLF(false)
if err != nil {
return err
}
if !handled {
// Windows LF will reset the cursor column position. Write the LF
// and restore the cursor position.
pos, _, err := h.getCurrentInfo()
if err != nil {
return err
}
h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
if pos.X != 0 {
if err := h.Flush(); err != nil {
return err
}
h.logf("Resetting cursor position for LF without CR")
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return err
}
}
}
return nil
}
func (h *windowsAnsiEventHandler) Print(b byte) error {
if h.wrapNext {
h.buffer.WriteByte(h.marginByte)
h.clearWrap()
if _, err := h.simulateLF(true); err != nil {
return err
}
}
pos, info, err := h.getCurrentInfo()
if err != nil {
return err
}
if pos.X == info.Size.X-1 {
h.wrapNext = true
h.marginByte = b
} else {
pos.X++
h.updatePos(pos)
h.buffer.WriteByte(b)
}
return nil
}
func (h *windowsAnsiEventHandler) Execute(b byte) error {
switch b {
case ansiterm.ANSI_TAB:
h.logf("Execute(TAB)")
// Move to the next tab stop, but preserve auto-wrap if already set.
if !h.wrapNext {
pos, info, err := h.getCurrentInfo()
if err != nil {
return err
}
pos.X = (pos.X + 8) - pos.X%8
if pos.X >= info.Size.X {
pos.X = info.Size.X - 1
}
if err := h.Flush(); err != nil {
return err
}
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return err
}
}
return nil
case ansiterm.ANSI_BEL:
h.buffer.WriteByte(ansiterm.ANSI_BEL)
return nil
case ansiterm.ANSI_BACKSPACE:
if h.wrapNext {
if err := h.Flush(); err != nil {
return err
}
h.clearWrap()
}
pos, _, err := h.getCurrentInfo()
if err != nil {
return err
}
if pos.X > 0 {
pos.X--
h.updatePos(pos)
h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
}
return nil
case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
// Treat as true LF.
return h.executeLF()
case ansiterm.ANSI_LINE_FEED:
// Simulate a CR and LF for now since there is no way in go-ansiterm
// to tell if the LF should include CR (and more things break when it's
// missing than when it's incorrectly added).
handled, err := h.simulateLF(true)
if handled || err != nil {
return err
}
return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
case ansiterm.ANSI_CARRIAGE_RETURN:
if h.wrapNext {
if err := h.Flush(); err != nil {
return err
}
h.clearWrap()
}
pos, _, err := h.getCurrentInfo()
if err != nil {
return err
}
if pos.X != 0 {
pos.X = 0
h.updatePos(pos)
h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
}
return nil
default:
return nil
}
}
func (h *windowsAnsiEventHandler) CUU(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUU: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorVertical(-param)
}
func (h *windowsAnsiEventHandler) CUD(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUD: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorVertical(param)
}
func (h *windowsAnsiEventHandler) CUF(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUF: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorHorizontal(param)
}
func (h *windowsAnsiEventHandler) CUB(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUB: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorHorizontal(-param)
}
func (h *windowsAnsiEventHandler) CNL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CNL: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorLine(param)
}
func (h *windowsAnsiEventHandler) CPL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CPL: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorLine(-param)
}
func (h *windowsAnsiEventHandler) CHA(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CHA: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorColumn(param)
}
func (h *windowsAnsiEventHandler) VPA(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("VPA: [[%d]]", param)
h.clearWrap()
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
window := h.getCursorWindow(info)
position := info.CursorPosition
position.Y = window.Top + int16(param) - 1
return h.setCursorPosition(position, window)
}
func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUP: [[%d %d]]", row, col)
h.clearWrap()
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
window := h.getCursorWindow(info)
position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
return h.setCursorPosition(position, window)
}
func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("HVP: [[%d %d]]", row, col)
h.clearWrap()
return h.CUP(row, col)
}
func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
h.clearWrap()
return nil
}
func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DECOM: [%v]", []string{strconv.FormatBool(enable)})
h.clearWrap()
h.originMode = enable
return h.CUP(1, 1)
}
func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
h.clearWrap()
if err := h.ED(2); err != nil {
return err
}
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
targetWidth := int16(80)
if use132 {
targetWidth = 132
}
if info.Size.X < targetWidth {
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
h.logf("set buffer failed: %v", err)
return err
}
}
window := info.Window
window.Left = 0
window.Right = targetWidth - 1
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
h.logf("set window failed: %v", err)
return err
}
if info.Size.X > targetWidth {
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
h.logf("set buffer failed: %v", err)
return err
}
}
return SetConsoleCursorPosition(h.fd, COORD{0, 0})
}
func (h *windowsAnsiEventHandler) ED(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("ED: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
// [J -- Erases from the cursor to the end of the screen, including the cursor position.
// [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
// [2J -- Erases the complete display. The cursor does not move.
// Notes:
// -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
var start COORD
var end COORD
switch param {
case 0:
start = info.CursorPosition
end = COORD{info.Size.X - 1, info.Size.Y - 1}
case 1:
start = COORD{0, 0}
end = info.CursorPosition
case 2:
start = COORD{0, 0}
end = COORD{info.Size.X - 1, info.Size.Y - 1}
}
err = h.clearRange(h.attributes, start, end)
if err != nil {
return err
}
// If the whole buffer was cleared, move the window to the top while preserving
// the window-relative cursor position.
if param == 2 {
pos := info.CursorPosition
window := info.Window
pos.Y -= window.Top
window.Bottom -= window.Top
window.Top = 0
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return err
}
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
return err
}
}
return nil
}
func (h *windowsAnsiEventHandler) EL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("EL: [%v]", strconv.Itoa(param))
h.clearWrap()
// [K -- Erases from the cursor to the end of the line, including the cursor position.
// [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
// [2K -- Erases the complete line.
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
var start COORD
var end COORD
switch param {
case 0:
start = info.CursorPosition
end = COORD{info.Size.X, info.CursorPosition.Y}
case 1:
start = COORD{0, info.CursorPosition.Y}
end = info.CursorPosition
case 2:
start = COORD{0, info.CursorPosition.Y}
end = COORD{info.Size.X, info.CursorPosition.Y}
}
err = h.clearRange(h.attributes, start, end)
if err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) IL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("IL: [%v]", strconv.Itoa(param))
h.clearWrap()
return h.insertLines(param)
}
func (h *windowsAnsiEventHandler) DL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DL: [%v]", strconv.Itoa(param))
h.clearWrap()
return h.deleteLines(param)
}
func (h *windowsAnsiEventHandler) ICH(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("ICH: [%v]", strconv.Itoa(param))
h.clearWrap()
return h.insertCharacters(param)
}
func (h *windowsAnsiEventHandler) DCH(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DCH: [%v]", strconv.Itoa(param))
h.clearWrap()
return h.deleteCharacters(param)
}
func (h *windowsAnsiEventHandler) SGR(params []int) error {
if err := h.Flush(); err != nil {
return err
}
strings := []string{}
for _, v := range params {
strings = append(strings, strconv.Itoa(v))
}
h.logf("SGR: [%v]", strings)
if len(params) <= 0 {
h.attributes = h.infoReset.Attributes
h.inverted = false
} else {
for _, attr := range params {
if attr == ansiterm.ANSI_SGR_RESET {
h.attributes = h.infoReset.Attributes
h.inverted = false
continue
}
h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
}
}
attributes := h.attributes
if h.inverted {
attributes = invertAttributes(attributes)
}
err := SetConsoleTextAttribute(h.fd, attributes)
if err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) SU(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("SU: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.scrollUp(param)
}
func (h *windowsAnsiEventHandler) SD(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("SD: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.scrollDown(param)
}
func (h *windowsAnsiEventHandler) DA(params []string) error {
h.logf("DA: [%v]", params)
// DA cannot be implemented because it must send data on the VT100 input stream,
// which is not available to go-ansiterm.
return nil
}
func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DECSTBM: [%d, %d]", top, bottom)
// Windows is 0 indexed, Linux is 1 indexed
h.sr.top = int16(top - 1)
h.sr.bottom = int16(bottom - 1)
// This command also moves the cursor to the origin.
h.clearWrap()
return h.CUP(1, 1)
}
func (h *windowsAnsiEventHandler) RI() error {
if err := h.Flush(); err != nil {
return err
}
h.logf("RI: []")
h.clearWrap()
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
sr := h.effectiveSr(info.Window)
if info.CursorPosition.Y == sr.top {
return h.scrollDown(1)
}
return h.moveCursorVertical(-1)
}
func (h *windowsAnsiEventHandler) IND() error {
h.logf("IND: []")
return h.executeLF()
}
func (h *windowsAnsiEventHandler) Flush() error {
h.curInfo = nil
if h.buffer.Len() > 0 {
h.logf("Flush: [%s]", h.buffer.Bytes())
if _, err := h.buffer.WriteTo(h.file); err != nil {
return err
}
}
if h.wrapNext && !h.drewMarginByte {
h.logf("Flush: drawing margin byte '%c'", h.marginByte)
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
size := COORD{1, 1}
position := COORD{0, 0}
region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
if err := WriteConsoleOutput(h.fd, charInfo, size, position, &region); err != nil {
return err
}
h.drewMarginByte = true
}
return nil
}
// cacheConsoleInfo ensures that the current console screen information has been queried
// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
if h.curInfo == nil {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return COORD{}, nil, err
}
h.curInfo = info
h.curPos = info.CursorPosition
}
return h.curPos, h.curInfo, nil
}
func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
if h.curInfo == nil {
panic("failed to call getCurrentInfo before calling updatePos")
}
h.curPos = pos
}
// clearWrap clears the state where the cursor is in the margin
// waiting for the next character before wrapping the line. This must
// be done before most operations that act on the cursor.
func (h *windowsAnsiEventHandler) clearWrap() {
h.wrapNext = false
h.drewMarginByte = false
}

22
vendor/github.com/Microsoft/go-winio/LICENSE generated vendored Normal file
View 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
vendor/github.com/Microsoft/go-winio/README.md generated vendored Normal file
View 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.

280
vendor/github.com/Microsoft/go-winio/backup.go generated vendored Normal file
View File

@ -0,0 +1,280 @@
// +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 Read(). 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 s, ok := r.r.(io.Seeker); ok {
// Make sure Seek on io.SeekCurrent sometimes succeeds
// before trying the actual seek.
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
return nil, err
}
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}
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}
}
runtime.KeepAlive(r.f)
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)
runtime.KeepAlive(r.f)
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
}
// NewBackupFileWriter 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}
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}
}
runtime.KeepAlive(w.f)
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)
runtime.KeepAlive(w.f)
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
}

137
vendor/github.com/Microsoft/go-winio/ea.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package winio
import (
"bytes"
"encoding/binary"
"errors"
)
type fileFullEaInformation struct {
NextEntryOffset uint32
Flags uint8
NameLength uint8
ValueLength uint16
}
var (
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
errEaNameTooLarge = errors.New("extended attribute name too large")
errEaValueTooLarge = errors.New("extended attribute value too large")
)
// ExtendedAttribute represents a single Windows EA.
type ExtendedAttribute struct {
Name string
Value []byte
Flags uint8
}
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
var info fileFullEaInformation
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
if err != nil {
err = errInvalidEaBuffer
return
}
nameOffset := fileFullEaInformationSize
nameLen := int(info.NameLength)
valueOffset := nameOffset + int(info.NameLength) + 1
valueLen := int(info.ValueLength)
nextOffset := int(info.NextEntryOffset)
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
err = errInvalidEaBuffer
return
}
ea.Name = string(b[nameOffset : nameOffset+nameLen])
ea.Value = b[valueOffset : valueOffset+valueLen]
ea.Flags = info.Flags
if info.NextEntryOffset != 0 {
nb = b[info.NextEntryOffset:]
}
return
}
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
for len(b) != 0 {
ea, nb, err := parseEa(b)
if err != nil {
return nil, err
}
eas = append(eas, ea)
b = nb
}
return
}
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
if int(uint8(len(ea.Name))) != len(ea.Name) {
return errEaNameTooLarge
}
if int(uint16(len(ea.Value))) != len(ea.Value) {
return errEaValueTooLarge
}
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
withPadding := (entrySize + 3) &^ 3
nextOffset := uint32(0)
if !last {
nextOffset = withPadding
}
info := fileFullEaInformation{
NextEntryOffset: nextOffset,
Flags: ea.Flags,
NameLength: uint8(len(ea.Name)),
ValueLength: uint16(len(ea.Value)),
}
err := binary.Write(buf, binary.LittleEndian, &info)
if err != nil {
return err
}
_, err = buf.Write([]byte(ea.Name))
if err != nil {
return err
}
err = buf.WriteByte(0)
if err != nil {
return err
}
_, err = buf.Write(ea.Value)
if err != nil {
return err
}
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
if err != nil {
return err
}
return nil
}
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
// buffer for use with BackupWrite, ZwSetEaFile, etc.
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
var buf bytes.Buffer
for i := range eas {
last := false
if i == len(eas)-1 {
last = true
}
err := writeEa(&buf, &eas[i], last)
if err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}

323
vendor/github.com/Microsoft/go-winio/file.go generated vendored Normal file
View File

@ -0,0 +1,323 @@
// +build windows
package winio
import (
"errors"
"io"
"runtime"
"sync"
"sync/atomic"
"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 wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
func (b *atomicBool) swap(new bool) bool {
var newInt int32
if new {
newInt = 1
}
return atomic.SwapInt32((*int32)(b), newInt) == 1
}
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 }
type timeoutChan chan struct{}
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
wgLock sync.RWMutex
closing atomicBool
socket bool
readDeadline deadlineHandler
writeDeadline deadlineHandler
}
type deadlineHandler struct {
setLock sync.Mutex
channel timeoutChan
channelLock sync.RWMutex
timer *time.Timer
timedout atomicBool
}
// 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
}
f.readDeadline.channel = make(timeoutChan)
f.writeDeadline.channel = make(timeoutChan)
return f, nil
}
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
// If we return the result of makeWin32File directly, it can result in an
// interface-wrapped nil, rather than a nil interface value.
f, err := makeWin32File(h)
if err != nil {
return nil, err
}
return f, nil
}
// closeHandle closes the resources associated with a Win32 handle
func (f *win32File) closeHandle() {
f.wgLock.Lock()
// Atomically set that we are closing, releasing the resources only once.
if !f.closing.swap(true) {
f.wgLock.Unlock()
// cancel all IO and wait for it to complete
cancelIoEx(f.handle, nil)
f.wg.Wait()
// at this point, no new IO can start
syscall.Close(f.handle)
f.handle = 0
} else {
f.wgLock.Unlock()
}
}
// Close closes a win32File.
func (f *win32File) Close() error {
f.closeHandle()
return nil
}
// prepareIo prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIo() (*ioOperation, error) {
f.wgLock.RLock()
if f.closing.isSet() {
f.wgLock.RUnlock()
return nil, ErrFileClosed
}
f.wg.Add(1)
f.wgLock.RUnlock()
c := &ioOperation{}
c.ch = make(chan ioResult)
return c, nil
}
// ioCompletionProcessor processes completed async IOs forever
func ioCompletionProcessor(h syscall.Handle) {
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, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING {
return int(bytes), err
}
if f.closing.isSet() {
cancelIoEx(f.handle, &c.o)
}
var timeout timeoutChan
if d != nil {
d.channelLock.Lock()
timeout = d.channel
d.channelLock.Unlock()
}
var r ioResult
select {
case r = <-c.ch:
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
if f.closing.isSet() {
err = ErrFileClosed
}
} else if err != nil && f.socket {
// err is from Win32. Query the overlapped structure to get the winsock error.
var bytes, flags uint32
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
}
case <-timeout:
cancelIoEx(f.handle, &c.o)
r = <-c.ch
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
err = ErrTimeout
}
}
// runtime.KeepAlive is needed, as c is passed via native
// code to ioCompletionProcessor, c must remain alive
// until the channel read is complete.
runtime.KeepAlive(c)
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
}
defer f.wg.Done()
if f.readDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
runtime.KeepAlive(b)
// 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
}
defer f.wg.Done()
if f.writeDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
runtime.KeepAlive(b)
return n, err
}
func (f *win32File) SetReadDeadline(deadline time.Time) error {
return f.readDeadline.set(deadline)
}
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
return f.writeDeadline.set(deadline)
}
func (f *win32File) Flush() error {
return syscall.FlushFileBuffers(f.handle)
}
func (f *win32File) Fd() uintptr {
return uintptr(f.handle)
}
func (d *deadlineHandler) set(deadline time.Time) error {
d.setLock.Lock()
defer d.setLock.Unlock()
if d.timer != nil {
if !d.timer.Stop() {
<-d.channel
}
d.timer = nil
}
d.timedout.setFalse()
select {
case <-d.channel:
d.channelLock.Lock()
d.channel = make(chan struct{})
d.channelLock.Unlock()
default:
}
if deadline.IsZero() {
return nil
}
timeoutIO := func() {
d.timedout.setTrue()
close(d.channel)
}
now := time.Now()
duration := deadline.Sub(now)
if deadline.After(now) {
// Deadline is in the future, set a timer to wait
d.timer = time.AfterFunc(duration, timeoutIO)
} else {
// Deadline is in the past. Cancel all pending IO now.
timeoutIO()
}
return nil
}

61
vendor/github.com/Microsoft/go-winio/fileinfo.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
// +build windows
package winio
import (
"os"
"runtime"
"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 uint32
pad uint32 // 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}
}
runtime.KeepAlive(f)
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}
}
runtime.KeepAlive(f)
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}
}
runtime.KeepAlive(f)
return fileID, nil
}

9
vendor/github.com/Microsoft/go-winio/go.mod generated vendored Normal file
View File

@ -0,0 +1,9 @@
module github.com/Microsoft/go-winio
go 1.12
require (
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.4.1
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b
)

16
vendor/github.com/Microsoft/go-winio/go.sum generated vendored Normal file
View File

@ -0,0 +1,16 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

305
vendor/github.com/Microsoft/go-winio/hvsock.go generated vendored Normal file
View File

@ -0,0 +1,305 @@
package winio
import (
"fmt"
"io"
"net"
"os"
"syscall"
"time"
"unsafe"
"github.com/Microsoft/go-winio/pkg/guid"
)
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
const (
afHvSock = 34 // AF_HYPERV
socketError = ^uintptr(0)
)
// An HvsockAddr is an address for a AF_HYPERV socket.
type HvsockAddr struct {
VMID guid.GUID
ServiceID guid.GUID
}
type rawHvsockAddr struct {
Family uint16
_ uint16
VMID guid.GUID
ServiceID guid.GUID
}
// Network returns the address's network name, "hvsock".
func (addr *HvsockAddr) Network() string {
return "hvsock"
}
func (addr *HvsockAddr) String() string {
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
}
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
func VsockServiceID(port uint32) guid.GUID {
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
g.Data1 = port
return g
}
func (addr *HvsockAddr) raw() rawHvsockAddr {
return rawHvsockAddr{
Family: afHvSock,
VMID: addr.VMID,
ServiceID: addr.ServiceID,
}
}
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
addr.VMID = raw.VMID
addr.ServiceID = raw.ServiceID
}
// HvsockListener is a socket listener for the AF_HYPERV address family.
type HvsockListener struct {
sock *win32File
addr HvsockAddr
}
// HvsockConn is a connected socket of the AF_HYPERV address family.
type HvsockConn struct {
sock *win32File
local, remote HvsockAddr
}
func newHvSocket() (*win32File, error) {
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
if err != nil {
return nil, os.NewSyscallError("socket", err)
}
f, err := makeWin32File(fd)
if err != nil {
syscall.Close(fd)
return nil, err
}
f.socket = true
return f, nil
}
// ListenHvsock listens for connections on the specified hvsock address.
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
l := &HvsockListener{addr: *addr}
sock, err := newHvSocket()
if err != nil {
return nil, l.opErr("listen", err)
}
sa := addr.raw()
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
if err != nil {
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
}
err = syscall.Listen(sock.handle, 16)
if err != nil {
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
}
return &HvsockListener{sock: sock, addr: *addr}, nil
}
func (l *HvsockListener) opErr(op string, err error) error {
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
}
// Addr returns the listener's network address.
func (l *HvsockListener) Addr() net.Addr {
return &l.addr
}
// Accept waits for the next connection and returns it.
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
sock, err := newHvSocket()
if err != nil {
return nil, l.opErr("accept", err)
}
defer func() {
if sock != nil {
sock.Close()
}
}()
c, err := l.sock.prepareIo()
if err != nil {
return nil, l.opErr("accept", err)
}
defer l.sock.wg.Done()
// AcceptEx, per documentation, requires an extra 16 bytes per address.
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
var addrbuf [addrlen * 2]byte
var bytes uint32
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
_, err = l.sock.asyncIo(c, nil, bytes, err)
if err != nil {
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
}
conn := &HvsockConn{
sock: sock,
}
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
sock = nil
return conn, nil
}
// Close closes the listener, causing any pending Accept calls to fail.
func (l *HvsockListener) Close() error {
return l.sock.Close()
}
/* Need to finish ConnectEx handling
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
sock, err := newHvSocket()
if err != nil {
return nil, err
}
defer func() {
if sock != nil {
sock.Close()
}
}()
c, err := sock.prepareIo()
if err != nil {
return nil, err
}
defer sock.wg.Done()
var bytes uint32
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
_, err = sock.asyncIo(ctx, c, nil, bytes, err)
if err != nil {
return nil, err
}
conn := &HvsockConn{
sock: sock,
remote: *addr,
}
sock = nil
return conn, nil
}
*/
func (conn *HvsockConn) opErr(op string, err error) error {
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
}
func (conn *HvsockConn) Read(b []byte) (int, error) {
c, err := conn.sock.prepareIo()
if err != nil {
return 0, conn.opErr("read", err)
}
defer conn.sock.wg.Done()
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
var flags, bytes uint32
err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err)
if err != nil {
if _, ok := err.(syscall.Errno); ok {
err = os.NewSyscallError("wsarecv", err)
}
return 0, conn.opErr("read", err)
} else if n == 0 {
err = io.EOF
}
return n, err
}
func (conn *HvsockConn) Write(b []byte) (int, error) {
t := 0
for len(b) != 0 {
n, err := conn.write(b)
if err != nil {
return t + n, err
}
t += n
b = b[n:]
}
return t, nil
}
func (conn *HvsockConn) write(b []byte) (int, error) {
c, err := conn.sock.prepareIo()
if err != nil {
return 0, conn.opErr("write", err)
}
defer conn.sock.wg.Done()
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
var bytes uint32
err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err)
if err != nil {
if _, ok := err.(syscall.Errno); ok {
err = os.NewSyscallError("wsasend", err)
}
return 0, conn.opErr("write", err)
}
return n, err
}
// Close closes the socket connection, failing any pending read or write calls.
func (conn *HvsockConn) Close() error {
return conn.sock.Close()
}
func (conn *HvsockConn) shutdown(how int) error {
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
if err != nil {
return os.NewSyscallError("shutdown", err)
}
return nil
}
// CloseRead shuts down the read end of the socket.
func (conn *HvsockConn) CloseRead() error {
err := conn.shutdown(syscall.SHUT_RD)
if err != nil {
return conn.opErr("close", err)
}
return nil
}
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
// no more data will be written.
func (conn *HvsockConn) CloseWrite() error {
err := conn.shutdown(syscall.SHUT_WR)
if err != nil {
return conn.opErr("close", err)
}
return nil
}
// LocalAddr returns the local address of the connection.
func (conn *HvsockConn) LocalAddr() net.Addr {
return &conn.local
}
// RemoteAddr returns the remote address of the connection.
func (conn *HvsockConn) RemoteAddr() net.Addr {
return &conn.remote
}
// SetDeadline implements the net.Conn SetDeadline method.
func (conn *HvsockConn) SetDeadline(t time.Time) error {
conn.SetReadDeadline(t)
conn.SetWriteDeadline(t)
return nil
}
// SetReadDeadline implements the net.Conn SetReadDeadline method.
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
return conn.sock.SetReadDeadline(t)
}
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
return conn.sock.SetWriteDeadline(t)
}

510
vendor/github.com/Microsoft/go-winio/pipe.go generated vendored Normal file
View File

@ -0,0 +1,510 @@
// +build windows
package winio
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"runtime"
"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 *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
//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
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile
//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl
type ioStatusBlock struct {
Status, Information uintptr
}
type objectAttributes struct {
Length uintptr
RootDirectory uintptr
ObjectName *unicodeString
Attributes uintptr
SecurityDescriptor *securityDescriptor
SecurityQoS uintptr
}
type unicodeString struct {
Length uint16
MaximumLength uint16
Buffer uintptr
}
type securityDescriptor struct {
Revision byte
Sbz1 byte
Control uint16
Owner uintptr
Group uintptr
Sacl uintptr
Dacl uintptr
}
type ntstatus int32
func (status ntstatus) Err() error {
if status >= 0 {
return nil
}
return rtlNtStatusToDosError(status)
}
const (
cERROR_PIPE_BUSY = syscall.Errno(231)
cERROR_NO_DATA = syscall.Errno(232)
cERROR_PIPE_CONNECTED = syscall.Errno(535)
cERROR_SEM_TIMEOUT = syscall.Errno(121)
cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0
cPIPE_TYPE_MESSAGE = 4
cPIPE_READMODE_MESSAGE = 2
cFILE_OPEN = 1
cFILE_CREATE = 2
cFILE_PIPE_MESSAGE_TYPE = 1
cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2
cSE_DACL_PRESENT = 4
)
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
} else if err == syscall.ERROR_MORE_DATA {
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
// and the message still has more bytes. Treat this as a success, since
// this package presents all named pipes as byte streams.
err = nil
}
return n, err
}
func (s pipeAddress) Network() string {
return "pipe"
}
func (s pipeAddress) String() string {
return string(s)
}
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
for {
select {
case <-ctx.Done():
return syscall.Handle(0), ctx.Err()
default:
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 == nil {
return h, nil
}
if err != cERROR_PIPE_BUSY {
return h, &os.PathError{Err: err, Op: "open", Path: *path}
}
// Wait 10 msec and try again. This is a rather simplistic
// view, as we always try each 10 milliseconds.
time.Sleep(time.Millisecond * 10)
}
}
}
// DialPipe connects to a named pipe by path, timing out if the connection
// takes longer than the specified duration. If timeout is nil, then we use
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
var absTimeout time.Time
if timeout != nil {
absTimeout = time.Now().Add(*timeout)
} else {
absTimeout = time.Now().Add(time.Second * 2)
}
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
conn, err := DialPipeContext(ctx, path)
if err == context.DeadlineExceeded {
return nil, ErrTimeout
}
return conn, err
}
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
// cancellation or timeout.
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
var err error
var h syscall.Handle
h, err = tryDialPipe(ctx, &path)
if err != nil {
return nil, err
}
var flags uint32
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
if err != nil {
return nil, err
}
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
config PipeConfig
acceptCh chan (chan acceptResponse)
closeCh chan int
doneCh chan int
}
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
path16, err := syscall.UTF16FromString(path)
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
var oa objectAttributes
oa.Length = unsafe.Sizeof(oa)
var ntPath unicodeString
if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
defer localFree(ntPath.Buffer)
oa.ObjectName = &ntPath
// The security descriptor is only needed for the first pipe.
if first {
if sd != nil {
len := uint32(len(sd))
sdb := localAlloc(0, len)
defer localFree(sdb)
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
} else {
// Construct the default named pipe security descriptor.
var dacl uintptr
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
return 0, fmt.Errorf("getting default named pipe ACL: %s", err)
}
defer localFree(dacl)
sdb := &securityDescriptor{
Revision: 1,
Control: cSE_DACL_PRESENT,
Dacl: dacl,
}
oa.SecurityDescriptor = sdb
}
}
typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS)
if c.MessageMode {
typ |= cFILE_PIPE_MESSAGE_TYPE
}
disposition := uint32(cFILE_OPEN)
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
if first {
disposition = cFILE_CREATE
// By not asking for read or write access, the named pipe file system
// will put this pipe into an initially disconnected state, blocking
// client connections until the next call with first == false.
access = syscall.SYNCHRONIZE
}
timeout := int64(-50 * 10000) // 50ms
var (
h syscall.Handle
iosb ioStatusBlock
)
err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err()
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
runtime.KeepAlive(ntPath)
return h, nil
}
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
h, err := makeServerPipeHandle(l.path, nil, &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) makeConnectedServerPipe() (*win32File, error) {
p, err := l.makeServerPipe()
if err != nil {
return nil, err
}
// Wait for the client to connect.
ch := make(chan error)
go func(p *win32File) {
ch <- connectPipe(p)
}(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
}
}
return p, err
}
func (l *win32PipeListener) listenerRoutine() {
closed := false
for !closed {
select {
case <-l.closeCh:
closed = true
case responseCh := <-l.acceptCh:
var (
p *win32File
err error
)
for {
p, err = l.makeConnectedServerPipe()
// If the connection was immediately closed by the client, try
// again.
if err != cERROR_NO_DATA {
break
}
}
responseCh <- acceptResponse{p, err}
closed = err == ErrPipeListenerClosed
}
}
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
}
l := &win32PipeListener{
firstHandle: h,
path: path,
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
}
defer p.wg.Done()
err = connectNamedPipe(p.handle, &c.o)
_, err = p.asyncIo(c, nil, 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)
}

187
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go generated vendored Normal file
View File

@ -0,0 +1,187 @@
// Package guid provides a GUID type. The backing structure for a GUID is
// identical to that used by the golang.org/x/sys/windows GUID type.
// There are two main binary encodings used for a GUID, the big-endian encoding,
// and the Windows (mixed-endian) encoding. See here for details:
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
package guid
import (
"crypto/rand"
"encoding"
"encoding/binary"
"fmt"
"strconv"
"golang.org/x/sys/windows"
)
// Variant specifies which GUID variant (or "type") of the GUID. It determines
// how the entirety of the rest of the GUID is interpreted.
type Variant uint8
// The variants specified by RFC 4122.
const (
// VariantUnknown specifies a GUID variant which does not conform to one of
// the variant encodings specified in RFC 4122.
VariantUnknown Variant = iota
VariantNCS
VariantRFC4122
VariantMicrosoft
VariantFuture
)
// Version specifies how the bits in the GUID were generated. For instance, a
// version 4 GUID is randomly generated, and a version 5 is generated from the
// hash of an input string.
type Version uint8
var _ = (encoding.TextMarshaler)(GUID{})
var _ = (encoding.TextUnmarshaler)(&GUID{})
// GUID represents a GUID/UUID. It has the same structure as
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
// that type. It is defined as its own type so that stringification and
// marshaling can be supported. The representation matches that used by native
// Windows code.
type GUID windows.GUID
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
func NewV4() (GUID, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return GUID{}, err
}
b[6] = (b[6] & 0x0f) | 0x40 // Version 4 (randomly generated)
b[8] = (b[8] & 0x3f) | 0x80 // RFC4122 variant
return FromArray(b), nil
}
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
var g GUID
g.Data1 = order.Uint32(b[0:4])
g.Data2 = order.Uint16(b[4:6])
g.Data3 = order.Uint16(b[6:8])
copy(g.Data4[:], b[8:16])
return g
}
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
b := [16]byte{}
order.PutUint32(b[0:4], g.Data1)
order.PutUint16(b[4:6], g.Data2)
order.PutUint16(b[6:8], g.Data3)
copy(b[8:16], g.Data4[:])
return b
}
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
func FromArray(b [16]byte) GUID {
return fromArray(b, binary.BigEndian)
}
// ToArray returns an array of 16 bytes representing the GUID in big-endian
// encoding.
func (g GUID) ToArray() [16]byte {
return g.toArray(binary.BigEndian)
}
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
func FromWindowsArray(b [16]byte) GUID {
return fromArray(b, binary.LittleEndian)
}
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
// encoding.
func (g GUID) ToWindowsArray() [16]byte {
return g.toArray(binary.LittleEndian)
}
func (g GUID) String() string {
return fmt.Sprintf(
"%08x-%04x-%04x-%04x-%012x",
g.Data1,
g.Data2,
g.Data3,
g.Data4[:2],
g.Data4[2:])
}
// FromString parses a string containing a GUID and returns the GUID. The only
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
// format.
func FromString(s string) (GUID, error) {
if len(s) != 36 {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
var g GUID
data1, err := strconv.ParseUint(s[0:8], 16, 32)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data1 = uint32(data1)
data2, err := strconv.ParseUint(s[9:13], 16, 16)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data2 = uint16(data2)
data3, err := strconv.ParseUint(s[14:18], 16, 16)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data3 = uint16(data3)
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data4[i] = uint8(v)
}
return g, nil
}
// Variant returns the GUID variant, as defined in RFC 4122.
func (g GUID) Variant() Variant {
b := g.Data4[0]
if b&0x80 == 0 {
return VariantNCS
} else if b&0xc0 == 0x80 {
return VariantRFC4122
} else if b&0xe0 == 0xc0 {
return VariantMicrosoft
} else if b&0xe0 == 0xe0 {
return VariantFuture
}
return VariantUnknown
}
// Version returns the GUID version, as defined in RFC 4122.
func (g GUID) Version() Version {
return Version((g.Data3 & 0xF000) >> 12)
}
// MarshalText returns the textual representation of the GUID.
func (g GUID) MarshalText() ([]byte, error) {
return []byte(g.String()), nil
}
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
// into this GUID.
func (g *GUID) UnmarshalText(text []byte) error {
g2, err := FromString(string(text))
if err != nil {
return err
}
*g = g2
return nil
}

202
vendor/github.com/Microsoft/go-winio/privilege.go generated vendored Normal file
View 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()
}

128
vendor/github.com/Microsoft/go-winio/reparse.go generated vendored Normal file
View 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
vendor/github.com/Microsoft/go-winio/sd.go generated vendored Normal file
View 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
}

3
vendor/github.com/Microsoft/go-winio/syscall.go generated vendored Normal file
View 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 hvsock.go

View File

@ -0,0 +1,562 @@
// Code generated by 'go generate'; DO NOT EDIT.
package winio
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
modntdll = windows.NewLazySystemDLL("ntdll.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
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")
procbind = modws2_32.NewProc("bind")
)
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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
var _p0 uint32
if wait {
_p0 = 1
} else {
_p0 = 0
}
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
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 = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.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 *syscall.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 = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createFile(name string, access uint32, mode uint32, sa *syscall.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 *syscall.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 = errnoErr(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 = errnoErr(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 = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
ptr = uintptr(r0)
return
}
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
status = ntstatus(r0)
return
}
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
if r0 != 0 {
winerr = syscall.Errno(r0)
}
return
}
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
status = ntstatus(r0)
return
}
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
status = ntstatus(r0)
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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}

21
vendor/github.com/Microsoft/hcsshim/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
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.

View File

@ -0,0 +1,57 @@
package osversion
import (
"fmt"
"golang.org/x/sys/windows"
)
// OSVersion is a wrapper for Windows version information
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
type OSVersion struct {
Version uint32
MajorVersion uint8
MinorVersion uint8
Build uint16
}
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
type osVersionInfoEx struct {
OSVersionInfoSize uint32
MajorVersion uint32
MinorVersion uint32
BuildNumber uint32
PlatformID uint32
CSDVersion [128]uint16
ServicePackMajor uint16
ServicePackMinor uint16
SuiteMask uint16
ProductType byte
Reserve byte
}
// Get gets the operating system version on Windows.
// The calling application must be manifested to get the correct version information.
func Get() OSVersion {
var err error
osv := OSVersion{}
osv.Version, err = windows.GetVersion()
if err != nil {
// GetVersion never fails.
panic(err)
}
osv.MajorVersion = uint8(osv.Version & 0xFF)
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
osv.Build = uint16(osv.Version >> 16)
return osv
}
// Build gets the build-number on Windows
// The calling application must be manifested to get the correct version information.
func Build() uint16 {
return Get().Build
}
func (osv OSVersion) ToString() string {
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
}

View File

@ -0,0 +1,23 @@
package osversion
const (
// RS1 (version 1607, codename "Redstone 1") corresponds to Windows Server
// 2016 (ltsc2016) and Windows 10 (Anniversary Update).
RS1 = 14393
// RS2 (version 1703, codename "Redstone 2") was a client-only update, and
// corresponds to Windows 10 (Creators Update).
RS2 = 15063
// RS3 (version 1709, codename "Redstone 3") corresponds to Windows Server
// 1709 (Semi-Annual Channel (SAC)), and Windows 10 (Fall Creators Update).
RS3 = 16299
// RS4 (version 1803, codename "Redstone 4") corresponds to Windows Server
// 1803 (Semi-Annual Channel (SAC)), and Windows 10 (April 2018 Update).
RS4 = 17134
// RS5 (version 1809, codename "Redstone 5") corresponds to Windows Server
// 2019 (ltsc2019), and Windows 10 (October 2018 Update).
RS5 = 17763
)

191
vendor/github.com/containerd/continuity/LICENSE generated vendored Normal file
View File

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

172
vendor/github.com/containerd/continuity/fs/copy.go generated vendored Normal file
View File

@ -0,0 +1,172 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/pkg/errors"
)
var bufferPool = &sync.Pool{
New: func() interface{} {
buffer := make([]byte, 32*1024)
return &buffer
},
}
// XAttrErrorHandlers transform a non-nil xattr error.
// Return nil to ignore an error.
// xattrKey can be empty for listxattr operation.
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
type copyDirOpts struct {
xeh XAttrErrorHandler
}
type CopyDirOpt func(*copyDirOpts) error
// WithXAttrErrorHandler allows specifying XAttrErrorHandler
// If nil XAttrErrorHandler is specified (default), CopyDir stops
// on a non-nil xattr error.
func WithXAttrErrorHandler(xeh XAttrErrorHandler) CopyDirOpt {
return func(o *copyDirOpts) error {
o.xeh = xeh
return nil
}
}
// WithAllowXAttrErrors allows ignoring xattr errors.
func WithAllowXAttrErrors() CopyDirOpt {
xeh := func(dst, src, xattrKey string, err error) error {
return nil
}
return WithXAttrErrorHandler(xeh)
}
// CopyDir copies the directory from src to dst.
// Most efficient copy of files is attempted.
func CopyDir(dst, src string, opts ...CopyDirOpt) error {
var o copyDirOpts
for _, opt := range opts {
if err := opt(&o); err != nil {
return err
}
}
inodes := map[uint64]string{}
return copyDirectory(dst, src, inodes, &o)
}
func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) error {
stat, err := os.Stat(src)
if err != nil {
return errors.Wrapf(err, "failed to stat %s", src)
}
if !stat.IsDir() {
return errors.Errorf("source is not directory")
}
if st, err := os.Stat(dst); err != nil {
if err := os.Mkdir(dst, stat.Mode()); err != nil {
return errors.Wrapf(err, "failed to mkdir %s", dst)
}
} else if !st.IsDir() {
return errors.Errorf("cannot copy to non-directory: %s", dst)
} else {
if err := os.Chmod(dst, stat.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod on %s", dst)
}
}
fis, err := ioutil.ReadDir(src)
if err != nil {
return errors.Wrapf(err, "failed to read %s", src)
}
if err := copyFileInfo(stat, dst); err != nil {
return errors.Wrapf(err, "failed to copy file info for %s", dst)
}
for _, fi := range fis {
source := filepath.Join(src, fi.Name())
target := filepath.Join(dst, fi.Name())
switch {
case fi.IsDir():
if err := copyDirectory(target, source, inodes, o); err != nil {
return err
}
continue
case (fi.Mode() & os.ModeType) == 0:
link, err := getLinkSource(target, fi, inodes)
if err != nil {
return errors.Wrap(err, "failed to get hardlink")
}
if link != "" {
if err := os.Link(link, target); err != nil {
return errors.Wrap(err, "failed to create hard link")
}
} else if err := CopyFile(target, source); err != nil {
return errors.Wrap(err, "failed to copy files")
}
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
link, err := os.Readlink(source)
if err != nil {
return errors.Wrapf(err, "failed to read link: %s", source)
}
if err := os.Symlink(link, target); err != nil {
return errors.Wrapf(err, "failed to create symlink: %s", target)
}
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
if err := copyDevice(target, fi); err != nil {
return errors.Wrapf(err, "failed to create device")
}
default:
// TODO: Support pipes and sockets
return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
}
if err := copyFileInfo(fi, target); err != nil {
return errors.Wrap(err, "failed to copy file info")
}
if err := copyXAttrs(target, source, o.xeh); err != nil {
return errors.Wrap(err, "failed to copy xattrs")
}
}
return nil
}
// CopyFile copies the source file to the target.
// The most efficient means of copying is used for the platform.
func CopyFile(target, source string) error {
src, err := os.Open(source)
if err != nil {
return errors.Wrapf(err, "failed to open source %s", source)
}
defer src.Close()
tgt, err := os.Create(target)
if err != nil {
return errors.Wrapf(err, "failed to open target %s", target)
}
defer tgt.Close()
return copyFileContent(tgt, src)
}

View File

@ -0,0 +1,144 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"io"
"os"
"syscall"
"github.com/containerd/continuity/sysx"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func copyFileInfo(fi os.FileInfo, name string) error {
st := fi.Sys().(*syscall.Stat_t)
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
if os.IsPermission(err) {
// Normally if uid/gid are the same this would be a no-op, but some
// filesystems may still return EPERM... for instance NFS does this.
// In such a case, this is not an error.
if dstStat, err2 := os.Lstat(name); err2 == nil {
st2 := dstStat.Sys().(*syscall.Stat_t)
if st.Uid == st2.Uid && st.Gid == st2.Gid {
err = nil
}
}
}
if err != nil {
return errors.Wrapf(err, "failed to chown %s", name)
}
}
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
}
}
timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(StatMtime(st))}
if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return errors.Wrapf(err, "failed to utime %s", name)
}
return nil
}
const maxSSizeT = int64(^uint(0) >> 1)
func copyFileContent(dst, src *os.File) error {
st, err := src.Stat()
if err != nil {
return errors.Wrap(err, "unable to stat source")
}
size := st.Size()
first := true
srcFd := int(src.Fd())
dstFd := int(dst.Fd())
for size > 0 {
// Ensure that we are never trying to copy more than SSIZE_MAX at a
// time and at the same time avoids overflows when the file is larger
// than 4GB on 32-bit systems.
var copySize int
if size > maxSSizeT {
copySize = int(maxSSizeT)
} else {
copySize = int(size)
}
n, err := unix.CopyFileRange(srcFd, nil, dstFd, nil, copySize, 0)
if err != nil {
if (err != unix.ENOSYS && err != unix.EXDEV) || !first {
return errors.Wrap(err, "copy file range failed")
}
buf := bufferPool.Get().(*[]byte)
_, err = io.CopyBuffer(dst, src, *buf)
bufferPool.Put(buf)
return errors.Wrap(err, "userspace copy failed")
}
first = false
size -= int64(n)
}
return nil
}
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
if xeh != nil {
e = xeh(dst, src, "", e)
}
return e
}
for _, xattr := range xattrKeys {
data, err := sysx.LGetxattr(src, xattr)
if err != nil {
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
}
return nil
}
func copyDevice(dst string, fi os.FileInfo) error {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return errors.New("unsupported stat type")
}
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
}

112
vendor/github.com/containerd/continuity/fs/copy_unix.go generated vendored Normal file
View File

@ -0,0 +1,112 @@
// +build solaris darwin freebsd
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"io"
"os"
"syscall"
"github.com/containerd/continuity/sysx"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func copyFileInfo(fi os.FileInfo, name string) error {
st := fi.Sys().(*syscall.Stat_t)
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
if os.IsPermission(err) {
// Normally if uid/gid are the same this would be a no-op, but some
// filesystems may still return EPERM... for instance NFS does this.
// In such a case, this is not an error.
if dstStat, err2 := os.Lstat(name); err2 == nil {
st2 := dstStat.Sys().(*syscall.Stat_t)
if st.Uid == st2.Uid && st.Gid == st2.Gid {
err = nil
}
}
}
if err != nil {
return errors.Wrapf(err, "failed to chown %s", name)
}
}
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
}
}
timespec := []syscall.Timespec{StatAtime(st), StatMtime(st)}
if err := syscall.UtimesNano(name, timespec); err != nil {
return errors.Wrapf(err, "failed to utime %s", name)
}
return nil
}
func copyFileContent(dst, src *os.File) error {
buf := bufferPool.Get().(*[]byte)
_, err := io.CopyBuffer(dst, src, *buf)
bufferPool.Put(buf)
return err
}
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
if xeh != nil {
e = xeh(dst, src, "", e)
}
return e
}
for _, xattr := range xattrKeys {
data, err := sysx.LGetxattr(src, xattr)
if err != nil {
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
}
return nil
}
func copyDevice(dst string, fi os.FileInfo) error {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return errors.New("unsupported stat type")
}
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
}

View File

@ -0,0 +1,49 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"io"
"os"
"github.com/pkg/errors"
)
func copyFileInfo(fi os.FileInfo, name string) error {
if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
}
// TODO: copy windows specific metadata
return nil
}
func copyFileContent(dst, src *os.File) error {
buf := bufferPool.Get().(*[]byte)
_, err := io.CopyBuffer(dst, src, *buf)
bufferPool.Put(buf)
return err
}
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
return nil
}
func copyDevice(dst string, fi os.FileInfo) error {
return errors.New("device copy not supported")
}

326
vendor/github.com/containerd/continuity/fs/diff.go generated vendored Normal file
View File

@ -0,0 +1,326 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"context"
"os"
"path/filepath"
"strings"
"golang.org/x/sync/errgroup"
"github.com/sirupsen/logrus"
)
// ChangeKind is the type of modification that
// a change is making.
type ChangeKind int
const (
// ChangeKindUnmodified represents an unmodified
// file
ChangeKindUnmodified = iota
// ChangeKindAdd represents an addition of
// a file
ChangeKindAdd
// ChangeKindModify represents a change to
// an existing file
ChangeKindModify
// ChangeKindDelete represents a delete of
// a file
ChangeKindDelete
)
func (k ChangeKind) String() string {
switch k {
case ChangeKindUnmodified:
return "unmodified"
case ChangeKindAdd:
return "add"
case ChangeKindModify:
return "modify"
case ChangeKindDelete:
return "delete"
default:
return ""
}
}
// Change represents single change between a diff and its parent.
type Change struct {
Kind ChangeKind
Path string
}
// ChangeFunc is the type of function called for each change
// computed during a directory changes calculation.
type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error
// Changes computes changes between two directories calling the
// given change function for each computed change. The first
// directory is intended to the base directory and second
// directory the changed directory.
//
// The change callback is called by the order of path names and
// should be appliable in that order.
// Due to this apply ordering, the following is true
// - Removed directory trees only create a single change for the root
// directory removed. Remaining changes are implied.
// - A directory which is modified to become a file will not have
// delete entries for sub-path items, their removal is implied
// by the removal of the parent directory.
//
// Opaque directories will not be treated specially and each file
// removed from the base directory will show up as a removal.
//
// File content comparisons will be done on files which have timestamps
// which may have been truncated. If either of the files being compared
// has a zero value nanosecond value, each byte will be compared for
// differences. If 2 files have the same seconds value but different
// nanosecond values where one of those values is zero, the files will
// be considered unchanged if the content is the same. This behavior
// is to account for timestamp truncation during archiving.
func Changes(ctx context.Context, a, b string, changeFn ChangeFunc) error {
if a == "" {
logrus.Debugf("Using single walk diff for %s", b)
return addDirChanges(ctx, changeFn, b)
} else if diffOptions := detectDirDiff(b, a); diffOptions != nil {
logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, a)
return diffDirChanges(ctx, changeFn, a, diffOptions)
}
logrus.Debugf("Using double walk diff for %s from %s", b, a)
return doubleWalkDiff(ctx, changeFn, a, b)
}
func addDirChanges(ctx context.Context, changeFn ChangeFunc, root string) error {
return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
path, err = filepath.Rel(root, path)
if err != nil {
return err
}
path = filepath.Join(string(os.PathSeparator), path)
// Skip root
if path == string(os.PathSeparator) {
return nil
}
return changeFn(ChangeKindAdd, path, f, nil)
})
}
// diffDirOptions is used when the diff can be directly calculated from
// a diff directory to its base, without walking both trees.
type diffDirOptions struct {
diffDir string
skipChange func(string) (bool, error)
deleteChange func(string, string, os.FileInfo) (string, error)
}
// diffDirChanges walks the diff directory and compares changes against the base.
func diffDirChanges(ctx context.Context, changeFn ChangeFunc, base string, o *diffDirOptions) error {
changedDirs := make(map[string]struct{})
return filepath.Walk(o.diffDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
path, err = filepath.Rel(o.diffDir, path)
if err != nil {
return err
}
path = filepath.Join(string(os.PathSeparator), path)
// Skip root
if path == string(os.PathSeparator) {
return nil
}
// TODO: handle opaqueness, start new double walker at this
// location to get deletes, and skip tree in single walker
if o.skipChange != nil {
if skip, err := o.skipChange(path); skip {
return err
}
}
var kind ChangeKind
deletedFile, err := o.deleteChange(o.diffDir, path, f)
if err != nil {
return err
}
// Find out what kind of modification happened
if deletedFile != "" {
path = deletedFile
kind = ChangeKindDelete
f = nil
} else {
// Otherwise, the file was added
kind = ChangeKindAdd
// ...Unless it already existed in a base, in which case, it's a modification
stat, err := os.Stat(filepath.Join(base, path))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
// The file existed in the base, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
if stat.IsDir() && f.IsDir() {
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
// Both directories are the same, don't record the change
return nil
}
}
kind = ChangeKindModify
}
}
// If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files.
// This block is here to ensure the change is recorded even if the
// modify time, mode and size of the parent directory in the rw and ro layers are all equal.
// Check https://github.com/docker/docker/pull/13590 for details.
if f.IsDir() {
changedDirs[path] = struct{}{}
}
if kind == ChangeKindAdd || kind == ChangeKindDelete {
parent := filepath.Dir(path)
if _, ok := changedDirs[parent]; !ok && parent != "/" {
pi, err := os.Stat(filepath.Join(o.diffDir, parent))
if err := changeFn(ChangeKindModify, parent, pi, err); err != nil {
return err
}
changedDirs[parent] = struct{}{}
}
}
return changeFn(kind, path, f, nil)
})
}
// doubleWalkDiff walks both directories to create a diff
func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err error) {
g, ctx := errgroup.WithContext(ctx)
var (
c1 = make(chan *currentPath)
c2 = make(chan *currentPath)
f1, f2 *currentPath
rmdir string
)
g.Go(func() error {
defer close(c1)
return pathWalk(ctx, a, c1)
})
g.Go(func() error {
defer close(c2)
return pathWalk(ctx, b, c2)
})
g.Go(func() error {
for c1 != nil || c2 != nil {
if f1 == nil && c1 != nil {
f1, err = nextPath(ctx, c1)
if err != nil {
return err
}
if f1 == nil {
c1 = nil
}
}
if f2 == nil && c2 != nil {
f2, err = nextPath(ctx, c2)
if err != nil {
return err
}
if f2 == nil {
c2 = nil
}
}
if f1 == nil && f2 == nil {
continue
}
var f os.FileInfo
k, p := pathChange(f1, f2)
switch k {
case ChangeKindAdd:
if rmdir != "" {
rmdir = ""
}
f = f2.f
f2 = nil
case ChangeKindDelete:
// Check if this file is already removed by being
// under of a removed directory
if rmdir != "" && strings.HasPrefix(f1.path, rmdir) {
f1 = nil
continue
} else if f1.f.IsDir() {
rmdir = f1.path + string(os.PathSeparator)
} else if rmdir != "" {
rmdir = ""
}
f1 = nil
case ChangeKindModify:
same, err := sameFile(f1, f2)
if err != nil {
return err
}
if f1.f.IsDir() && !f2.f.IsDir() {
rmdir = f1.path + string(os.PathSeparator)
} else if rmdir != "" {
rmdir = ""
}
f = f2.f
f1 = nil
f2 = nil
if same {
if !isLinked(f) {
continue
}
k = ChangeKindUnmodified
}
}
if err := changeFn(k, p, f, nil); err != nil {
return err
}
}
return nil
})
return g.Wait()
}

View File

@ -0,0 +1,74 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"bytes"
"os"
"syscall"
"github.com/containerd/continuity/sysx"
"github.com/pkg/errors"
)
// detectDirDiff returns diff dir options if a directory could
// be found in the mount info for upper which is the direct
// diff with the provided lower directory
func detectDirDiff(upper, lower string) *diffDirOptions {
// TODO: get mount options for upper
// TODO: detect AUFS
// TODO: detect overlay
return nil
}
// compareSysStat returns whether the stats are equivalent,
// whether the files are considered the same file, and
// an error
func compareSysStat(s1, s2 interface{}) (bool, error) {
ls1, ok := s1.(*syscall.Stat_t)
if !ok {
return false, nil
}
ls2, ok := s2.(*syscall.Stat_t)
if !ok {
return false, nil
}
return ls1.Mode == ls2.Mode && ls1.Uid == ls2.Uid && ls1.Gid == ls2.Gid && ls1.Rdev == ls2.Rdev, nil
}
func compareCapabilities(p1, p2 string) (bool, error) {
c1, err := sysx.LGetxattr(p1, "security.capability")
if err != nil && err != sysx.ENODATA {
return false, errors.Wrapf(err, "failed to get xattr for %s", p1)
}
c2, err := sysx.LGetxattr(p2, "security.capability")
if err != nil && err != sysx.ENODATA {
return false, errors.Wrapf(err, "failed to get xattr for %s", p2)
}
return bytes.Equal(c1, c2), nil
}
func isLinked(f os.FileInfo) bool {
s, ok := f.Sys().(*syscall.Stat_t)
if !ok {
return false
}
return !f.IsDir() && s.Nlink > 1
}

View File

@ -0,0 +1,48 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"os"
"golang.org/x/sys/windows"
)
func detectDirDiff(upper, lower string) *diffDirOptions {
return nil
}
func compareSysStat(s1, s2 interface{}) (bool, error) {
f1, ok := s1.(windows.Win32FileAttributeData)
if !ok {
return false, nil
}
f2, ok := s2.(windows.Win32FileAttributeData)
if !ok {
return false, nil
}
return f1.FileAttributes == f2.FileAttributes, nil
}
func compareCapabilities(p1, p2 string) (bool, error) {
// TODO: Use windows equivalent
return true, nil
}
func isLinked(os.FileInfo) bool {
return false
}

View File

@ -0,0 +1,103 @@
// +build linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"fmt"
"io/ioutil"
"os"
"syscall"
"unsafe"
)
func locateDummyIfEmpty(path string) (string, error) {
children, err := ioutil.ReadDir(path)
if err != nil {
return "", err
}
if len(children) != 0 {
return "", nil
}
dummyFile, err := ioutil.TempFile(path, "fsutils-dummy")
if err != nil {
return "", err
}
name := dummyFile.Name()
err = dummyFile.Close()
return name, err
}
// SupportsDType returns whether the filesystem mounted on path supports d_type
func SupportsDType(path string) (bool, error) {
// locate dummy so that we have at least one dirent
dummy, err := locateDummyIfEmpty(path)
if err != nil {
return false, err
}
if dummy != "" {
defer os.Remove(dummy)
}
visited := 0
supportsDType := true
fn := func(ent *syscall.Dirent) bool {
visited++
if ent.Type == syscall.DT_UNKNOWN {
supportsDType = false
// stop iteration
return true
}
// continue iteration
return false
}
if err = iterateReadDir(path, fn); err != nil {
return false, err
}
if visited == 0 {
return false, fmt.Errorf("did not hit any dirent during iteration %s", path)
}
return supportsDType, nil
}
func iterateReadDir(path string, fn func(*syscall.Dirent) bool) error {
d, err := os.Open(path)
if err != nil {
return err
}
defer d.Close()
fd := int(d.Fd())
buf := make([]byte, 4096)
for {
nbytes, err := syscall.ReadDirent(fd, buf)
if err != nil {
return err
}
if nbytes == 0 {
break
}
for off := 0; off < nbytes; {
ent := (*syscall.Dirent)(unsafe.Pointer(&buf[off]))
if stop := fn(ent); stop {
return nil
}
off += int(ent.Reclen)
}
}
return nil
}

38
vendor/github.com/containerd/continuity/fs/du.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import "context"
// Usage of disk information
type Usage struct {
Inodes int64
Size int64
}
// DiskUsage counts the number of inodes and disk usage for the resources under
// path.
func DiskUsage(ctx context.Context, roots ...string) (Usage, error) {
return diskUsage(ctx, roots...)
}
// DiffUsage counts the numbers of inodes and disk usage in the
// diff between the 2 directories. The first path is intended
// as the base directory and the second as the changed directory.
func DiffUsage(ctx context.Context, a, b string) (Usage, error) {
return diffUsage(ctx, a, b)
}

Some files were not shown because too many files have changed in this diff Show More