new account binding, with example usage
This commit is contained in:
parent
18d53211d9
commit
e21ca3f08a
|
@ -6,85 +6,115 @@
|
|||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
"Comment": "v1.17.0-67-ge5bef42",
|
||||
"Rev": "e5bef42c62aa7d25aba4880dc02b7624f01e9e19"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/accounts",
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/common",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto/ecies",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto/randentropy",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto/secp256k1",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto/sha3",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/ethdb",
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/event",
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/event/filter",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/internal/debug",
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/logger",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/logger/glog",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/metrics",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/node",
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/p2p",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/p2p/discover",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/p2p/nat",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/rlp",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/rpc",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum/whisper",
|
||||
"Comment": "v1.4.5",
|
||||
"Rev": "a269a713d6486627bfe2a9f130502554879a4308"
|
||||
"Comment": "v1.0.1-894-gca8606b",
|
||||
"Rev": "ca8606be4d90c0ec49581fe22c8ee4d251a31714"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/golang/snappy",
|
||||
|
@ -128,10 +158,19 @@
|
|||
"Comment": "v0.3.5",
|
||||
"Rev": "4f1a71750d95a5a8a46c40a67ffbed8129c2f138"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pborman/uuid",
|
||||
"Comment": "v1.0-11-gc55201b",
|
||||
"Rev": "c55201b036063326c5b1b89ccfe45a184973d073"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rcrowley/go-metrics",
|
||||
"Rev": "eeba7bd0dd01ace6e690fa833b3f22aaec29af43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rjeczalik/notify",
|
||||
"Rev": "5dd6205716539662f8f14ab513552b41eab69d5d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rs/cors",
|
||||
"Rev": "3ca2b550f6a4333b63c845850f760a7d00412cd6"
|
||||
|
@ -140,11 +179,6 @@
|
|||
"ImportPath": "github.com/rs/xhandler",
|
||||
"Rev": "d9d9599b6aaf6a058cb7b1f48291ded2cbd13390"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/status-im/go-ethereum/whisper",
|
||||
"Comment": "v1.0.1-886-gc128e86",
|
||||
"Rev": "c128e8673ae5db9776f4d56880797fe4756e4976"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
||||
|
@ -193,10 +227,18 @@
|
|||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/util",
|
||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
||||
"Rev": "1777f3ba8c1fed80fcaec3317e3aaa4f627764d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ripemd160",
|
||||
"Rev": "1777f3ba8c1fed80fcaec3317e3aaa4f627764d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/scrypt",
|
||||
"Rev": "1777f3ba8c1fed80fcaec3317e3aaa4f627764d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "fb93926129b8ec0056f2f458b1f519654814edf0"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#include <stdio.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
int doNewAccount() {
|
||||
char *account = NewAccount("badpassword", "/home/dwhitena/.ethereum/keystore");
|
||||
printf("%s\n", account);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdio.h>
|
||||
int doNewAccount();
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var (
|
||||
scryptN = 262144
|
||||
scryptP = 1
|
||||
)
|
||||
|
||||
func main() {
|
||||
Example()
|
||||
}
|
||||
|
||||
//export NewAccount
|
||||
func NewAccount(p, k *C.char) *C.char {
|
||||
|
||||
password := C.GoString(p)
|
||||
keydir := C.GoString(k)
|
||||
|
||||
var sync *[]node.Service
|
||||
w := true
|
||||
accman := accounts.NewManager(keydir, scryptN, scryptP, sync)
|
||||
|
||||
account, err := accman.NewAccount(password, w)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
address := fmt.Sprintf("{%x}", account.Address)
|
||||
return C.CString(address)
|
||||
|
||||
}
|
||||
|
||||
func Example() {
|
||||
C.doNewAccount()
|
||||
}
|
12
test.go
12
test.go
|
@ -1,12 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/go-ethereum/whisper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
testmessage := whisper.Message{}
|
||||
fmt.Println(testmessage)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
*.coverprofile
|
|
@ -0,0 +1,32 @@
|
|||
language: go
|
||||
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.2.2
|
||||
- 1.3.3
|
||||
- 1.4
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- master
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
include:
|
||||
- go: 1.6.2
|
||||
os: osx
|
||||
- go: 1.1.2
|
||||
install: go get -v .
|
||||
before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION
|
||||
script:
|
||||
- ./runtests vet
|
||||
- ./runtests test
|
||||
|
||||
before_script:
|
||||
- go get github.com/urfave/gfmxr/...
|
||||
|
||||
script:
|
||||
- ./runtests vet
|
||||
- ./runtests test
|
||||
- ./runtests gfmxr
|
|
@ -0,0 +1,322 @@
|
|||
# Change Log
|
||||
|
||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- `./runtests` test runner with coverage tracking by default
|
||||
- testing on OS X
|
||||
- testing on Windows
|
||||
|
||||
### Changed
|
||||
- Use spaces for alignment in help/usage output instead of tabs, making the
|
||||
output alignment consistent regardless of tab width
|
||||
|
||||
### Fixed
|
||||
- Printing of command aliases in help text
|
||||
- Printing of visible flags for both struct and struct pointer flags
|
||||
|
||||
## [1.17.0] - 2016-05-09
|
||||
### Added
|
||||
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
|
||||
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
|
||||
- Support for hiding commands by setting `Hidden: true` -- this will hide the
|
||||
commands in help output
|
||||
|
||||
### Changed
|
||||
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
|
||||
quoted in help text output.
|
||||
- All flag types now include `(default: {value})` strings following usage when a
|
||||
default value can be (reasonably) detected.
|
||||
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
|
||||
with non-slice flag types
|
||||
- Apps now exit with a code of 3 if an unknown subcommand is specified
|
||||
(previously they printed "No help topic for...", but still exited 0. This
|
||||
makes it easier to script around apps built using `cli` since they can trust
|
||||
that a 0 exit code indicated a successful execution.
|
||||
- cleanups based on [Go Report Card
|
||||
feedback](https://goreportcard.com/report/github.com/urfave/cli)
|
||||
|
||||
## [1.16.0] - 2016-05-02
|
||||
### Added
|
||||
- `Hidden` field on all flag struct types to omit from generated help text
|
||||
|
||||
### Changed
|
||||
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
|
||||
generated help text via the `Hidden` field
|
||||
|
||||
### Fixed
|
||||
- handling of error values in `HandleAction` and `HandleExitCoder`
|
||||
|
||||
## [1.15.0] - 2016-04-30
|
||||
### Added
|
||||
- This file!
|
||||
- Support for placeholders in flag usage strings
|
||||
- `App.Metadata` map for arbitrary data/state management
|
||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
|
||||
parsing.
|
||||
- Support for nested lookup of dot-delimited keys in structures loaded from
|
||||
YAML.
|
||||
|
||||
### Changed
|
||||
- The `App.Action` and `Command.Action` now prefer a return signature of
|
||||
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
|
||||
`error` is returned, there may be two outcomes:
|
||||
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
|
||||
automatically
|
||||
- Else the error is bubbled up and returned from `App.Run`
|
||||
- Specifying an `Action` with the legacy return signature of
|
||||
`func(*cli.Context)` will produce a deprecation message to stderr
|
||||
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
|
||||
from `App.Run`
|
||||
- Specifying an `Action` func that has an invalid (input) signature will
|
||||
produce a non-zero exit from `App.Run`
|
||||
|
||||
### Deprecated
|
||||
- <a name="deprecated-cli-app-runandexitonerror"></a>
|
||||
`cli.App.RunAndExitOnError`, which should now be done by returning an error
|
||||
that fulfills `cli.ExitCoder` to `cli.App.Run`.
|
||||
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
|
||||
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
|
||||
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
|
||||
|
||||
### Fixed
|
||||
- Added missing `*cli.Context.GlobalFloat64` method
|
||||
|
||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Codebeat badge
|
||||
- Support for categorization via `CategorizedHelp` and `Categories` on app.
|
||||
|
||||
### Changed
|
||||
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
|
||||
|
||||
### Fixed
|
||||
- Ensure version is not shown in help text when `HideVersion` set.
|
||||
|
||||
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- YAML file input support.
|
||||
- `NArg` method on context.
|
||||
|
||||
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Custom usage error handling.
|
||||
- Custom text support in `USAGE` section of help output.
|
||||
- Improved help messages for empty strings.
|
||||
- AppVeyor CI configuration.
|
||||
|
||||
### Changed
|
||||
- Removed `panic` from default help printer func.
|
||||
- De-duping and optimizations.
|
||||
|
||||
### Fixed
|
||||
- Correctly handle `Before`/`After` at command level when no subcommands.
|
||||
- Case of literal `-` argument causing flag reordering.
|
||||
- Environment variable hints on Windows.
|
||||
- Docs updates.
|
||||
|
||||
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
|
||||
### Changed
|
||||
- Use `path.Base` in `Name` and `HelpName`
|
||||
- Export `GetName` on flag types.
|
||||
|
||||
### Fixed
|
||||
- Flag parsing when skipping is enabled.
|
||||
- Test output cleanup.
|
||||
- Move completion check to account for empty input case.
|
||||
|
||||
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Destination scan support for flags.
|
||||
- Testing against `tip` in Travis CI config.
|
||||
|
||||
### Changed
|
||||
- Go version in Travis CI config.
|
||||
|
||||
### Fixed
|
||||
- Removed redundant tests.
|
||||
- Use correct example naming in tests.
|
||||
|
||||
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
|
||||
### Fixed
|
||||
- Remove unused var in bash completion.
|
||||
|
||||
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Coverage and reference logos in README.
|
||||
|
||||
### Fixed
|
||||
- Use specified values in help and version parsing.
|
||||
- Only display app version and help message once.
|
||||
|
||||
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- More tests for existing functionality.
|
||||
- `ArgsUsage` at app and command level for help text flexibility.
|
||||
|
||||
### Fixed
|
||||
- Honor `HideHelp` and `HideVersion` in `App.Run`.
|
||||
- Remove juvenile word from README.
|
||||
|
||||
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `FullName` on command with accompanying help output update.
|
||||
- Set default `$PROG` in bash completion.
|
||||
|
||||
### Changed
|
||||
- Docs formatting.
|
||||
|
||||
### Fixed
|
||||
- Removed self-referential imports in tests.
|
||||
|
||||
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for `Copyright` at app level.
|
||||
- `Parent` func at context level to walk up context lineage.
|
||||
|
||||
### Fixed
|
||||
- Global flag processing at top level.
|
||||
|
||||
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Aggregate errors from `Before`/`After` funcs.
|
||||
- Doc comments on flag structs.
|
||||
- Include non-global flags when checking version and help.
|
||||
- Travis CI config updates.
|
||||
|
||||
### Fixed
|
||||
- Ensure slice type flags have non-nil values.
|
||||
- Collect global flags from the full command hierarchy.
|
||||
- Docs prose.
|
||||
|
||||
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
|
||||
### Changed
|
||||
- `HelpPrinter` signature includes output writer.
|
||||
|
||||
### Fixed
|
||||
- Specify go 1.1+ in docs.
|
||||
- Set `Writer` when running command as app.
|
||||
|
||||
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Multiple author support.
|
||||
- `NumFlags` at context level.
|
||||
- `Aliases` at command level.
|
||||
|
||||
### Deprecated
|
||||
- `ShortName` at command level.
|
||||
|
||||
### Fixed
|
||||
- Subcommand help output.
|
||||
- Backward compatible support for deprecated `Author` and `Email` fields.
|
||||
- Docs regarding `Names`/`Aliases`.
|
||||
|
||||
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `After` hook func support at app and command level.
|
||||
|
||||
### Fixed
|
||||
- Use parsed context when running command as subcommand.
|
||||
- Docs prose.
|
||||
|
||||
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for hiding `-h / --help` flags, but not `help` subcommand.
|
||||
- Stop flag parsing after `--`.
|
||||
|
||||
### Fixed
|
||||
- Help text for generic flags to specify single value.
|
||||
- Use double quotes in output for defaults.
|
||||
- Use `ParseInt` instead of `ParseUint` for int environment var values.
|
||||
- Use `0` as base when parsing int environment var values.
|
||||
|
||||
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for environment variable lookup "cascade".
|
||||
- Support for `Stdout` on app for output redirection.
|
||||
|
||||
### Fixed
|
||||
- Print command help instead of app help in `ShowCommandHelp`.
|
||||
|
||||
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Docs and example code updates.
|
||||
|
||||
### Changed
|
||||
- Default `-v / --version` flag made optional.
|
||||
|
||||
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `FlagNames` at context level.
|
||||
- Exposed `VersionPrinter` var for more control over version output.
|
||||
- Zsh completion hook.
|
||||
- `AUTHOR` section in default app help template.
|
||||
- Contribution guidelines.
|
||||
- `DurationFlag` type.
|
||||
|
||||
## [1.2.0] - 2014-08-02
|
||||
### Added
|
||||
- Support for environment variable defaults on flags plus tests.
|
||||
|
||||
## [1.1.0] - 2014-07-15
|
||||
### Added
|
||||
- Bash completion.
|
||||
- Optional hiding of built-in help command.
|
||||
- Optional skipping of flag parsing at command level.
|
||||
- `Author`, `Email`, and `Compiled` metadata on app.
|
||||
- `Before` hook func support at app and command level.
|
||||
- `CommandNotFound` func support at app level.
|
||||
- Command reference available on context.
|
||||
- `GenericFlag` type.
|
||||
- `Float64Flag` type.
|
||||
- `BoolTFlag` type.
|
||||
- `IsSet` flag helper on context.
|
||||
- More flag lookup funcs at context level.
|
||||
- More tests & docs.
|
||||
|
||||
### Changed
|
||||
- Help template updates to account for presence/absence of flags.
|
||||
- Separated subcommand help template.
|
||||
- Exposed `HelpPrinter` var for more control over help output.
|
||||
|
||||
## [1.0.0] - 2013-11-01
|
||||
### Added
|
||||
- `help` flag in default app flag set and each command flag set.
|
||||
- Custom handling of argument parsing errors.
|
||||
- Command lookup by name at app level.
|
||||
- `StringSliceFlag` type and supporting `StringSlice` type.
|
||||
- `IntSliceFlag` type and supporting `IntSlice` type.
|
||||
- Slice type flag lookups by name at context level.
|
||||
- Export of app and command help functions.
|
||||
- More tests & docs.
|
||||
|
||||
## 0.1.0 - 2013-07-22
|
||||
### Added
|
||||
- Initial implementation.
|
||||
|
||||
[Unreleased]: https://github.com/urfave/cli/compare/v1.17.0...HEAD
|
||||
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
|
||||
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
|
||||
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
|
||||
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
|
||||
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
|
||||
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
|
||||
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
|
||||
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
|
||||
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
|
||||
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
|
||||
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
|
||||
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
|
||||
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
|
||||
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
|
||||
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
|
||||
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
|
||||
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
|
||||
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
|
||||
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
|
||||
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
|
||||
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
|
||||
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
|
||||
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
|
||||
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0
|
|
@ -0,0 +1,21 @@
|
|||
Copyright (C) 2013 Jeremy Saenz
|
||||
All Rights Reserved.
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
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.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,501 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
|
||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
||||
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
|
||||
|
||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
||||
|
||||
errNonFuncAction = NewExitError("ERROR invalid Action type. "+
|
||||
fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||
errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+
|
||||
fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||
)
|
||||
|
||||
// App is the main structure of a cli application. It is recommended that
|
||||
// an app be created with the cli.NewApp() function
|
||||
type App struct {
|
||||
// The name of the program. Defaults to path.Base(os.Args[0])
|
||||
Name string
|
||||
// Full name of command for help, defaults to Name
|
||||
HelpName string
|
||||
// Description of the program.
|
||||
Usage string
|
||||
// Text to override the USAGE section of help
|
||||
UsageText string
|
||||
// Description of the program argument format.
|
||||
ArgsUsage string
|
||||
// Version of the program
|
||||
Version string
|
||||
// List of commands to execute
|
||||
Commands []Command
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Boolean to enable bash completion commands
|
||||
EnableBashCompletion bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide built-in version flag and the VERSION section of help
|
||||
HideVersion bool
|
||||
// Populate on app startup, only gettable through method Categories()
|
||||
categories CommandCategories
|
||||
// An action to execute when the bash-completion flag is set
|
||||
BashComplete BashCompleteFunc
|
||||
// An action to execute before any subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no subcommands are run
|
||||
Before BeforeFunc
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After AfterFunc
|
||||
// The action to execute when no subcommands are specified
|
||||
Action interface{}
|
||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
||||
// of deprecation period has passed, maybe?
|
||||
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound CommandNotFoundFunc
|
||||
// Execute this function if an usage error occurs
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// Compilation date
|
||||
Compiled time.Time
|
||||
// List of all authors who contributed
|
||||
Authors []Author
|
||||
// Copyright of the binary if any
|
||||
Copyright string
|
||||
// Name of Author (Note: Use App.Authors, this is deprecated)
|
||||
Author string
|
||||
// Email of Author (Note: Use App.Authors, this is deprecated)
|
||||
Email string
|
||||
// Writer writer to write output to
|
||||
Writer io.Writer
|
||||
// ErrWriter writes error output
|
||||
ErrWriter io.Writer
|
||||
// Other custom info
|
||||
Metadata map[string]interface{}
|
||||
|
||||
didSetup bool
|
||||
}
|
||||
|
||||
// Tries to find out when this binary was compiled.
|
||||
// Returns the current time if it fails to find it.
|
||||
func compileTime() time.Time {
|
||||
info, err := os.Stat(os.Args[0])
|
||||
if err != nil {
|
||||
return time.Now()
|
||||
}
|
||||
return info.ModTime()
|
||||
}
|
||||
|
||||
// NewApp creates a new cli Application with some reasonable defaults for Name,
|
||||
// Usage, Version and Action.
|
||||
func NewApp() *App {
|
||||
return &App{
|
||||
Name: filepath.Base(os.Args[0]),
|
||||
HelpName: filepath.Base(os.Args[0]),
|
||||
Usage: "A new cli application",
|
||||
UsageText: "",
|
||||
Version: "0.0.0",
|
||||
BashComplete: DefaultAppComplete,
|
||||
Action: helpCommand.Action,
|
||||
Compiled: compileTime(),
|
||||
Writer: os.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
// Setup runs initialization code to ensure all data structures are ready for
|
||||
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
|
||||
// will return early if setup has already happened.
|
||||
func (a *App) Setup() {
|
||||
if a.didSetup {
|
||||
return
|
||||
}
|
||||
|
||||
a.didSetup = true
|
||||
|
||||
if a.Author != "" || a.Email != "" {
|
||||
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
|
||||
}
|
||||
|
||||
newCmds := []Command{}
|
||||
for _, c := range a.Commands {
|
||||
if c.HelpName == "" {
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
||||
}
|
||||
newCmds = append(newCmds, c)
|
||||
}
|
||||
a.Commands = newCmds
|
||||
|
||||
a.categories = CommandCategories{}
|
||||
for _, command := range a.Commands {
|
||||
a.categories = a.categories.AddCommand(command.Category, command)
|
||||
}
|
||||
sort.Sort(a.categories)
|
||||
|
||||
// append help to commands
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
if (HelpFlag != BoolFlag{}) {
|
||||
a.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
|
||||
//append version/help flags
|
||||
if a.EnableBashCompletion {
|
||||
a.appendFlag(BashCompletionFlag)
|
||||
}
|
||||
|
||||
if !a.HideVersion {
|
||||
a.appendFlag(VersionFlag)
|
||||
}
|
||||
}
|
||||
|
||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
||||
// to the proper flag/args combination
|
||||
func (a *App) Run(arguments []string) (err error) {
|
||||
a.Setup()
|
||||
|
||||
// parse flags
|
||||
set := flagSet(a.Name, a.Flags)
|
||||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(arguments[1:])
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, nil)
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(a.Writer, nerr)
|
||||
ShowAppHelp(context)
|
||||
return nerr
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err := a.OnUsageError(context, err, false)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||
ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if !a.HideHelp && checkHelp(context) {
|
||||
ShowAppHelp(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !a.HideVersion && checkVersion(context) {
|
||||
ShowVersion(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
if afterErr := a.After(context); afterErr != nil {
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
|
||||
ShowAppHelp(context)
|
||||
HandleExitCoder(beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
}
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling
|
||||
func (a *App) RunAndExitOnError() {
|
||||
fmt.Fprintf(a.errWriter(),
|
||||
"DEPRECATED cli.App.RunAndExitOnError. %s See %s\n",
|
||||
contactSysadmin, runAndExitOnErrorDeprecationURL)
|
||||
if err := a.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(a.errWriter(), err)
|
||||
OsExiter(1)
|
||||
}
|
||||
}
|
||||
|
||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
||||
// generate command-specific flags
|
||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
// append help to commands
|
||||
if len(a.Commands) > 0 {
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
if (HelpFlag != BoolFlag{}) {
|
||||
a.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newCmds := []Command{}
|
||||
for _, c := range a.Commands {
|
||||
if c.HelpName == "" {
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
||||
}
|
||||
newCmds = append(newCmds, c)
|
||||
}
|
||||
a.Commands = newCmds
|
||||
|
||||
// append flags
|
||||
if a.EnableBashCompletion {
|
||||
a.appendFlag(BashCompletionFlag)
|
||||
}
|
||||
|
||||
// parse flags
|
||||
set := flagSet(a.Name, a.Flags)
|
||||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, ctx)
|
||||
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(a.Writer, nerr)
|
||||
fmt.Fprintln(a.Writer)
|
||||
if len(a.Commands) > 0 {
|
||||
ShowSubcommandHelp(context)
|
||||
} else {
|
||||
ShowCommandHelp(ctx, context.Args().First())
|
||||
}
|
||||
return nerr
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err = a.OnUsageError(context, err, true)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||
ShowSubcommandHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(a.Commands) > 0 {
|
||||
if checkSubcommandHelp(context) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if checkCommandHelp(ctx, context.Args().First()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
if afterErr != nil {
|
||||
HandleExitCoder(err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
HandleExitCoder(beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
}
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Command returns the named command on App. Returns nil if the command does not exist
|
||||
func (a *App) Command(name string) *Command {
|
||||
for _, c := range a.Commands {
|
||||
if c.HasName(name) {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Categories returns a slice containing all the categories with the commands they contain
|
||||
func (a *App) Categories() CommandCategories {
|
||||
return a.categories
|
||||
}
|
||||
|
||||
// VisibleCategories returns a slice of categories and commands that are
|
||||
// Hidden=false
|
||||
func (a *App) VisibleCategories() []*CommandCategory {
|
||||
ret := []*CommandCategory{}
|
||||
for _, category := range a.categories {
|
||||
if visible := func() *CommandCategory {
|
||||
for _, command := range category.Commands {
|
||||
if !command.Hidden {
|
||||
return category
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); visible != nil {
|
||||
ret = append(ret, visible)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (a *App) VisibleCommands() []Command {
|
||||
ret := []Command{}
|
||||
for _, command := range a.Commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (a *App) VisibleFlags() []Flag {
|
||||
return visibleFlags(a.Flags)
|
||||
}
|
||||
|
||||
func (a *App) hasFlag(flag Flag) bool {
|
||||
for _, f := range a.Flags {
|
||||
if flag == f {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *App) errWriter() io.Writer {
|
||||
|
||||
// When the app ErrWriter is nil use the package level one.
|
||||
if a.ErrWriter == nil {
|
||||
return ErrWriter
|
||||
}
|
||||
|
||||
return a.ErrWriter
|
||||
}
|
||||
|
||||
func (a *App) appendFlag(flag Flag) {
|
||||
if !a.hasFlag(flag) {
|
||||
a.Flags = append(a.Flags, flag)
|
||||
}
|
||||
}
|
||||
|
||||
// Author represents someone who has contributed to a cli project.
|
||||
type Author struct {
|
||||
Name string // The Authors name
|
||||
Email string // The Authors email
|
||||
}
|
||||
|
||||
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
|
||||
func (a Author) String() string {
|
||||
e := ""
|
||||
if a.Email != "" {
|
||||
e = "<" + a.Email + "> "
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v %v", a.Name, e)
|
||||
}
|
||||
|
||||
// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an
|
||||
// ActionFunc, a func with the legacy signature for Action, or some other
|
||||
// invalid thing. If it's an ActionFunc or a func with the legacy signature for
|
||||
// Action, the func is run!
|
||||
func HandleAction(action interface{}, context *Context) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Try to detect a known reflection error from *this scope*, rather than
|
||||
// swallowing all panics that may happen when calling an Action func.
|
||||
s := fmt.Sprintf("%v", r)
|
||||
if strings.HasPrefix(s, "reflect: ") && strings.Contains(s, "too many input arguments") {
|
||||
err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2)
|
||||
} else {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if reflect.TypeOf(action).Kind() != reflect.Func {
|
||||
return errNonFuncAction
|
||||
}
|
||||
|
||||
vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)})
|
||||
|
||||
if len(vals) == 0 {
|
||||
fmt.Fprintf(ErrWriter,
|
||||
"DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n",
|
||||
contactSysadmin, appActionDeprecationURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(vals) > 1 {
|
||||
return errInvalidActionSignature
|
||||
}
|
||||
|
||||
if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok {
|
||||
return retErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
version: "{build}"
|
||||
|
||||
os: Windows Server 2012 R2
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\urfave\cli
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
GOVERSION: 1.6
|
||||
PYTHON: C:\Python27-x64
|
||||
PYTHON_VERSION: 2.7.x
|
||||
PYTHON_ARCH: 64
|
||||
GFMXR_DEBUG: 1
|
||||
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
|
||||
- go version
|
||||
- go env
|
||||
- go get github.com/urfave/gfmxr/...
|
||||
- go get -v -t ./...
|
||||
|
||||
build_script:
|
||||
- python runtests vet
|
||||
- python runtests test
|
||||
- python runtests gfmxr
|
|
@ -0,0 +1,44 @@
|
|||
package cli
|
||||
|
||||
// CommandCategories is a slice of *CommandCategory.
|
||||
type CommandCategories []*CommandCategory
|
||||
|
||||
// CommandCategory is a category containing commands.
|
||||
type CommandCategory struct {
|
||||
Name string
|
||||
Commands Commands
|
||||
}
|
||||
|
||||
func (c CommandCategories) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
}
|
||||
|
||||
func (c CommandCategories) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c CommandCategories) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
// AddCommand adds a command to a category.
|
||||
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
|
||||
for _, commandCategory := range c {
|
||||
if commandCategory.Name == category {
|
||||
commandCategory.Commands = append(commandCategory.Commands, command)
|
||||
return c
|
||||
}
|
||||
}
|
||||
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (c *CommandCategory) VisibleCommands() []Command {
|
||||
ret := []Command{}
|
||||
for _, command := range c.Commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Package cli provides a minimal framework for creating and organizing command line
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||
// cli application can be written as follows:
|
||||
// func main() {
|
||||
// cli.NewApp().Run(os.Args)
|
||||
// }
|
||||
//
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
||||
// func main() {
|
||||
// app := cli.NewApp()
|
||||
// app.Name = "greet"
|
||||
// app.Usage = "say a greeting"
|
||||
// app.Action = func(c *cli.Context) error {
|
||||
// println("Greetings")
|
||||
// }
|
||||
//
|
||||
// app.Run(os.Args)
|
||||
// }
|
||||
package cli
|
|
@ -0,0 +1,279 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Command is a subcommand for a cli.App.
|
||||
type Command struct {
|
||||
// The name of the command
|
||||
Name string
|
||||
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
||||
ShortName string
|
||||
// A list of aliases for the command
|
||||
Aliases []string
|
||||
// A short description of the usage of this command
|
||||
Usage string
|
||||
// Custom text to show on USAGE section of help
|
||||
UsageText string
|
||||
// A longer explanation of how the command works
|
||||
Description string
|
||||
// A short description of the arguments of this command
|
||||
ArgsUsage string
|
||||
// The category the command is part of
|
||||
Category string
|
||||
// The function to call when checking for bash command completions
|
||||
BashComplete BashCompleteFunc
|
||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no sub-subcommands are run
|
||||
Before BeforeFunc
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After AfterFunc
|
||||
// The function to call when this command is invoked
|
||||
Action interface{}
|
||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
||||
// of deprecation period has passed, maybe?
|
||||
|
||||
// Execute this function if a usage error occurs.
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// List of child commands
|
||||
Subcommands Commands
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Treat all flags as normal arguments if true
|
||||
SkipFlagParsing bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide this command from help or completion
|
||||
Hidden bool
|
||||
|
||||
// Full name of command for help, defaults to full command name, including parent commands.
|
||||
HelpName string
|
||||
commandNamePath []string
|
||||
}
|
||||
|
||||
// FullName returns the full name of the command.
|
||||
// For subcommands this ensures that parent commands are part of the command path
|
||||
func (c Command) FullName() string {
|
||||
if c.commandNamePath == nil {
|
||||
return c.Name
|
||||
}
|
||||
return strings.Join(c.commandNamePath, " ")
|
||||
}
|
||||
|
||||
// Commands is a slice of Command
|
||||
type Commands []Command
|
||||
|
||||
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||
func (c Command) Run(ctx *Context) (err error) {
|
||||
if len(c.Subcommands) > 0 {
|
||||
return c.startApp(ctx)
|
||||
}
|
||||
|
||||
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
||||
// append help to flags
|
||||
c.Flags = append(
|
||||
c.Flags,
|
||||
HelpFlag,
|
||||
)
|
||||
}
|
||||
|
||||
if ctx.App.EnableBashCompletion {
|
||||
c.Flags = append(c.Flags, BashCompletionFlag)
|
||||
}
|
||||
|
||||
set := flagSet(c.Name, c.Flags)
|
||||
set.SetOutput(ioutil.Discard)
|
||||
|
||||
if !c.SkipFlagParsing {
|
||||
firstFlagIndex := -1
|
||||
terminatorIndex := -1
|
||||
for index, arg := range ctx.Args() {
|
||||
if arg == "--" {
|
||||
terminatorIndex = index
|
||||
break
|
||||
} else if arg == "-" {
|
||||
// Do nothing. A dash alone is not really a flag.
|
||||
continue
|
||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||
firstFlagIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
if firstFlagIndex > -1 {
|
||||
args := ctx.Args()
|
||||
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
||||
copy(regularArgs, args[1:firstFlagIndex])
|
||||
|
||||
var flagArgs []string
|
||||
if terminatorIndex > -1 {
|
||||
flagArgs = args[firstFlagIndex:terminatorIndex]
|
||||
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
||||
} else {
|
||||
flagArgs = args[firstFlagIndex:]
|
||||
}
|
||||
|
||||
err = set.Parse(append(flagArgs, regularArgs...))
|
||||
} else {
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
}
|
||||
} else {
|
||||
if c.SkipFlagParsing {
|
||||
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if c.OnUsageError != nil {
|
||||
err := c.OnUsageError(ctx, err, false)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
nerr := normalizeFlags(c.Flags, set)
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, nerr)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return nerr
|
||||
}
|
||||
|
||||
context := NewContext(ctx.App, set, ctx)
|
||||
|
||||
if checkCommandCompletions(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkCommandHelp(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.After != nil {
|
||||
defer func() {
|
||||
afterErr := c.After(context)
|
||||
if afterErr != nil {
|
||||
HandleExitCoder(err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if c.Before != nil {
|
||||
err = c.Before(context)
|
||||
if err != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, err)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
context.Command = c
|
||||
err = HandleAction(c.Action, context)
|
||||
|
||||
if err != nil {
|
||||
HandleExitCoder(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Names returns the names including short names and aliases.
|
||||
func (c Command) Names() []string {
|
||||
names := []string{c.Name}
|
||||
|
||||
if c.ShortName != "" {
|
||||
names = append(names, c.ShortName)
|
||||
}
|
||||
|
||||
return append(names, c.Aliases...)
|
||||
}
|
||||
|
||||
// HasName returns true if Command.Name or Command.ShortName matches given name
|
||||
func (c Command) HasName(name string) bool {
|
||||
for _, n := range c.Names() {
|
||||
if n == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Command) startApp(ctx *Context) error {
|
||||
app := NewApp()
|
||||
app.Metadata = ctx.App.Metadata
|
||||
// set the name and usage
|
||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
||||
if c.HelpName == "" {
|
||||
app.HelpName = c.HelpName
|
||||
} else {
|
||||
app.HelpName = app.Name
|
||||
}
|
||||
|
||||
if c.Description != "" {
|
||||
app.Usage = c.Description
|
||||
} else {
|
||||
app.Usage = c.Usage
|
||||
}
|
||||
|
||||
// set CommandNotFound
|
||||
app.CommandNotFound = ctx.App.CommandNotFound
|
||||
|
||||
// set the flags and commands
|
||||
app.Commands = c.Subcommands
|
||||
app.Flags = c.Flags
|
||||
app.HideHelp = c.HideHelp
|
||||
|
||||
app.Version = ctx.App.Version
|
||||
app.HideVersion = ctx.App.HideVersion
|
||||
app.Compiled = ctx.App.Compiled
|
||||
app.Author = ctx.App.Author
|
||||
app.Email = ctx.App.Email
|
||||
app.Writer = ctx.App.Writer
|
||||
|
||||
app.categories = CommandCategories{}
|
||||
for _, command := range c.Subcommands {
|
||||
app.categories = app.categories.AddCommand(command.Category, command)
|
||||
}
|
||||
|
||||
sort.Sort(app.categories)
|
||||
|
||||
// bash completion
|
||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||
if c.BashComplete != nil {
|
||||
app.BashComplete = c.BashComplete
|
||||
}
|
||||
|
||||
// set the actions
|
||||
app.Before = c.Before
|
||||
app.After = c.After
|
||||
if c.Action != nil {
|
||||
app.Action = c.Action
|
||||
} else {
|
||||
app.Action = helpSubcommand.Action
|
||||
}
|
||||
|
||||
for index, cc := range app.Commands {
|
||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
||||
}
|
||||
|
||||
return app.RunAsSubcommand(ctx)
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (c Command) VisibleFlags() []Flag {
|
||||
return visibleFlags(c.Flags)
|
||||
}
|
|
@ -0,0 +1,446 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Context is a type that is passed through to
|
||||
// each Handler action in a cli application. Context
|
||||
// can be used to retrieve context-specific Args and
|
||||
// parsed command-line options.
|
||||
type Context struct {
|
||||
App *App
|
||||
Command Command
|
||||
flagSet *flag.FlagSet
|
||||
setFlags map[string]bool
|
||||
globalSetFlags map[string]bool
|
||||
parentContext *Context
|
||||
}
|
||||
|
||||
// NewContext creates a new context. For use in when invoking an App or Command action.
|
||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
||||
return &Context{App: app, flagSet: set, parentContext: parentCtx}
|
||||
}
|
||||
|
||||
// Int looks up the value of a local int flag, returns 0 if no int flag exists
|
||||
func (c *Context) Int(name string) int {
|
||||
return lookupInt(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Duration looks up the value of a local time.Duration flag, returns 0 if no
|
||||
// time.Duration flag exists
|
||||
func (c *Context) Duration(name string) time.Duration {
|
||||
return lookupDuration(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Float64 looks up the value of a local float64 flag, returns 0 if no float64
|
||||
// flag exists
|
||||
func (c *Context) Float64(name string) float64 {
|
||||
return lookupFloat64(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Bool looks up the value of a local bool flag, returns false if no bool flag exists
|
||||
func (c *Context) Bool(name string) bool {
|
||||
return lookupBool(name, c.flagSet)
|
||||
}
|
||||
|
||||
// BoolT looks up the value of a local boolT flag, returns false if no bool flag exists
|
||||
func (c *Context) BoolT(name string) bool {
|
||||
return lookupBoolT(name, c.flagSet)
|
||||
}
|
||||
|
||||
// String looks up the value of a local string flag, returns "" if no string flag exists
|
||||
func (c *Context) String(name string) string {
|
||||
return lookupString(name, c.flagSet)
|
||||
}
|
||||
|
||||
// StringSlice looks up the value of a local string slice flag, returns nil if no
|
||||
// string slice flag exists
|
||||
func (c *Context) StringSlice(name string) []string {
|
||||
return lookupStringSlice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// IntSlice looks up the value of a local int slice flag, returns nil if no int
|
||||
// slice flag exists
|
||||
func (c *Context) IntSlice(name string) []int {
|
||||
return lookupIntSlice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Generic looks up the value of a local generic flag, returns nil if no generic
|
||||
// flag exists
|
||||
func (c *Context) Generic(name string) interface{} {
|
||||
return lookupGeneric(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalInt looks up the value of a global int flag, returns 0 if no int flag exists
|
||||
func (c *Context) GlobalInt(name string) int {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupInt(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GlobalFloat64 looks up the value of a global float64 flag, returns float64(0)
|
||||
// if no float64 flag exists
|
||||
func (c *Context) GlobalFloat64(name string) float64 {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupFloat64(name, fs)
|
||||
}
|
||||
return float64(0)
|
||||
}
|
||||
|
||||
// GlobalDuration looks up the value of a global time.Duration flag, returns 0
|
||||
// if no time.Duration flag exists
|
||||
func (c *Context) GlobalDuration(name string) time.Duration {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupDuration(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GlobalBool looks up the value of a global bool flag, returns false if no bool
|
||||
// flag exists
|
||||
func (c *Context) GlobalBool(name string) bool {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupBool(name, fs)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GlobalBoolT looks up the value of a global bool flag, returns true if no bool
|
||||
// flag exists
|
||||
func (c *Context) GlobalBoolT(name string) bool {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupBoolT(name, fs)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GlobalString looks up the value of a global string flag, returns "" if no
|
||||
// string flag exists
|
||||
func (c *Context) GlobalString(name string) string {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupString(name, fs)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GlobalStringSlice looks up the value of a global string slice flag, returns
|
||||
// nil if no string slice flag exists
|
||||
func (c *Context) GlobalStringSlice(name string) []string {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupStringSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GlobalIntSlice looks up the value of a global int slice flag, returns nil if
|
||||
// no int slice flag exists
|
||||
func (c *Context) GlobalIntSlice(name string) []int {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupIntSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GlobalGeneric looks up the value of a global generic flag, returns nil if no
|
||||
// generic flag exists
|
||||
func (c *Context) GlobalGeneric(name string) interface{} {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupGeneric(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NumFlags returns the number of flags set
|
||||
func (c *Context) NumFlags() int {
|
||||
return c.flagSet.NFlag()
|
||||
}
|
||||
|
||||
// Set sets a context flag to a value.
|
||||
func (c *Context) Set(name, value string) error {
|
||||
return c.flagSet.Set(name, value)
|
||||
}
|
||||
|
||||
// GlobalSet sets a context flag to a value on the global flagset
|
||||
func (c *Context) GlobalSet(name, value string) error {
|
||||
return globalContext(c).flagSet.Set(name, value)
|
||||
}
|
||||
|
||||
// IsSet determines if the flag was actually set
|
||||
func (c *Context) IsSet(name string) bool {
|
||||
if c.setFlags == nil {
|
||||
c.setFlags = make(map[string]bool)
|
||||
c.flagSet.Visit(func(f *flag.Flag) {
|
||||
c.setFlags[f.Name] = true
|
||||
})
|
||||
}
|
||||
return c.setFlags[name] == true
|
||||
}
|
||||
|
||||
// GlobalIsSet determines if the global flag was actually set
|
||||
func (c *Context) GlobalIsSet(name string) bool {
|
||||
if c.globalSetFlags == nil {
|
||||
c.globalSetFlags = make(map[string]bool)
|
||||
ctx := c
|
||||
if ctx.parentContext != nil {
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext {
|
||||
ctx.flagSet.Visit(func(f *flag.Flag) {
|
||||
c.globalSetFlags[f.Name] = true
|
||||
})
|
||||
}
|
||||
}
|
||||
return c.globalSetFlags[name]
|
||||
}
|
||||
|
||||
// FlagNames returns a slice of flag names used in this context.
|
||||
func (c *Context) FlagNames() (names []string) {
|
||||
for _, flag := range c.Command.Flags {
|
||||
name := strings.Split(flag.GetName(), ",")[0]
|
||||
if name == "help" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GlobalFlagNames returns a slice of global flag names used by the app.
|
||||
func (c *Context) GlobalFlagNames() (names []string) {
|
||||
for _, flag := range c.App.Flags {
|
||||
name := strings.Split(flag.GetName(), ",")[0]
|
||||
if name == "help" || name == "version" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Parent returns the parent context, if any
|
||||
func (c *Context) Parent() *Context {
|
||||
return c.parentContext
|
||||
}
|
||||
|
||||
// Args contains apps console arguments
|
||||
type Args []string
|
||||
|
||||
// Args returns the command line arguments associated with the context.
|
||||
func (c *Context) Args() Args {
|
||||
args := Args(c.flagSet.Args())
|
||||
return args
|
||||
}
|
||||
|
||||
// NArg returns the number of the command line arguments.
|
||||
func (c *Context) NArg() int {
|
||||
return len(c.Args())
|
||||
}
|
||||
|
||||
// Get returns the nth argument, or else a blank string
|
||||
func (a Args) Get(n int) string {
|
||||
if len(a) > n {
|
||||
return a[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// First returns the first argument, or else a blank string
|
||||
func (a Args) First() string {
|
||||
return a.Get(0)
|
||||
}
|
||||
|
||||
// Tail returns the rest of the arguments (not the first one)
|
||||
// or else an empty string slice
|
||||
func (a Args) Tail() []string {
|
||||
if len(a) >= 2 {
|
||||
return []string(a)[1:]
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Present checks if there are any arguments present
|
||||
func (a Args) Present() bool {
|
||||
return len(a) != 0
|
||||
}
|
||||
|
||||
// Swap swaps arguments at the given indexes
|
||||
func (a Args) Swap(from, to int) error {
|
||||
if from >= len(a) || to >= len(a) {
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
a[from], a[to] = a[to], a[from]
|
||||
return nil
|
||||
}
|
||||
|
||||
func globalContext(ctx *Context) *Context {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
if ctx.parentContext == nil {
|
||||
return ctx
|
||||
}
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
}
|
||||
|
||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
|
||||
if ctx.parentContext != nil {
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
for ; ctx != nil; ctx = ctx.parentContext {
|
||||
if f := ctx.flagSet.Lookup(name); f != nil {
|
||||
return ctx.flagSet
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupInt(name string, set *flag.FlagSet) int {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := strconv.Atoi(f.Value.String())
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := time.ParseDuration(f.Value.String())
|
||||
if err == nil {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupFloat64(name string, set *flag.FlagSet) float64 {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := strconv.ParseFloat(f.Value.String(), 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupString(name string, set *flag.FlagSet) string {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return f.Value.String()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return (f.Value.(*StringSlice)).Value()
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return (f.Value.(*IntSlice)).Value()
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return f.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupBool(name string, set *flag.FlagSet) bool {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := strconv.ParseBool(f.Value.String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func lookupBoolT(name string, set *flag.FlagSet) bool {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := strconv.ParseBool(f.Value.String())
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
||||
switch ff.Value.(type) {
|
||||
case *StringSlice:
|
||||
default:
|
||||
set.Set(name, ff.Value.String())
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
||||
visited := make(map[string]bool)
|
||||
set.Visit(func(f *flag.Flag) {
|
||||
visited[f.Name] = true
|
||||
})
|
||||
for _, f := range flags {
|
||||
parts := strings.Split(f.GetName(), ",")
|
||||
if len(parts) == 1 {
|
||||
continue
|
||||
}
|
||||
var ff *flag.Flag
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if visited[name] {
|
||||
if ff != nil {
|
||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
||||
}
|
||||
ff = set.Lookup(name)
|
||||
}
|
||||
}
|
||||
if ff == nil {
|
||||
continue
|
||||
}
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if !visited[name] {
|
||||
copyFlag(name, ff, set)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
||||
var OsExiter = os.Exit
|
||||
|
||||
// ErrWriter is used to write errors to the user. This can be anything
|
||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
||||
var ErrWriter io.Writer = os.Stderr
|
||||
|
||||
// MultiError is an error that wraps multiple errors.
|
||||
type MultiError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
||||
func NewMultiError(err ...error) MultiError {
|
||||
return MultiError{Errors: err}
|
||||
}
|
||||
|
||||
// Error implents the error interface.
|
||||
func (m MultiError) Error() string {
|
||||
errs := make([]string, len(m.Errors))
|
||||
for i, err := range m.Errors {
|
||||
errs[i] = err.Error()
|
||||
}
|
||||
|
||||
return strings.Join(errs, "\n")
|
||||
}
|
||||
|
||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
||||
// code
|
||||
type ExitCoder interface {
|
||||
error
|
||||
ExitCode() int
|
||||
}
|
||||
|
||||
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
|
||||
type ExitError struct {
|
||||
exitCode int
|
||||
message string
|
||||
}
|
||||
|
||||
// NewExitError makes a new *ExitError
|
||||
func NewExitError(message string, exitCode int) *ExitError {
|
||||
return &ExitError{
|
||||
exitCode: exitCode,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the string message, fulfilling the interface required by
|
||||
// `error`
|
||||
func (ee *ExitError) Error() string {
|
||||
return ee.message
|
||||
}
|
||||
|
||||
// ExitCode returns the exit code, fulfilling the interface required by
|
||||
// `ExitCoder`
|
||||
func (ee *ExitError) ExitCode() int {
|
||||
return ee.exitCode
|
||||
}
|
||||
|
||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
||||
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
||||
// given exit code. If the given error is a MultiError, then this func is
|
||||
// called on all members of the Errors slice.
|
||||
func HandleExitCoder(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(ExitCoder); ok {
|
||||
if err.Error() != "" {
|
||||
fmt.Fprintln(ErrWriter, err)
|
||||
}
|
||||
OsExiter(exitErr.ExitCode())
|
||||
return
|
||||
}
|
||||
|
||||
if multiErr, ok := err.(MultiError); ok {
|
||||
for _, merr := range multiErr.Errors {
|
||||
HandleExitCoder(merr)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,667 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultPlaceholder = "value"
|
||||
|
||||
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
||||
var BashCompletionFlag = BoolFlag{
|
||||
Name: "generate-bash-completion",
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
// VersionFlag prints the version for the application
|
||||
var VersionFlag = BoolFlag{
|
||||
Name: "version, v",
|
||||
Usage: "print the version",
|
||||
}
|
||||
|
||||
// HelpFlag prints the help for all commands and subcommands
|
||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
||||
// unless HideHelp is set to true)
|
||||
var HelpFlag = BoolFlag{
|
||||
Name: "help, h",
|
||||
Usage: "show help",
|
||||
}
|
||||
|
||||
// FlagStringer converts a flag definition to a string. This is used by help
|
||||
// to display a flag.
|
||||
var FlagStringer FlagStringFunc = stringifyFlag
|
||||
|
||||
// Flag is a common interface related to parsing flags in cli.
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
||||
// this interface be implemented.
|
||||
type Flag interface {
|
||||
fmt.Stringer
|
||||
// Apply Flag settings to the given flag set
|
||||
Apply(*flag.FlagSet)
|
||||
GetName() string
|
||||
}
|
||||
|
||||
func flagSet(name string, flags []Flag) *flag.FlagSet {
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
|
||||
for _, f := range flags {
|
||||
f.Apply(set)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func eachName(longName string, fn func(string)) {
|
||||
parts := strings.Split(longName, ",")
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
fn(name)
|
||||
}
|
||||
}
|
||||
|
||||
// Generic is a generic parseable type identified by a specific flag
|
||||
type Generic interface {
|
||||
Set(value string) error
|
||||
String() string
|
||||
}
|
||||
|
||||
// GenericFlag is the flag type for types implementing Generic
|
||||
type GenericFlag struct {
|
||||
Name string
|
||||
Value Generic
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the string representation of the generic flag to display the
|
||||
// help text to the user (uses the String() method of the generic flag to show
|
||||
// the value)
|
||||
func (f GenericFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||
// provided by the user for parsing by the flag
|
||||
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||
val := f.Value
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
val.Set(envVal)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of a flag.
|
||||
func (f GenericFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// StringSlice is an opaque type for []string to satisfy flag.Value
|
||||
type StringSlice []string
|
||||
|
||||
// Set appends the string value to the list of values
|
||||
func (f *StringSlice) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *StringSlice) String() string {
|
||||
return fmt.Sprintf("%s", *f)
|
||||
}
|
||||
|
||||
// Value returns the slice of strings set by this flag
|
||||
func (f *StringSlice) Value() []string {
|
||||
return *f
|
||||
}
|
||||
|
||||
// StringSliceFlag is a string flag that can be specified multiple times on the
|
||||
// command-line
|
||||
type StringSliceFlag struct {
|
||||
Name string
|
||||
Value *StringSlice
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f StringSliceFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
newVal := &StringSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
newVal.Set(s)
|
||||
}
|
||||
f.Value = newVal
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Value == nil {
|
||||
f.Value = &StringSlice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of a flag.
|
||||
func (f StringSliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IntSlice is an opaque type for []int to satisfy flag.Value
|
||||
type IntSlice []int
|
||||
|
||||
// Set parses the value into an integer and appends it to the list of values
|
||||
func (f *IntSlice) Set(value string) error {
|
||||
tmp, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = append(*f, tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *IntSlice) String() string {
|
||||
return fmt.Sprintf("%d", *f)
|
||||
}
|
||||
|
||||
// Value returns the slice of ints set by this flag
|
||||
func (f *IntSlice) Value() []int {
|
||||
return *f
|
||||
}
|
||||
|
||||
// IntSliceFlag is an int flag that can be specified multiple times on the
|
||||
// command-line
|
||||
type IntSliceFlag struct {
|
||||
Name string
|
||||
Value *IntSlice
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f IntSliceFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
newVal := &IntSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
err := newVal.Set(s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(ErrWriter, err.Error())
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Value == nil {
|
||||
f.Value = &IntSlice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f IntSliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// BoolFlag is a switch that defaults to false
|
||||
type BoolFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *bool
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f BoolFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
val := false
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err == nil {
|
||||
val = envValBool
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f BoolFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// BoolTFlag this represents a boolean flag that is true by default, but can
|
||||
// still be set to false by --some-flag=false
|
||||
type BoolTFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *bool
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f BoolTFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
val := true
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err == nil {
|
||||
val = envValBool
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f BoolTFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// StringFlag represents a flag that takes as string value
|
||||
type StringFlag struct {
|
||||
Name string
|
||||
Value string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f StringFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
f.Value = envVal
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.StringVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.String(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f StringFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IntFlag is a flag that takes an integer
|
||||
// Errors if the value provided cannot be parsed
|
||||
type IntFlag struct {
|
||||
Name string
|
||||
Value int
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *int
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f IntFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err == nil {
|
||||
f.Value = int(envValInt)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.IntVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Int(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f IntFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// DurationFlag is a flag that takes a duration specified in Go's duration
|
||||
// format: https://golang.org/pkg/time/#ParseDuration
|
||||
type DurationFlag struct {
|
||||
Name string
|
||||
Value time.Duration
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *time.Duration
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f DurationFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValDuration, err := time.ParseDuration(envVal)
|
||||
if err == nil {
|
||||
f.Value = envValDuration
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.DurationVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Duration(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f DurationFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// Float64Flag is a flag that takes an float value
|
||||
// Errors if the value provided cannot be parsed
|
||||
type Float64Flag struct {
|
||||
Name string
|
||||
Value float64
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *float64
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f Float64Flag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
||||
if err == nil {
|
||||
f.Value = float64(envValFloat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.Float64Var(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Float64(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f Float64Flag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func visibleFlags(fl []Flag) []Flag {
|
||||
visible := []Flag{}
|
||||
for _, flag := range fl {
|
||||
if !flagValue(flag).FieldByName("Hidden").Bool() {
|
||||
visible = append(visible, flag)
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
func prefixFor(name string) (prefix string) {
|
||||
if len(name) == 1 {
|
||||
prefix = "-"
|
||||
} else {
|
||||
prefix = "--"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the placeholder, if any, and the unquoted usage string.
|
||||
func unquoteUsage(usage string) (string, string) {
|
||||
for i := 0; i < len(usage); i++ {
|
||||
if usage[i] == '`' {
|
||||
for j := i + 1; j < len(usage); j++ {
|
||||
if usage[j] == '`' {
|
||||
name := usage[i+1 : j]
|
||||
usage = usage[:i] + name + usage[j+1:]
|
||||
return name, usage
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", usage
|
||||
}
|
||||
|
||||
func prefixedNames(fullName, placeholder string) string {
|
||||
var prefixed string
|
||||
parts := strings.Split(fullName, ",")
|
||||
for i, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
prefixed += prefixFor(name) + name
|
||||
if placeholder != "" {
|
||||
prefixed += " " + placeholder
|
||||
}
|
||||
if i < len(parts)-1 {
|
||||
prefixed += ", "
|
||||
}
|
||||
}
|
||||
return prefixed
|
||||
}
|
||||
|
||||
func withEnvHint(envVar, str string) string {
|
||||
envText := ""
|
||||
if envVar != "" {
|
||||
prefix := "$"
|
||||
suffix := ""
|
||||
sep := ", $"
|
||||
if runtime.GOOS == "windows" {
|
||||
prefix = "%"
|
||||
suffix = "%"
|
||||
sep = "%, %"
|
||||
}
|
||||
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
|
||||
func flagValue(f Flag) reflect.Value {
|
||||
fv := reflect.ValueOf(f)
|
||||
for fv.Kind() == reflect.Ptr {
|
||||
fv = reflect.Indirect(fv)
|
||||
}
|
||||
return fv
|
||||
}
|
||||
|
||||
func stringifyFlag(f Flag) string {
|
||||
fv := flagValue(f)
|
||||
|
||||
switch f.(type) {
|
||||
case IntSliceFlag:
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
stringifyIntSliceFlag(f.(IntSliceFlag)))
|
||||
case StringSliceFlag:
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
stringifyStringSliceFlag(f.(StringSliceFlag)))
|
||||
}
|
||||
|
||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
||||
|
||||
needsPlaceholder := false
|
||||
defaultValueString := ""
|
||||
val := fv.FieldByName("Value")
|
||||
|
||||
if val.IsValid() {
|
||||
needsPlaceholder = true
|
||||
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
||||
|
||||
if val.Kind() == reflect.String && val.String() != "" {
|
||||
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
|
||||
}
|
||||
}
|
||||
|
||||
if defaultValueString == " (default: )" {
|
||||
defaultValueString = ""
|
||||
}
|
||||
|
||||
if needsPlaceholder && placeholder == "" {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
|
||||
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
|
||||
}
|
||||
|
||||
func stringifyIntSliceFlag(f IntSliceFlag) string {
|
||||
defaultVals := []string{}
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, i := range f.Value.Value() {
|
||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||
}
|
||||
|
||||
func stringifyStringSliceFlag(f StringSliceFlag) string {
|
||||
defaultVals := []string{}
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, s := range f.Value.Value() {
|
||||
if len(s) > 0 {
|
||||
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||
}
|
||||
|
||||
func stringifySliceFlag(usage, name string, defaultVals []string) string {
|
||||
placeholder, usage := unquoteUsage(usage)
|
||||
if placeholder == "" {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
defaultVal := ""
|
||||
if len(defaultVals) > 0 {
|
||||
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
|
||||
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package cli
|
||||
|
||||
// BashCompleteFunc is an action to execute when the bash-completion flag is set
|
||||
type BashCompleteFunc func(*Context)
|
||||
|
||||
// BeforeFunc is an action to execute before any subcommands are run, but after
|
||||
// the context is ready if a non-nil error is returned, no subcommands are run
|
||||
type BeforeFunc func(*Context) error
|
||||
|
||||
// AfterFunc is an action to execute after any subcommands are run, but after the
|
||||
// subcommand has finished it is run even if Action() panics
|
||||
type AfterFunc func(*Context) error
|
||||
|
||||
// ActionFunc is the action to execute when no subcommands are specified
|
||||
type ActionFunc func(*Context) error
|
||||
|
||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
||||
type CommandNotFoundFunc func(*Context, string)
|
||||
|
||||
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
|
||||
// customized usage error messages. This function is able to replace the
|
||||
// original error messages. If this function is not set, the "Incorrect usage"
|
||||
// is displayed and the execution is interrupted.
|
||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
||||
|
||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
||||
// expected to be a single line.
|
||||
type FlagStringFunc func(Flag) string
|
|
@ -0,0 +1,267 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// AppHelpTemplate is the text template for the Default help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var AppHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
||||
{{if .Version}}{{if not .HideVersion}}
|
||||
VERSION:
|
||||
{{.Version}}
|
||||
{{end}}{{end}}{{if len .Authors}}
|
||||
AUTHOR(S):
|
||||
{{range .Authors}}{{.}}{{end}}
|
||||
{{end}}{{if .VisibleCommands}}
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
|
||||
{{end}}{{end}}{{if .VisibleFlags}}
|
||||
GLOBAL OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}{{if .Copyright}}
|
||||
COPYRIGHT:
|
||||
{{.Copyright}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
// CommandHelpTemplate is the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var CommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var SubcommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
|
||||
{{end}}{{if .VisibleFlags}}
|
||||
OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
var helpCommand = Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *Context) error {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
return ShowCommandHelp(c, args.First())
|
||||
}
|
||||
|
||||
ShowAppHelp(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var helpSubcommand = Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *Context) error {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
return ShowCommandHelp(c, args.First())
|
||||
}
|
||||
|
||||
return ShowSubcommandHelp(c)
|
||||
},
|
||||
}
|
||||
|
||||
// Prints help for the App or Command
|
||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
||||
|
||||
// HelpPrinter is a function that writes the help output. If not set a default
|
||||
// is used. The function signature is:
|
||||
// func(w io.Writer, templ string, data interface{})
|
||||
var HelpPrinter helpPrinter = printHelp
|
||||
|
||||
// VersionPrinter prints the version for the App
|
||||
var VersionPrinter = printVersion
|
||||
|
||||
// ShowAppHelp is an action that displays the help.
|
||||
func ShowAppHelp(c *Context) error {
|
||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
||||
func DefaultAppComplete(c *Context) {
|
||||
for _, command := range c.App.Commands {
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
for _, name := range command.Names() {
|
||||
fmt.Fprintln(c.App.Writer, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ShowCommandHelp prints help for the given command
|
||||
func ShowCommandHelp(ctx *Context, command string) error {
|
||||
// show the subcommand help for a command with subcommands
|
||||
if command == "" {
|
||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, c := range ctx.App.Commands {
|
||||
if c.HasName(command) {
|
||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.App.CommandNotFound == nil {
|
||||
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
|
||||
}
|
||||
|
||||
ctx.App.CommandNotFound(ctx, command)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowSubcommandHelp prints help for the given subcommand
|
||||
func ShowSubcommandHelp(c *Context) error {
|
||||
return ShowCommandHelp(c, c.Command.Name)
|
||||
}
|
||||
|
||||
// ShowVersion prints the version number of the App
|
||||
func ShowVersion(c *Context) {
|
||||
VersionPrinter(c)
|
||||
}
|
||||
|
||||
func printVersion(c *Context) {
|
||||
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
||||
}
|
||||
|
||||
// ShowCompletions prints the lists of commands within a given context
|
||||
func ShowCompletions(c *Context) {
|
||||
a := c.App
|
||||
if a != nil && a.BashComplete != nil {
|
||||
a.BashComplete(c)
|
||||
}
|
||||
}
|
||||
|
||||
// ShowCommandCompletions prints the custom completions for a given command
|
||||
func ShowCommandCompletions(ctx *Context, command string) {
|
||||
c := ctx.App.Command(command)
|
||||
if c != nil && c.BashComplete != nil {
|
||||
c.BashComplete(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
||||
funcMap := template.FuncMap{
|
||||
"join": strings.Join,
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||
err := t.Execute(w, data)
|
||||
if err != nil {
|
||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
||||
// we can do to recover.
|
||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
||||
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func checkVersion(c *Context) bool {
|
||||
found := false
|
||||
if VersionFlag.Name != "" {
|
||||
eachName(VersionFlag.Name, func(name string) {
|
||||
if c.GlobalBool(name) || c.Bool(name) {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func checkHelp(c *Context) bool {
|
||||
found := false
|
||||
if HelpFlag.Name != "" {
|
||||
eachName(HelpFlag.Name, func(name string) {
|
||||
if c.GlobalBool(name) || c.Bool(name) {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func checkCommandHelp(c *Context, name string) bool {
|
||||
if c.Bool("h") || c.Bool("help") {
|
||||
ShowCommandHelp(c, name)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkSubcommandHelp(c *Context) bool {
|
||||
if c.GlobalBool("h") || c.GlobalBool("help") {
|
||||
ShowSubcommandHelp(c)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkCompletions(c *Context) bool {
|
||||
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
|
||||
ShowCompletions(c)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkCommandCompletions(c *Context, name string) bool {
|
||||
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
|
||||
ShowCommandCompletions(c, name)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from subprocess import check_call, check_output
|
||||
|
||||
|
||||
PACKAGE_NAME = os.environ.get(
|
||||
'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
|
||||
)
|
||||
|
||||
|
||||
def main(sysargs=sys.argv[:]):
|
||||
targets = {
|
||||
'vet': _vet,
|
||||
'test': _test,
|
||||
'gfmxr': _gfmxr
|
||||
}
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'target', nargs='?', choices=tuple(targets.keys()), default='test'
|
||||
)
|
||||
args = parser.parse_args(sysargs[1:])
|
||||
|
||||
targets[args.target]()
|
||||
return 0
|
||||
|
||||
|
||||
def _test():
|
||||
if check_output('go version'.split()).split()[2] < 'go1.2':
|
||||
_run('go test -v .'.split())
|
||||
return
|
||||
|
||||
coverprofiles = []
|
||||
for subpackage in ['', 'altsrc']:
|
||||
coverprofile = 'cli.coverprofile'
|
||||
if subpackage != '':
|
||||
coverprofile = '{}.coverprofile'.format(subpackage)
|
||||
|
||||
coverprofiles.append(coverprofile)
|
||||
|
||||
_run('go test -v'.split() + [
|
||||
'-coverprofile={}'.format(coverprofile),
|
||||
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
|
||||
])
|
||||
|
||||
combined_name = _combine_coverprofiles(coverprofiles)
|
||||
_run('go tool cover -func={}'.format(combined_name).split())
|
||||
os.remove(combined_name)
|
||||
|
||||
|
||||
def _gfmxr():
|
||||
_run(['gfmxr', '-c', str(_gfmxr_count()), '-s', 'README.md'])
|
||||
|
||||
|
||||
def _vet():
|
||||
_run('go vet ./...'.split())
|
||||
|
||||
|
||||
def _run(command):
|
||||
print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
|
||||
check_call(command)
|
||||
|
||||
|
||||
def _gfmxr_count():
|
||||
with open('README.md') as infile:
|
||||
lines = infile.read().splitlines()
|
||||
return len(filter(_is_go_runnable, lines))
|
||||
|
||||
|
||||
def _is_go_runnable(line):
|
||||
return line.startswith('package main')
|
||||
|
||||
|
||||
def _combine_coverprofiles(coverprofiles):
|
||||
combined = tempfile.NamedTemporaryFile(
|
||||
suffix='.coverprofile', delete=False
|
||||
)
|
||||
combined.write('mode: set\n')
|
||||
|
||||
for coverprofile in coverprofiles:
|
||||
with open(coverprofile, 'r') as infile:
|
||||
for line in infile.readlines():
|
||||
if not line.startswith('mode: '):
|
||||
combined.write(line)
|
||||
|
||||
combined.flush()
|
||||
name = combined.name
|
||||
combined.close()
|
||||
return name
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
392
vendor/github.com/ethereum/go-ethereum/accounts/account_manager.go
generated
vendored
Normal file
392
vendor/github.com/ethereum/go-ethereum/accounts/account_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,392 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package accounts implements encrypted storage of secp256k1 private keys.
|
||||
//
|
||||
// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
|
||||
// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/whisper"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLocked = errors.New("account is locked")
|
||||
ErrNoMatch = errors.New("no key for given address or file")
|
||||
ErrDecrypt = errors.New("could not decrypt key with given passphrase")
|
||||
)
|
||||
|
||||
// Account represents a stored key.
|
||||
// When used as an argument, it selects a unique key file to act on.
|
||||
type Account struct {
|
||||
Address common.Address // Ethereum account address derived from the key
|
||||
|
||||
// File contains the key file name.
|
||||
// When Acccount is used as an argument to select a key, File can be left blank to
|
||||
// select just by address or set to the basename or absolute path of a file in the key
|
||||
// directory. Accounts returned by Manager will always contain an absolute path.
|
||||
File string
|
||||
}
|
||||
|
||||
func (acc *Account) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + acc.Address.Hex() + `"`), nil
|
||||
}
|
||||
|
||||
func (acc *Account) UnmarshalJSON(raw []byte) error {
|
||||
return json.Unmarshal(raw, &acc.Address)
|
||||
}
|
||||
|
||||
// Manager manages a key storage directory on disk.
|
||||
type Manager struct {
|
||||
cache *addrCache
|
||||
keyStore keyStore
|
||||
mu sync.RWMutex
|
||||
unlocked map[common.Address]*unlocked
|
||||
sync *[]node.Service
|
||||
}
|
||||
|
||||
type unlocked struct {
|
||||
*Key
|
||||
abort chan struct{}
|
||||
}
|
||||
|
||||
// NewManager creates a manager for the given directory.
|
||||
func NewManager(keydir string, scryptN, scryptP int, s *[]node.Service) *Manager {
|
||||
keydir, _ = filepath.Abs(keydir)
|
||||
am := &Manager{
|
||||
keyStore: &keyStorePassphrase{keydir, scryptN, scryptP},
|
||||
sync: s,
|
||||
}
|
||||
am.init(keydir)
|
||||
return am
|
||||
}
|
||||
|
||||
// NewPlaintextManager creates a manager for the given directory.
|
||||
// Deprecated: Use NewManager.
|
||||
func NewPlaintextManager(keydir string) *Manager {
|
||||
keydir, _ = filepath.Abs(keydir)
|
||||
am := &Manager{keyStore: &keyStorePlain{keydir}}
|
||||
am.init(keydir)
|
||||
return am
|
||||
}
|
||||
|
||||
func (am *Manager) init(keydir string) {
|
||||
am.unlocked = make(map[common.Address]*unlocked)
|
||||
am.cache = newAddrCache(keydir)
|
||||
// TODO: In order for this finalizer to work, there must be no references
|
||||
// to am. addrCache doesn't keep a reference but unlocked keys do,
|
||||
// so the finalizer will not trigger until all timed unlocks have expired.
|
||||
runtime.SetFinalizer(am, func(m *Manager) {
|
||||
m.cache.close()
|
||||
})
|
||||
}
|
||||
|
||||
// HasAddress reports whether a key with the given address is present.
|
||||
func (am *Manager) HasAddress(addr common.Address) bool {
|
||||
return am.cache.hasAddress(addr)
|
||||
}
|
||||
|
||||
// Accounts returns all key files present in the directory.
|
||||
func (am *Manager) Accounts() []Account {
|
||||
return am.cache.accounts()
|
||||
}
|
||||
|
||||
// DeleteAccount deletes the key matched by account if the passphrase is correct.
|
||||
// If a contains no filename, the address must match a unique key.
|
||||
func (am *Manager) DeleteAccount(a Account, passphrase string) error {
|
||||
// Decrypting the key isn't really necessary, but we do
|
||||
// it anyway to check the password and zero out the key
|
||||
// immediately afterwards.
|
||||
a, key, err := am.getDecryptedKey(a, passphrase)
|
||||
if key != nil {
|
||||
zeroKey(key.PrivateKey)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The order is crucial here. The key is dropped from the
|
||||
// cache after the file is gone so that a reload happening in
|
||||
// between won't insert it into the cache again.
|
||||
err = os.Remove(a.File)
|
||||
if err == nil {
|
||||
am.cache.delete(a)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Sign signs hash with an unlocked private key matching the given address.
|
||||
func (am *Manager) Sign(addr common.Address, hash []byte) (signature []byte, err error) {
|
||||
am.mu.RLock()
|
||||
defer am.mu.RUnlock()
|
||||
unlockedKey, found := am.unlocked[addr]
|
||||
if !found {
|
||||
return nil, ErrLocked
|
||||
}
|
||||
return crypto.Sign(hash, unlockedKey.PrivateKey)
|
||||
}
|
||||
|
||||
// Unlock unlocks the given account indefinitely.
|
||||
func (am *Manager) Unlock(a Account, keyAuth string) error {
|
||||
return am.TimedUnlock(a, keyAuth, 0)
|
||||
}
|
||||
|
||||
// Lock removes the private key with the given address from memory.
|
||||
func (am *Manager) Lock(addr common.Address) error {
|
||||
am.mu.Lock()
|
||||
if unl, found := am.unlocked[addr]; found {
|
||||
am.mu.Unlock()
|
||||
am.expire(addr, unl, time.Duration(0)*time.Nanosecond)
|
||||
} else {
|
||||
am.mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TimedUnlock unlocks the given account with the passphrase. The account
|
||||
// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
|
||||
// until the program exits. The account must match a unique key file.
|
||||
//
|
||||
// If the account address is already unlocked for a duration, TimedUnlock extends or
|
||||
// shortens the active unlock timeout. If the address was previously unlocked
|
||||
// indefinitely the timeout is not altered.
|
||||
func (am *Manager) TimedUnlock(a Account, passphrase string, timeout time.Duration) error {
|
||||
|
||||
a, key, err := am.getDecryptedKey(a, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sync key to subprotocols (e.g., whisper identity)
|
||||
if am.sync != nil {
|
||||
address := fmt.Sprintf("%x", a.Address)
|
||||
err = am.syncAccounts(address, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sync accounts: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
am.mu.Lock()
|
||||
defer am.mu.Unlock()
|
||||
u, found := am.unlocked[a.Address]
|
||||
if found {
|
||||
if u.abort == nil {
|
||||
// The address was unlocked indefinitely, so unlocking
|
||||
// it with a timeout would be confusing.
|
||||
zeroKey(key.PrivateKey)
|
||||
return nil
|
||||
} else {
|
||||
// Terminate the expire goroutine and replace it below.
|
||||
close(u.abort)
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
u = &unlocked{Key: key, abort: make(chan struct{})}
|
||||
go am.expire(a.Address, u, timeout)
|
||||
} else {
|
||||
u = &unlocked{Key: key}
|
||||
}
|
||||
am.unlocked[a.Address] = u
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *Manager) syncAccounts(a string, key *Key) error {
|
||||
for _, service := range *am.sync {
|
||||
if whisperInstance, ok := service.(*whisper.Whisper); ok && key.WhisperEnabled {
|
||||
err := whisperInstance.InjectIdentity(key.PrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sync accounts with shh: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *Manager) getDecryptedKey(a Account, auth string) (Account, *Key, error) {
|
||||
am.cache.maybeReload()
|
||||
am.cache.mu.Lock()
|
||||
a, err := am.cache.find(a)
|
||||
am.cache.mu.Unlock()
|
||||
if err != nil {
|
||||
return a, nil, err
|
||||
}
|
||||
key, err := am.keyStore.GetKey(a.Address, a.File, auth)
|
||||
return a, key, err
|
||||
}
|
||||
|
||||
func (am *Manager) expire(addr common.Address, u *unlocked, timeout time.Duration) {
|
||||
t := time.NewTimer(timeout)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-u.abort:
|
||||
// just quit
|
||||
case <-t.C:
|
||||
am.mu.Lock()
|
||||
// only drop if it's still the same key instance that dropLater
|
||||
// was launched with. we can check that using pointer equality
|
||||
// because the map stores a new pointer every time the key is
|
||||
// unlocked.
|
||||
if am.unlocked[addr] == u {
|
||||
zeroKey(u.PrivateKey)
|
||||
delete(am.unlocked, addr)
|
||||
}
|
||||
am.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccount generates a new key and stores it into the key directory,
|
||||
// encrypting it with the passphrase.
|
||||
func (am *Manager) NewAccount(passphrase string, w bool) (Account, error) {
|
||||
|
||||
key, account, err := storeNewKey(am.keyStore, crand.Reader, passphrase, w)
|
||||
if err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
|
||||
// Add the account to the cache immediately rather
|
||||
// than waiting for file system notifications to pick it up.
|
||||
am.cache.add(account)
|
||||
|
||||
// sync key to subprotocols (e.g., whisper identity)
|
||||
if am.sync != nil {
|
||||
address := fmt.Sprintf("%x", account.Address)
|
||||
err = am.syncAccounts(address, key)
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("failed to sync accounts: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// AccountByIndex returns the ith account.
|
||||
func (am *Manager) AccountByIndex(i int) (Account, error) {
|
||||
accounts := am.Accounts()
|
||||
if i < 0 || i >= len(accounts) {
|
||||
return Account{}, fmt.Errorf("account index %d out of range [0, %d]", i, len(accounts)-1)
|
||||
}
|
||||
return accounts[i], nil
|
||||
}
|
||||
|
||||
// Export exports as a JSON key, encrypted with newPassphrase.
|
||||
func (am *Manager) Export(a Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
|
||||
_, key, err := am.getDecryptedKey(a, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var N, P int
|
||||
if store, ok := am.keyStore.(*keyStorePassphrase); ok {
|
||||
N, P = store.scryptN, store.scryptP
|
||||
} else {
|
||||
N, P = StandardScryptN, StandardScryptP
|
||||
}
|
||||
return EncryptKey(key, newPassphrase, N, P)
|
||||
}
|
||||
|
||||
// Import stores the given encrypted JSON key into the key directory.
|
||||
func (am *Manager) Import(keyJSON []byte, passphrase, newPassphrase string) (Account, error) {
|
||||
key, err := DecryptKey(keyJSON, passphrase)
|
||||
if key != nil && key.PrivateKey != nil {
|
||||
defer zeroKey(key.PrivateKey)
|
||||
}
|
||||
if err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
return am.importKey(key, newPassphrase)
|
||||
}
|
||||
|
||||
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
|
||||
func (am *Manager) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error) {
|
||||
key := newKeyFromECDSA(priv)
|
||||
if am.cache.hasAddress(key.Address) {
|
||||
return Account{}, fmt.Errorf("account already exists")
|
||||
}
|
||||
|
||||
return am.importKey(key, passphrase)
|
||||
}
|
||||
|
||||
func (am *Manager) importKey(key *Key, passphrase string) (Account, error) {
|
||||
a := Account{Address: key.Address, File: am.keyStore.JoinPath(keyFileName(key.Address))}
|
||||
if err := am.keyStore.StoreKey(a.File, key, passphrase); err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
am.cache.add(a)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Update changes the passphrase of an existing account.
|
||||
func (am *Manager) Update(a Account, passphrase, newPassphrase string) error {
|
||||
a, key, err := am.getDecryptedKey(a, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return am.keyStore.StoreKey(a.File, key, newPassphrase)
|
||||
}
|
||||
|
||||
// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
|
||||
// a key file in the key directory. The key file is encrypted with the same passphrase.
|
||||
func (am *Manager) ImportPreSaleKey(keyJSON []byte, passphrase string) (Account, error) {
|
||||
a, _, err := importPreSaleKey(am.keyStore, keyJSON, passphrase)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
am.cache.add(a)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// zeroKey zeroes a private key in memory.
|
||||
func zeroKey(k *ecdsa.PrivateKey) {
|
||||
b := k.D.Bits()
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// APIs implements node.Service
|
||||
func (am *Manager) APIs() []rpc.API {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Protocols implements node.Service
|
||||
func (am *Manager) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start implements node.Service
|
||||
func (am *Manager) Start(srvr *p2p.Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop implements node.Service
|
||||
func (am *Manager) Stop() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
||||
// not support change notifications. It also applies if the keystore directory does not
|
||||
// exist yet, the code will attempt to create a watcher at most this often.
|
||||
const minReloadInterval = 2 * time.Second
|
||||
|
||||
type accountsByFile []Account
|
||||
|
||||
func (s accountsByFile) Len() int { return len(s) }
|
||||
func (s accountsByFile) Less(i, j int) bool { return s[i].File < s[j].File }
|
||||
func (s accountsByFile) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// AmbiguousAddrError is returned when attempting to unlock
|
||||
// an address for which more than one file exists.
|
||||
type AmbiguousAddrError struct {
|
||||
Addr common.Address
|
||||
Matches []Account
|
||||
}
|
||||
|
||||
func (err *AmbiguousAddrError) Error() string {
|
||||
files := ""
|
||||
for i, a := range err.Matches {
|
||||
files += a.File
|
||||
if i < len(err.Matches)-1 {
|
||||
files += ", "
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("multiple keys match address (%s)", files)
|
||||
}
|
||||
|
||||
// addrCache is a live index of all accounts in the keystore.
|
||||
type addrCache struct {
|
||||
keydir string
|
||||
watcher *watcher
|
||||
mu sync.Mutex
|
||||
all accountsByFile
|
||||
byAddr map[common.Address][]Account
|
||||
throttle *time.Timer
|
||||
}
|
||||
|
||||
func newAddrCache(keydir string) *addrCache {
|
||||
ac := &addrCache{
|
||||
keydir: keydir,
|
||||
byAddr: make(map[common.Address][]Account),
|
||||
}
|
||||
ac.watcher = newWatcher(ac)
|
||||
return ac
|
||||
}
|
||||
|
||||
func (ac *addrCache) accounts() []Account {
|
||||
ac.maybeReload()
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
cpy := make([]Account, len(ac.all))
|
||||
copy(cpy, ac.all)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (ac *addrCache) hasAddress(addr common.Address) bool {
|
||||
ac.maybeReload()
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
return len(ac.byAddr[addr]) > 0
|
||||
}
|
||||
|
||||
func (ac *addrCache) add(newAccount Account) {
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
|
||||
i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].File >= newAccount.File })
|
||||
if i < len(ac.all) && ac.all[i] == newAccount {
|
||||
return
|
||||
}
|
||||
// newAccount is not in the cache.
|
||||
ac.all = append(ac.all, Account{})
|
||||
copy(ac.all[i+1:], ac.all[i:])
|
||||
ac.all[i] = newAccount
|
||||
ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
|
||||
}
|
||||
|
||||
// note: removed needs to be unique here (i.e. both File and Address must be set).
|
||||
func (ac *addrCache) delete(removed Account) {
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
ac.all = removeAccount(ac.all, removed)
|
||||
if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
|
||||
delete(ac.byAddr, removed.Address)
|
||||
} else {
|
||||
ac.byAddr[removed.Address] = ba
|
||||
}
|
||||
}
|
||||
|
||||
func removeAccount(slice []Account, elem Account) []Account {
|
||||
for i := range slice {
|
||||
if slice[i] == elem {
|
||||
return append(slice[:i], slice[i+1:]...)
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// find returns the cached account for address if there is a unique match.
|
||||
// The exact matching rules are explained by the documentation of Account.
|
||||
// Callers must hold ac.mu.
|
||||
func (ac *addrCache) find(a Account) (Account, error) {
|
||||
// Limit search to address candidates if possible.
|
||||
matches := ac.all
|
||||
if (a.Address != common.Address{}) {
|
||||
matches = ac.byAddr[a.Address]
|
||||
}
|
||||
if a.File != "" {
|
||||
// If only the basename is specified, complete the path.
|
||||
if !strings.ContainsRune(a.File, filepath.Separator) {
|
||||
a.File = filepath.Join(ac.keydir, a.File)
|
||||
}
|
||||
for i := range matches {
|
||||
if matches[i].File == a.File {
|
||||
return matches[i], nil
|
||||
}
|
||||
}
|
||||
if (a.Address == common.Address{}) {
|
||||
return Account{}, ErrNoMatch
|
||||
}
|
||||
}
|
||||
switch len(matches) {
|
||||
case 1:
|
||||
return matches[0], nil
|
||||
case 0:
|
||||
return Account{}, ErrNoMatch
|
||||
default:
|
||||
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]Account, len(matches))}
|
||||
copy(err.Matches, matches)
|
||||
return Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
func (ac *addrCache) maybeReload() {
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
if ac.watcher.running {
|
||||
return // A watcher is running and will keep the cache up-to-date.
|
||||
}
|
||||
if ac.throttle == nil {
|
||||
ac.throttle = time.NewTimer(0)
|
||||
} else {
|
||||
select {
|
||||
case <-ac.throttle.C:
|
||||
default:
|
||||
return // The cache was reloaded recently.
|
||||
}
|
||||
}
|
||||
ac.watcher.start()
|
||||
ac.reload()
|
||||
ac.throttle.Reset(minReloadInterval)
|
||||
}
|
||||
|
||||
func (ac *addrCache) close() {
|
||||
ac.mu.Lock()
|
||||
ac.watcher.close()
|
||||
if ac.throttle != nil {
|
||||
ac.throttle.Stop()
|
||||
}
|
||||
ac.mu.Unlock()
|
||||
}
|
||||
|
||||
// reload caches addresses of existing accounts.
|
||||
// Callers must hold ac.mu.
|
||||
func (ac *addrCache) reload() {
|
||||
accounts, err := ac.scan()
|
||||
if err != nil && glog.V(logger.Debug) {
|
||||
glog.Errorf("can't load keys: %v", err)
|
||||
}
|
||||
ac.all = accounts
|
||||
sort.Sort(ac.all)
|
||||
for k := range ac.byAddr {
|
||||
delete(ac.byAddr, k)
|
||||
}
|
||||
for _, a := range accounts {
|
||||
ac.byAddr[a.Address] = append(ac.byAddr[a.Address], a)
|
||||
}
|
||||
glog.V(logger.Debug).Infof("reloaded keys, cache has %d accounts", len(ac.all))
|
||||
}
|
||||
|
||||
func (ac *addrCache) scan() ([]Account, error) {
|
||||
files, err := ioutil.ReadDir(ac.keydir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
buf = new(bufio.Reader)
|
||||
addrs []Account
|
||||
keyJSON struct {
|
||||
Address common.Address `json:"address"`
|
||||
}
|
||||
)
|
||||
for _, fi := range files {
|
||||
path := filepath.Join(ac.keydir, fi.Name())
|
||||
if skipKeyFile(fi) {
|
||||
glog.V(logger.Detail).Infof("ignoring file %s", path)
|
||||
continue
|
||||
}
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
glog.V(logger.Detail).Infoln(err)
|
||||
continue
|
||||
}
|
||||
buf.Reset(fd)
|
||||
// Parse the address.
|
||||
keyJSON.Address = common.Address{}
|
||||
err = json.NewDecoder(buf).Decode(&keyJSON)
|
||||
switch {
|
||||
case err != nil:
|
||||
glog.V(logger.Debug).Infof("can't decode key %s: %v", path, err)
|
||||
case (keyJSON.Address == common.Address{}):
|
||||
glog.V(logger.Debug).Infof("can't decode key %s: missing or zero address", path)
|
||||
default:
|
||||
addrs = append(addrs, Account{Address: keyJSON.Address, File: path})
|
||||
}
|
||||
fd.Close()
|
||||
}
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
func skipKeyFile(fi os.FileInfo) bool {
|
||||
// Skip editor backups and UNIX-style hidden files.
|
||||
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
|
||||
return true
|
||||
}
|
||||
// Skip misc special files, directories (yes, symlinks too).
|
||||
if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
version = 3
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
Id uuid.UUID // Version 4 "random" for unique id not derived from key data
|
||||
// to simplify lookups we also store the address
|
||||
Address common.Address
|
||||
// we only store privkey as pubkey/address can be derived from it
|
||||
// privkey in this struct is always in plaintext
|
||||
PrivateKey *ecdsa.PrivateKey
|
||||
// if whisper is enabled here, the address will be used as a whisper
|
||||
// identity upon creation of the account or unlocking of the account
|
||||
WhisperEnabled bool
|
||||
}
|
||||
|
||||
type keyStore interface {
|
||||
// Loads and decrypts the key from disk.
|
||||
GetKey(addr common.Address, filename string, auth string) (*Key, error)
|
||||
// Writes and encrypts the key.
|
||||
StoreKey(filename string, k *Key, auth string) error
|
||||
// Joins filename with the key directory unless it is already absolute.
|
||||
JoinPath(filename string) string
|
||||
}
|
||||
|
||||
type plainKeyJSON struct {
|
||||
Address string `json:"address"`
|
||||
PrivateKey string `json:"privatekey"`
|
||||
Id string `json:"id"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type encryptedKeyJSONV3 struct {
|
||||
Address string `json:"address"`
|
||||
Crypto cryptoJSON `json:"crypto"`
|
||||
Id string `json:"id"`
|
||||
Version int `json:"version"`
|
||||
WhisperEnabled bool `json:"whisperenabled"`
|
||||
}
|
||||
|
||||
type encryptedKeyJSONV1 struct {
|
||||
Address string `json:"address"`
|
||||
Crypto cryptoJSON `json:"crypto"`
|
||||
Id string `json:"id"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type cryptoJSON struct {
|
||||
Cipher string `json:"cipher"`
|
||||
CipherText string `json:"ciphertext"`
|
||||
CipherParams cipherparamsJSON `json:"cipherparams"`
|
||||
KDF string `json:"kdf"`
|
||||
KDFParams map[string]interface{} `json:"kdfparams"`
|
||||
MAC string `json:"mac"`
|
||||
}
|
||||
|
||||
type cipherparamsJSON struct {
|
||||
IV string `json:"iv"`
|
||||
}
|
||||
|
||||
type scryptParamsJSON struct {
|
||||
N int `json:"n"`
|
||||
R int `json:"r"`
|
||||
P int `json:"p"`
|
||||
DkLen int `json:"dklen"`
|
||||
Salt string `json:"salt"`
|
||||
}
|
||||
|
||||
func (k *Key) MarshalJSON() (j []byte, err error) {
|
||||
jStruct := plainKeyJSON{
|
||||
hex.EncodeToString(k.Address[:]),
|
||||
hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)),
|
||||
k.Id.String(),
|
||||
version,
|
||||
}
|
||||
j, err = json.Marshal(jStruct)
|
||||
return j, err
|
||||
}
|
||||
|
||||
func (k *Key) UnmarshalJSON(j []byte) (err error) {
|
||||
keyJSON := new(plainKeyJSON)
|
||||
err = json.Unmarshal(j, &keyJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u := new(uuid.UUID)
|
||||
*u = uuid.Parse(keyJSON.Id)
|
||||
k.Id = *u
|
||||
addr, err := hex.DecodeString(keyJSON.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privkey, err := hex.DecodeString(keyJSON.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.Address = common.BytesToAddress(addr)
|
||||
k.PrivateKey = crypto.ToECDSA(privkey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
|
||||
id := uuid.NewRandom()
|
||||
key := &Key{
|
||||
Id: id,
|
||||
Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
|
||||
PrivateKey: privateKeyECDSA,
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit
|
||||
// into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we
|
||||
// retry until the first byte is 0.
|
||||
func NewKeyForDirectICAP(rand io.Reader) *Key {
|
||||
randBytes := make([]byte, 64)
|
||||
_, err := rand.Read(randBytes)
|
||||
if err != nil {
|
||||
panic("key generation: could not read from random source: " + err.Error())
|
||||
}
|
||||
reader := bytes.NewReader(randBytes)
|
||||
privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader)
|
||||
if err != nil {
|
||||
panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
|
||||
}
|
||||
key := newKeyFromECDSA(privateKeyECDSA)
|
||||
if !strings.HasPrefix(key.Address.Hex(), "0x00") {
|
||||
return NewKeyForDirectICAP(rand)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func newKey(rand io.Reader) (*Key, error) {
|
||||
privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), rand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newKeyFromECDSA(privateKeyECDSA), nil
|
||||
}
|
||||
|
||||
func storeNewKey(ks keyStore, rand io.Reader, auth string, w bool) (*Key, Account, error) {
|
||||
key, err := newKey(rand)
|
||||
if err != nil {
|
||||
return nil, Account{}, err
|
||||
}
|
||||
key.WhisperEnabled = w
|
||||
a := Account{Address: key.Address, File: ks.JoinPath(keyFileName(key.Address))}
|
||||
if err := ks.StoreKey(a.File, key, auth); err != nil {
|
||||
zeroKey(key.PrivateKey)
|
||||
return nil, a, err
|
||||
}
|
||||
return key, a, err
|
||||
}
|
||||
|
||||
func writeKeyFile(file string, content []byte) error {
|
||||
// Create the keystore directory with appropriate permissions
|
||||
// in case it is not present yet.
|
||||
const dirPerm = 0700
|
||||
if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
// Atomic write: create a temporary hidden file first
|
||||
// then move it into place. TempFile assigns mode 0600.
|
||||
f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.Write(content); err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
return os.Rename(f.Name(), file)
|
||||
}
|
||||
|
||||
// keyFileName implements the naming convention for keyfiles:
|
||||
// UTC--<created_at UTC ISO8601>-<address hex>
|
||||
func keyFileName(keyAddr common.Address) string {
|
||||
ts := time.Now().UTC()
|
||||
return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:]))
|
||||
}
|
||||
|
||||
func toISO8601(t time.Time) string {
|
||||
var tz string
|
||||
name, offset := t.Zone()
|
||||
if name == "UTC" {
|
||||
tz = "Z"
|
||||
} else {
|
||||
tz = fmt.Sprintf("%03d00", offset/3600)
|
||||
}
|
||||
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
|
||||
}
|
298
vendor/github.com/ethereum/go-ethereum/accounts/key_store_passphrase.go
generated
vendored
Normal file
298
vendor/github.com/ethereum/go-ethereum/accounts/key_store_passphrase.go
generated
vendored
Normal file
|
@ -0,0 +1,298 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
|
||||
This key store behaves as KeyStorePlain with the difference that
|
||||
the private key is encrypted and on disk uses another JSON encoding.
|
||||
|
||||
The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||
|
||||
*/
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
||||
"github.com/pborman/uuid"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
keyHeaderKDF = "scrypt"
|
||||
|
||||
// n,r,p = 2^18, 8, 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
|
||||
StandardScryptN = 1 << 18
|
||||
StandardScryptP = 1
|
||||
|
||||
// n,r,p = 2^12, 8, 6 uses 4MB memory and approx 100ms CPU time on a modern CPU.
|
||||
LightScryptN = 1 << 12
|
||||
LightScryptP = 6
|
||||
|
||||
scryptR = 8
|
||||
scryptDKLen = 32
|
||||
)
|
||||
|
||||
type keyStorePassphrase struct {
|
||||
keysDirPath string
|
||||
scryptN int
|
||||
scryptP int
|
||||
}
|
||||
|
||||
func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
|
||||
// Load the key from the keystore and decrypt its contents
|
||||
keyjson, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := DecryptKey(keyjson, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Make sure we're really operating on the requested key (no swap attacks)
|
||||
if key.Address != addr {
|
||||
return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
|
||||
keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeKeyFile(filename, keyjson)
|
||||
}
|
||||
|
||||
func (ks keyStorePassphrase) JoinPath(filename string) string {
|
||||
if filepath.IsAbs(filename) {
|
||||
return filename
|
||||
} else {
|
||||
return filepath.Join(ks.keysDirPath, filename)
|
||||
}
|
||||
}
|
||||
|
||||
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
||||
// blob that can be decrypted later on.
|
||||
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||
authArray := []byte(auth)
|
||||
salt := randentropy.GetEntropyCSPRNG(32)
|
||||
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptKey := derivedKey[:16]
|
||||
keyBytes := crypto.FromECDSA(key.PrivateKey)
|
||||
|
||||
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
|
||||
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||
|
||||
scryptParamsJSON := make(map[string]interface{}, 5)
|
||||
scryptParamsJSON["n"] = scryptN
|
||||
scryptParamsJSON["r"] = scryptR
|
||||
scryptParamsJSON["p"] = scryptP
|
||||
scryptParamsJSON["dklen"] = scryptDKLen
|
||||
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
||||
|
||||
cipherParamsJSON := cipherparamsJSON{
|
||||
IV: hex.EncodeToString(iv),
|
||||
}
|
||||
|
||||
cryptoStruct := cryptoJSON{
|
||||
Cipher: "aes-128-ctr",
|
||||
CipherText: hex.EncodeToString(cipherText),
|
||||
CipherParams: cipherParamsJSON,
|
||||
KDF: "scrypt",
|
||||
KDFParams: scryptParamsJSON,
|
||||
MAC: hex.EncodeToString(mac),
|
||||
}
|
||||
encryptedKeyJSONV3 := encryptedKeyJSONV3{
|
||||
hex.EncodeToString(key.Address[:]),
|
||||
cryptoStruct,
|
||||
key.Id.String(),
|
||||
version,
|
||||
key.WhisperEnabled,
|
||||
}
|
||||
return json.Marshal(encryptedKeyJSONV3)
|
||||
}
|
||||
|
||||
// DecryptKey decrypts a key from a json blob, returning the private key itself.
|
||||
func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
||||
// Parse the json into a simple map to fetch the key version
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(keyjson, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Depending on the version try to parse one way or another
|
||||
var (
|
||||
keyBytes, keyId []byte
|
||||
err error
|
||||
)
|
||||
if version, ok := m["version"].(string); ok && version == "1" {
|
||||
k := new(encryptedKeyJSONV1)
|
||||
if err := json.Unmarshal(keyjson, k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, keyId, err = decryptKeyV1(k, auth)
|
||||
} else {
|
||||
k := new(encryptedKeyJSONV3)
|
||||
if err := json.Unmarshal(keyjson, k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, keyId, err = decryptKeyV3(k, auth)
|
||||
}
|
||||
// Handle any decryption errors and return the key
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := crypto.ToECDSA(keyBytes)
|
||||
return &Key{
|
||||
Id: uuid.UUID(keyId),
|
||||
Address: crypto.PubkeyToAddress(key.PublicKey),
|
||||
PrivateKey: key,
|
||||
WhisperEnabled: m["whisperenabled"].(bool),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||
if keyProtected.Version != version {
|
||||
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
|
||||
}
|
||||
|
||||
if keyProtected.Crypto.Cipher != "aes-128-ctr" {
|
||||
return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher)
|
||||
}
|
||||
|
||||
keyId = uuid.Parse(keyProtected.Id)
|
||||
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||
if !bytes.Equal(calculatedMAC, mac) {
|
||||
return nil, nil, ErrDecrypt
|
||||
}
|
||||
|
||||
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return plainText, keyId, err
|
||||
}
|
||||
|
||||
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||
keyId = uuid.Parse(keyProtected.Id)
|
||||
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||
if !bytes.Equal(calculatedMAC, mac) {
|
||||
return nil, nil, ErrDecrypt
|
||||
}
|
||||
|
||||
plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return plainText, keyId, err
|
||||
}
|
||||
|
||||
func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
|
||||
authArray := []byte(auth)
|
||||
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
|
||||
|
||||
if cryptoJSON.KDF == "scrypt" {
|
||||
n := ensureInt(cryptoJSON.KDFParams["n"])
|
||||
r := ensureInt(cryptoJSON.KDFParams["r"])
|
||||
p := ensureInt(cryptoJSON.KDFParams["p"])
|
||||
return scrypt.Key(authArray, salt, n, r, p, dkLen)
|
||||
|
||||
} else if cryptoJSON.KDF == "pbkdf2" {
|
||||
c := ensureInt(cryptoJSON.KDFParams["c"])
|
||||
prf := cryptoJSON.KDFParams["prf"].(string)
|
||||
if prf != "hmac-sha256" {
|
||||
return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
|
||||
}
|
||||
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
|
||||
}
|
||||
|
||||
// TODO: can we do without this when unmarshalling dynamic JSON?
|
||||
// why do integers in KDF params end up as float64 and not int after
|
||||
// unmarshal?
|
||||
func ensureInt(x interface{}) int {
|
||||
res, ok := x.(int)
|
||||
if !ok {
|
||||
res = int(x.(float64))
|
||||
}
|
||||
return res
|
||||
}
|
62
vendor/github.com/ethereum/go-ethereum/accounts/key_store_plain.go
generated
vendored
Normal file
62
vendor/github.com/ethereum/go-ethereum/accounts/key_store_plain.go
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type keyStorePlain struct {
|
||||
keysDirPath string
|
||||
}
|
||||
|
||||
func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key, error) {
|
||||
fd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
key := new(Key)
|
||||
if err := json.NewDecoder(fd).Decode(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.Address != addr {
|
||||
return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
|
||||
content, err := json.Marshal(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeKeyFile(filename, content)
|
||||
}
|
||||
|
||||
func (ks keyStorePlain) JoinPath(filename string) string {
|
||||
if filepath.IsAbs(filename) {
|
||||
return filename
|
||||
} else {
|
||||
return filepath.Join(ks.keysDirPath, filename)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/pborman/uuid"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
|
||||
func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (Account, *Key, error) {
|
||||
key, err := decryptPreSaleKey(keyJSON, password)
|
||||
if err != nil {
|
||||
return Account{}, nil, err
|
||||
}
|
||||
key.Id = uuid.NewRandom()
|
||||
a := Account{Address: key.Address, File: keyStore.JoinPath(keyFileName(key.Address))}
|
||||
err = keyStore.StoreKey(a.File, key, password)
|
||||
return a, key, err
|
||||
}
|
||||
|
||||
func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) {
|
||||
preSaleKeyStruct := struct {
|
||||
EncSeed string
|
||||
EthAddr string
|
||||
Email string
|
||||
BtcAddr string
|
||||
}{}
|
||||
err = json.Unmarshal(fileContent, &preSaleKeyStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed)
|
||||
iv := encSeedBytes[:16]
|
||||
cipherText := encSeedBytes[16:]
|
||||
/*
|
||||
See https://github.com/ethereum/pyethsaletool
|
||||
|
||||
pyethsaletool generates the encryption key from password by
|
||||
2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:().
|
||||
16 byte key length within PBKDF2 and resulting key is used as AES key
|
||||
*/
|
||||
passBytes := []byte(password)
|
||||
derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New)
|
||||
plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ethPriv := crypto.Keccak256(plainText)
|
||||
ecKey := crypto.ToECDSA(ethPriv)
|
||||
key = &Key{
|
||||
Id: nil,
|
||||
Address: crypto.PubkeyToAddress(ecKey.PublicKey),
|
||||
PrivateKey: ecKey,
|
||||
}
|
||||
derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
|
||||
expectedAddr := preSaleKeyStruct.EthAddr
|
||||
if derivedAddr != expectedAddr {
|
||||
err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr)
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
|
||||
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
|
||||
// AES-128 is selected due to size of encryptKey.
|
||||
aesBlock, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream := cipher.NewCTR(aesBlock, iv)
|
||||
outText := make([]byte, len(inText))
|
||||
stream.XORKeyStream(outText, inText)
|
||||
return outText, err
|
||||
}
|
||||
|
||||
func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
|
||||
aesBlock, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
|
||||
paddedPlaintext := make([]byte, len(cipherText))
|
||||
decrypter.CryptBlocks(paddedPlaintext, cipherText)
|
||||
plaintext := pkcs7Unpad(paddedPlaintext)
|
||||
if plaintext == nil {
|
||||
return nil, ErrDecrypt
|
||||
}
|
||||
return plaintext, err
|
||||
}
|
||||
|
||||
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
|
||||
func pkcs7Unpad(in []byte) []byte {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
padding := in[len(in)-1]
|
||||
if int(padding) > len(in) || padding > aes.BlockSize {
|
||||
return nil
|
||||
} else if padding == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
|
||||
if in[i] != padding {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return in[:len(in)-int(padding)]
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build darwin,!ios freebsd linux,!arm64 netbsd solaris windows
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/rjeczalik/notify"
|
||||
)
|
||||
|
||||
type watcher struct {
|
||||
ac *addrCache
|
||||
starting bool
|
||||
running bool
|
||||
ev chan notify.EventInfo
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
func newWatcher(ac *addrCache) *watcher {
|
||||
return &watcher{
|
||||
ac: ac,
|
||||
ev: make(chan notify.EventInfo, 10),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// starts the watcher loop in the background.
|
||||
// Start a watcher in the background if that's not already in progress.
|
||||
// The caller must hold w.ac.mu.
|
||||
func (w *watcher) start() {
|
||||
if w.starting || w.running {
|
||||
return
|
||||
}
|
||||
w.starting = true
|
||||
go w.loop()
|
||||
}
|
||||
|
||||
func (w *watcher) close() {
|
||||
close(w.quit)
|
||||
}
|
||||
|
||||
func (w *watcher) loop() {
|
||||
defer func() {
|
||||
w.ac.mu.Lock()
|
||||
w.running = false
|
||||
w.starting = false
|
||||
w.ac.mu.Unlock()
|
||||
}()
|
||||
|
||||
err := notify.Watch(w.ac.keydir, w.ev, notify.All)
|
||||
if err != nil {
|
||||
glog.V(logger.Detail).Infof("can't watch %s: %v", w.ac.keydir, err)
|
||||
return
|
||||
}
|
||||
defer notify.Stop(w.ev)
|
||||
glog.V(logger.Detail).Infof("now watching %s", w.ac.keydir)
|
||||
defer glog.V(logger.Detail).Infof("no longer watching %s", w.ac.keydir)
|
||||
|
||||
w.ac.mu.Lock()
|
||||
w.running = true
|
||||
w.ac.mu.Unlock()
|
||||
|
||||
// Wait for file system events and reload.
|
||||
// When an event occurs, the reload call is delayed a bit so that
|
||||
// multiple events arriving quickly only cause a single reload.
|
||||
var (
|
||||
debounce = time.NewTimer(0)
|
||||
debounceDuration = 500 * time.Millisecond
|
||||
inCycle, hadEvent bool
|
||||
)
|
||||
defer debounce.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-w.quit:
|
||||
return
|
||||
case <-w.ev:
|
||||
if !inCycle {
|
||||
debounce.Reset(debounceDuration)
|
||||
inCycle = true
|
||||
} else {
|
||||
hadEvent = true
|
||||
}
|
||||
case <-debounce.C:
|
||||
w.ac.mu.Lock()
|
||||
w.ac.reload()
|
||||
w.ac.mu.Unlock()
|
||||
if hadEvent {
|
||||
debounce.Reset(debounceDuration)
|
||||
inCycle, hadEvent = true, false
|
||||
} else {
|
||||
inCycle, hadEvent = false, false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build ios linux,arm64 !darwin,!freebsd,!linux,!netbsd,!solaris,!windows
|
||||
|
||||
// This is the fallback implementation of directory watching.
|
||||
// It is used on unsupported platforms.
|
||||
|
||||
package accounts
|
||||
|
||||
type watcher struct{ running bool }
|
||||
|
||||
func newWatcher(*addrCache) *watcher { return new(watcher) }
|
||||
func (*watcher) start() {}
|
||||
func (*watcher) close() {}
|
|
@ -0,0 +1,12 @@
|
|||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile ~/.gitignore_global
|
||||
|
||||
/tmp
|
||||
*/**/*un~
|
||||
*un~
|
||||
.DS_Store
|
||||
*/**/.DS_Store
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethdb
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
|
||||
gometrics "github.com/rcrowley/go-metrics"
|
||||
)
|
||||
|
||||
var OpenFileLimit = 64
|
||||
|
||||
// cacheRatio specifies how the total alloted cache is distributed between the
|
||||
// various system databases.
|
||||
var cacheRatio = map[string]float64{
|
||||
"dapp": 0.0,
|
||||
"chaindata": 1.0,
|
||||
}
|
||||
|
||||
// handleRatio specifies how the total alloted file descriptors is distributed
|
||||
// between the various system databases.
|
||||
var handleRatio = map[string]float64{
|
||||
"dapp": 0.0,
|
||||
"chaindata": 1.0,
|
||||
}
|
||||
|
||||
type LDBDatabase struct {
|
||||
fn string // filename for reporting
|
||||
db *leveldb.DB // LevelDB instance
|
||||
|
||||
getTimer gometrics.Timer // Timer for measuring the database get request counts and latencies
|
||||
putTimer gometrics.Timer // Timer for measuring the database put request counts and latencies
|
||||
delTimer gometrics.Timer // Timer for measuring the database delete request counts and latencies
|
||||
missMeter gometrics.Meter // Meter for measuring the missed database get requests
|
||||
readMeter gometrics.Meter // Meter for measuring the database get request data usage
|
||||
writeMeter gometrics.Meter // Meter for measuring the database put request data usage
|
||||
compTimeMeter gometrics.Meter // Meter for measuring the total time spent in database compaction
|
||||
compReadMeter gometrics.Meter // Meter for measuring the data read during compaction
|
||||
compWriteMeter gometrics.Meter // Meter for measuring the data written during compaction
|
||||
|
||||
quitLock sync.Mutex // Mutex protecting the quit channel access
|
||||
quitChan chan chan error // Quit channel to stop the metrics collection before closing the database
|
||||
}
|
||||
|
||||
// NewLDBDatabase returns a LevelDB wrapped object.
|
||||
func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) {
|
||||
// Calculate the cache and file descriptor allowance for this particular database
|
||||
cache = int(float64(cache) * cacheRatio[filepath.Base(file)])
|
||||
if cache < 16 {
|
||||
cache = 16
|
||||
}
|
||||
handles = int(float64(handles) * handleRatio[filepath.Base(file)])
|
||||
if handles < 16 {
|
||||
handles = 16
|
||||
}
|
||||
glog.V(logger.Info).Infof("Alloted %dMB cache and %d file handles to %s", cache, handles, file)
|
||||
|
||||
// Open the db and recover any potential corruptions
|
||||
db, err := leveldb.OpenFile(file, &opt.Options{
|
||||
OpenFilesCacheCapacity: handles,
|
||||
BlockCacheCapacity: cache / 2 * opt.MiB,
|
||||
WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally
|
||||
})
|
||||
if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
|
||||
db, err = leveldb.RecoverFile(file, nil)
|
||||
}
|
||||
// (Re)check for errors and abort if opening of the db failed
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &LDBDatabase{
|
||||
fn: file,
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Put puts the given key / value to the queue
|
||||
func (self *LDBDatabase) Put(key []byte, value []byte) error {
|
||||
// Measure the database put latency, if requested
|
||||
if self.putTimer != nil {
|
||||
defer self.putTimer.UpdateSince(time.Now())
|
||||
}
|
||||
// Generate the data to write to disk, update the meter and write
|
||||
//value = rle.Compress(value)
|
||||
|
||||
if self.writeMeter != nil {
|
||||
self.writeMeter.Mark(int64(len(value)))
|
||||
}
|
||||
return self.db.Put(key, value, nil)
|
||||
}
|
||||
|
||||
// Get returns the given key if it's present.
|
||||
func (self *LDBDatabase) Get(key []byte) ([]byte, error) {
|
||||
// Measure the database get latency, if requested
|
||||
if self.getTimer != nil {
|
||||
defer self.getTimer.UpdateSince(time.Now())
|
||||
}
|
||||
// Retrieve the key and increment the miss counter if not found
|
||||
dat, err := self.db.Get(key, nil)
|
||||
if err != nil {
|
||||
if self.missMeter != nil {
|
||||
self.missMeter.Mark(1)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// Otherwise update the actually retrieved amount of data
|
||||
if self.readMeter != nil {
|
||||
self.readMeter.Mark(int64(len(dat)))
|
||||
}
|
||||
return dat, nil
|
||||
//return rle.Decompress(dat)
|
||||
}
|
||||
|
||||
// Delete deletes the key from the queue and database
|
||||
func (self *LDBDatabase) Delete(key []byte) error {
|
||||
// Measure the database delete latency, if requested
|
||||
if self.delTimer != nil {
|
||||
defer self.delTimer.UpdateSince(time.Now())
|
||||
}
|
||||
// Execute the actual operation
|
||||
return self.db.Delete(key, nil)
|
||||
}
|
||||
|
||||
func (self *LDBDatabase) NewIterator() iterator.Iterator {
|
||||
return self.db.NewIterator(nil, nil)
|
||||
}
|
||||
|
||||
func (self *LDBDatabase) Close() {
|
||||
// Stop the metrics collection to avoid internal database races
|
||||
self.quitLock.Lock()
|
||||
defer self.quitLock.Unlock()
|
||||
|
||||
if self.quitChan != nil {
|
||||
errc := make(chan error)
|
||||
self.quitChan <- errc
|
||||
if err := <-errc; err != nil {
|
||||
glog.V(logger.Error).Infof("metrics failure in '%s': %v\n", self.fn, err)
|
||||
}
|
||||
}
|
||||
err := self.db.Close()
|
||||
if glog.V(logger.Error) {
|
||||
if err == nil {
|
||||
glog.Infoln("closed db:", self.fn)
|
||||
} else {
|
||||
glog.Errorf("error closing db %s: %v", self.fn, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LDBDatabase) LDB() *leveldb.DB {
|
||||
return self.db
|
||||
}
|
||||
|
||||
// Meter configures the database metrics collectors and
|
||||
func (self *LDBDatabase) Meter(prefix string) {
|
||||
// Short circuit metering if the metrics system is disabled
|
||||
if !metrics.Enabled {
|
||||
return
|
||||
}
|
||||
// Initialize all the metrics collector at the requested prefix
|
||||
self.getTimer = metrics.NewTimer(prefix + "user/gets")
|
||||
self.putTimer = metrics.NewTimer(prefix + "user/puts")
|
||||
self.delTimer = metrics.NewTimer(prefix + "user/dels")
|
||||
self.missMeter = metrics.NewMeter(prefix + "user/misses")
|
||||
self.readMeter = metrics.NewMeter(prefix + "user/reads")
|
||||
self.writeMeter = metrics.NewMeter(prefix + "user/writes")
|
||||
self.compTimeMeter = metrics.NewMeter(prefix + "compact/time")
|
||||
self.compReadMeter = metrics.NewMeter(prefix + "compact/input")
|
||||
self.compWriteMeter = metrics.NewMeter(prefix + "compact/output")
|
||||
|
||||
// Create a quit channel for the periodic collector and run it
|
||||
self.quitLock.Lock()
|
||||
self.quitChan = make(chan chan error)
|
||||
self.quitLock.Unlock()
|
||||
|
||||
go self.meter(3 * time.Second)
|
||||
}
|
||||
|
||||
// meter periodically retrieves internal leveldb counters and reports them to
|
||||
// the metrics subsystem.
|
||||
//
|
||||
// This is how a stats table look like (currently):
|
||||
// Compactions
|
||||
// Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB)
|
||||
// -------+------------+---------------+---------------+---------------+---------------
|
||||
// 0 | 0 | 0.00000 | 1.27969 | 0.00000 | 12.31098
|
||||
// 1 | 85 | 109.27913 | 28.09293 | 213.92493 | 214.26294
|
||||
// 2 | 523 | 1000.37159 | 7.26059 | 66.86342 | 66.77884
|
||||
// 3 | 570 | 1113.18458 | 0.00000 | 0.00000 | 0.00000
|
||||
func (self *LDBDatabase) meter(refresh time.Duration) {
|
||||
// Create the counters to store current and previous values
|
||||
counters := make([][]float64, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
counters[i] = make([]float64, 3)
|
||||
}
|
||||
// Iterate ad infinitum and collect the stats
|
||||
for i := 1; ; i++ {
|
||||
// Retrieve the database stats
|
||||
stats, err := self.db.GetProperty("leveldb.stats")
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("failed to read database stats: %v", err)
|
||||
return
|
||||
}
|
||||
// Find the compaction table, skip the header
|
||||
lines := strings.Split(stats, "\n")
|
||||
for len(lines) > 0 && strings.TrimSpace(lines[0]) != "Compactions" {
|
||||
lines = lines[1:]
|
||||
}
|
||||
if len(lines) <= 3 {
|
||||
glog.V(logger.Error).Infof("compaction table not found")
|
||||
return
|
||||
}
|
||||
lines = lines[3:]
|
||||
|
||||
// Iterate over all the table rows, and accumulate the entries
|
||||
for j := 0; j < len(counters[i%2]); j++ {
|
||||
counters[i%2][j] = 0
|
||||
}
|
||||
for _, line := range lines {
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) != 6 {
|
||||
break
|
||||
}
|
||||
for idx, counter := range parts[3:] {
|
||||
if value, err := strconv.ParseFloat(strings.TrimSpace(counter), 64); err != nil {
|
||||
glog.V(logger.Error).Infof("compaction entry parsing failed: %v", err)
|
||||
return
|
||||
} else {
|
||||
counters[i%2][idx] += value
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update all the requested meters
|
||||
if self.compTimeMeter != nil {
|
||||
self.compTimeMeter.Mark(int64((counters[i%2][0] - counters[(i-1)%2][0]) * 1000 * 1000 * 1000))
|
||||
}
|
||||
if self.compReadMeter != nil {
|
||||
self.compReadMeter.Mark(int64((counters[i%2][1] - counters[(i-1)%2][1]) * 1024 * 1024))
|
||||
}
|
||||
if self.compWriteMeter != nil {
|
||||
self.compWriteMeter.Mark(int64((counters[i%2][2] - counters[(i-1)%2][2]) * 1024 * 1024))
|
||||
}
|
||||
// Sleep a bit, then repeat the stats collection
|
||||
select {
|
||||
case errc := <-self.quitChan:
|
||||
// Quit requesting, stop hammering the database
|
||||
errc <- nil
|
||||
return
|
||||
|
||||
case <-time.After(refresh):
|
||||
// Timeout, gather a new set of stats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this stuff and expose leveldb directly
|
||||
|
||||
func (db *LDBDatabase) NewBatch() Batch {
|
||||
return &ldbBatch{db: db.db, b: new(leveldb.Batch)}
|
||||
}
|
||||
|
||||
type ldbBatch struct {
|
||||
db *leveldb.DB
|
||||
b *leveldb.Batch
|
||||
}
|
||||
|
||||
func (b *ldbBatch) Put(key, value []byte) error {
|
||||
b.b.Put(key, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *ldbBatch) Write() error {
|
||||
return b.db.Write(b.b, nil)
|
||||
}
|
|
@ -14,19 +14,17 @@
|
|||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
Package whisper implements the Whisper PoC-1.
|
||||
package ethdb
|
||||
|
||||
(https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec)
|
||||
type Database interface {
|
||||
Put(key []byte, value []byte) error
|
||||
Get(key []byte) ([]byte, error)
|
||||
Delete(key []byte) error
|
||||
Close()
|
||||
NewBatch() Batch
|
||||
}
|
||||
|
||||
Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
|
||||
As such it may be likened and compared to both, not dissimilar to the
|
||||
matter/energy duality (apologies to physicists for the blatant abuse of a
|
||||
fundamental and beautiful natural principle).
|
||||
|
||||
Whisper is a pure identity-based messaging system. Whisper provides a low-level
|
||||
(non-application-specific) but easily-accessible API without being based upon
|
||||
or prejudiced by the low-level hardware attributes and characteristics,
|
||||
particularly the notion of singular endpoints.
|
||||
*/
|
||||
package whisper
|
||||
type Batch interface {
|
||||
Put(key, value []byte) error
|
||||
Write() error
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
/*
|
||||
* This is a test memory database. Do not use for any production it does not get persisted
|
||||
*/
|
||||
type MemDatabase struct {
|
||||
db map[string][]byte
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMemDatabase() (*MemDatabase, error) {
|
||||
return &MemDatabase{
|
||||
db: make(map[string][]byte),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *MemDatabase) Put(key []byte, value []byte) error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
db.db[string(key)] = common.CopyBytes(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MemDatabase) Set(key []byte, value []byte) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
db.Put(key, value)
|
||||
}
|
||||
|
||||
func (db *MemDatabase) Get(key []byte) ([]byte, error) {
|
||||
db.lock.RLock()
|
||||
defer db.lock.RUnlock()
|
||||
|
||||
if entry, ok := db.db[string(key)]; ok {
|
||||
return entry, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
func (db *MemDatabase) Keys() [][]byte {
|
||||
db.lock.RLock()
|
||||
defer db.lock.RUnlock()
|
||||
|
||||
keys := [][]byte{}
|
||||
for key, _ := range db.db {
|
||||
keys = append(keys, []byte(key))
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
/*
|
||||
func (db *MemDatabase) GetKeys() []*common.Key {
|
||||
data, _ := db.Get([]byte("KeyRing"))
|
||||
|
||||
return []*common.Key{common.NewKeyFromBytes(data)}
|
||||
}
|
||||
*/
|
||||
|
||||
func (db *MemDatabase) Delete(key []byte) error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
delete(db.db, string(key))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MemDatabase) Close() {}
|
||||
|
||||
func (db *MemDatabase) NewBatch() Batch {
|
||||
return &memBatch{db: db}
|
||||
}
|
||||
|
||||
type kv struct{ k, v []byte }
|
||||
|
||||
type memBatch struct {
|
||||
db *MemDatabase
|
||||
writes []kv
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (b *memBatch) Put(key, value []byte) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
b.writes = append(b.writes, kv{common.CopyBytes(key), common.CopyBytes(value)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *memBatch) Write() error {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
b.db.lock.Lock()
|
||||
defer b.db.lock.Unlock()
|
||||
|
||||
for _, kv := range b.writes {
|
||||
b.db.db[string(kv.k)] = kv.v
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package event implements an event multiplexer.
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Event is a time-tagged notification pushed to subscribers.
|
||||
type Event struct {
|
||||
Time time.Time
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// Subscription is implemented by event subscriptions.
|
||||
type Subscription interface {
|
||||
// Chan returns a channel that carries events.
|
||||
// Implementations should return the same channel
|
||||
// for any subsequent calls to Chan.
|
||||
Chan() <-chan *Event
|
||||
|
||||
// Unsubscribe stops delivery of events to a subscription.
|
||||
// The event channel is closed.
|
||||
// Unsubscribe can be called more than once.
|
||||
Unsubscribe()
|
||||
}
|
||||
|
||||
// A TypeMux dispatches events to registered receivers. Receivers can be
|
||||
// registered to handle events of certain type. Any operation
|
||||
// called after mux is stopped will return ErrMuxClosed.
|
||||
//
|
||||
// The zero value is ready to use.
|
||||
type TypeMux struct {
|
||||
mutex sync.RWMutex
|
||||
subm map[reflect.Type][]*muxsub
|
||||
stopped bool
|
||||
}
|
||||
|
||||
// ErrMuxClosed is returned when Posting on a closed TypeMux.
|
||||
var ErrMuxClosed = errors.New("event: mux closed")
|
||||
|
||||
// Subscribe creates a subscription for events of the given types. The
|
||||
// subscription's channel is closed when it is unsubscribed
|
||||
// or the mux is closed.
|
||||
func (mux *TypeMux) Subscribe(types ...interface{}) Subscription {
|
||||
sub := newsub(mux)
|
||||
mux.mutex.Lock()
|
||||
defer mux.mutex.Unlock()
|
||||
if mux.stopped {
|
||||
// set the status to closed so that calling Unsubscribe after this
|
||||
// call will short curuit
|
||||
sub.closed = true
|
||||
close(sub.postC)
|
||||
} else {
|
||||
if mux.subm == nil {
|
||||
mux.subm = make(map[reflect.Type][]*muxsub)
|
||||
}
|
||||
for _, t := range types {
|
||||
rtyp := reflect.TypeOf(t)
|
||||
oldsubs := mux.subm[rtyp]
|
||||
if find(oldsubs, sub) != -1 {
|
||||
panic(fmt.Sprintf("event: duplicate type %s in Subscribe", rtyp))
|
||||
}
|
||||
subs := make([]*muxsub, len(oldsubs)+1)
|
||||
copy(subs, oldsubs)
|
||||
subs[len(oldsubs)] = sub
|
||||
mux.subm[rtyp] = subs
|
||||
}
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
// Post sends an event to all receivers registered for the given type.
|
||||
// It returns ErrMuxClosed if the mux has been stopped.
|
||||
func (mux *TypeMux) Post(ev interface{}) error {
|
||||
event := &Event{
|
||||
Time: time.Now(),
|
||||
Data: ev,
|
||||
}
|
||||
rtyp := reflect.TypeOf(ev)
|
||||
mux.mutex.RLock()
|
||||
if mux.stopped {
|
||||
mux.mutex.RUnlock()
|
||||
return ErrMuxClosed
|
||||
}
|
||||
subs := mux.subm[rtyp]
|
||||
mux.mutex.RUnlock()
|
||||
for _, sub := range subs {
|
||||
sub.deliver(event)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop closes a mux. The mux can no longer be used.
|
||||
// Future Post calls will fail with ErrMuxClosed.
|
||||
// Stop blocks until all current deliveries have finished.
|
||||
func (mux *TypeMux) Stop() {
|
||||
mux.mutex.Lock()
|
||||
for _, subs := range mux.subm {
|
||||
for _, sub := range subs {
|
||||
sub.closewait()
|
||||
}
|
||||
}
|
||||
mux.subm = nil
|
||||
mux.stopped = true
|
||||
mux.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (mux *TypeMux) del(s *muxsub) {
|
||||
mux.mutex.Lock()
|
||||
for typ, subs := range mux.subm {
|
||||
if pos := find(subs, s); pos >= 0 {
|
||||
if len(subs) == 1 {
|
||||
delete(mux.subm, typ)
|
||||
} else {
|
||||
mux.subm[typ] = posdelete(subs, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mux.mutex.Unlock()
|
||||
}
|
||||
|
||||
func find(slice []*muxsub, item *muxsub) int {
|
||||
for i, v := range slice {
|
||||
if v == item {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func posdelete(slice []*muxsub, pos int) []*muxsub {
|
||||
news := make([]*muxsub, len(slice)-1)
|
||||
copy(news[:pos], slice[:pos])
|
||||
copy(news[pos:], slice[pos+1:])
|
||||
return news
|
||||
}
|
||||
|
||||
type muxsub struct {
|
||||
mux *TypeMux
|
||||
created time.Time
|
||||
closeMu sync.Mutex
|
||||
closing chan struct{}
|
||||
closed bool
|
||||
|
||||
// these two are the same channel. they are stored separately so
|
||||
// postC can be set to nil without affecting the return value of
|
||||
// Chan.
|
||||
postMu sync.RWMutex
|
||||
readC <-chan *Event
|
||||
postC chan<- *Event
|
||||
}
|
||||
|
||||
func newsub(mux *TypeMux) *muxsub {
|
||||
c := make(chan *Event)
|
||||
return &muxsub{
|
||||
mux: mux,
|
||||
created: time.Now(),
|
||||
readC: c,
|
||||
postC: c,
|
||||
closing: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *muxsub) Chan() <-chan *Event {
|
||||
return s.readC
|
||||
}
|
||||
|
||||
func (s *muxsub) Unsubscribe() {
|
||||
s.mux.del(s)
|
||||
s.closewait()
|
||||
}
|
||||
|
||||
func (s *muxsub) closewait() {
|
||||
s.closeMu.Lock()
|
||||
defer s.closeMu.Unlock()
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
close(s.closing)
|
||||
s.closed = true
|
||||
|
||||
s.postMu.Lock()
|
||||
close(s.postC)
|
||||
s.postC = nil
|
||||
s.postMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *muxsub) deliver(event *Event) {
|
||||
// Short circuit delivery if stale event
|
||||
if s.created.After(event.Time) {
|
||||
return
|
||||
}
|
||||
// Otherwise deliver the event
|
||||
s.postMu.RLock()
|
||||
defer s.postMu.RUnlock()
|
||||
|
||||
select {
|
||||
case s.postC <- event:
|
||||
case <-s.closing:
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package debug interfaces Go runtime debugging facilities.
|
||||
// This package is mostly glue code making these facilities available
|
||||
// through the CLI and RPC subsystem. If you want to use them from Go code,
|
||||
// use package runtime instead.
|
||||
package debug
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
// Handler is the global debugging handler.
|
||||
var Handler = new(HandlerT)
|
||||
|
||||
// HandlerT implements the debugging API.
|
||||
// Do not create values of this type, use the one
|
||||
// in the Handler variable instead.
|
||||
type HandlerT struct {
|
||||
mu sync.Mutex
|
||||
cpuW io.WriteCloser
|
||||
cpuFile string
|
||||
traceW io.WriteCloser
|
||||
traceFile string
|
||||
}
|
||||
|
||||
// Verbosity sets the glog verbosity ceiling.
|
||||
// The verbosity of individual packages and source files
|
||||
// can be raised using Vmodule.
|
||||
func (*HandlerT) Verbosity(level int) {
|
||||
glog.SetV(level)
|
||||
}
|
||||
|
||||
// Vmodule sets the glog verbosity pattern. See package
|
||||
// glog for details on pattern syntax.
|
||||
func (*HandlerT) Vmodule(pattern string) error {
|
||||
return glog.GetVModule().Set(pattern)
|
||||
}
|
||||
|
||||
// BacktraceAt sets the glog backtrace location.
|
||||
// See package glog for details on pattern syntax.
|
||||
func (*HandlerT) BacktraceAt(location string) error {
|
||||
return glog.GetTraceLocation().Set(location)
|
||||
}
|
||||
|
||||
// MemStats returns detailed runtime memory statistics.
|
||||
func (*HandlerT) MemStats() *runtime.MemStats {
|
||||
s := new(runtime.MemStats)
|
||||
runtime.ReadMemStats(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// GcStats returns GC statistics.
|
||||
func (*HandlerT) GcStats() *debug.GCStats {
|
||||
s := new(debug.GCStats)
|
||||
debug.ReadGCStats(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// CpuProfile turns on CPU profiling for nsec seconds and writes
|
||||
// profile data to file.
|
||||
func (h *HandlerT) CpuProfile(file string, nsec uint) error {
|
||||
if err := h.StartCPUProfile(file); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(time.Duration(nsec) * time.Second)
|
||||
h.StopCPUProfile()
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartCPUProfile turns on CPU profiling, writing to the given file.
|
||||
func (h *HandlerT) StartCPUProfile(file string) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.cpuW != nil {
|
||||
return errors.New("CPU profiling already in progress")
|
||||
}
|
||||
f, err := os.Create(expandHome(file))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
h.cpuW = f
|
||||
h.cpuFile = file
|
||||
glog.V(logger.Info).Infoln("CPU profiling started, writing to", h.cpuFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopCPUProfile stops an ongoing CPU profile.
|
||||
func (h *HandlerT) StopCPUProfile() error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
pprof.StopCPUProfile()
|
||||
if h.cpuW == nil {
|
||||
return errors.New("CPU profiling not in progress")
|
||||
}
|
||||
glog.V(logger.Info).Infoln("done writing CPU profile to", h.cpuFile)
|
||||
h.cpuW.Close()
|
||||
h.cpuW = nil
|
||||
h.cpuFile = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// GoTrace turns on tracing for nsec seconds and writes
|
||||
// trace data to file.
|
||||
func (h *HandlerT) GoTrace(file string, nsec uint) error {
|
||||
if err := h.StartGoTrace(file); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(time.Duration(nsec) * time.Second)
|
||||
h.StopGoTrace()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockProfile turns on CPU profiling for nsec seconds and writes
|
||||
// profile data to file. It uses a profile rate of 1 for most accurate
|
||||
// information. If a different rate is desired, set the rate
|
||||
// and write the profile manually.
|
||||
func (*HandlerT) BlockProfile(file string, nsec uint) error {
|
||||
runtime.SetBlockProfileRate(1)
|
||||
time.Sleep(time.Duration(nsec) * time.Second)
|
||||
defer runtime.SetBlockProfileRate(0)
|
||||
return writeProfile("block", file)
|
||||
}
|
||||
|
||||
// SetBlockProfileRate sets the rate of goroutine block profile data collection.
|
||||
// rate 0 disables block profiling.
|
||||
func (*HandlerT) SetBlockProfileRate(rate int) {
|
||||
runtime.SetBlockProfileRate(rate)
|
||||
}
|
||||
|
||||
// WriteBlockProfile writes a goroutine blocking profile to the given file.
|
||||
func (*HandlerT) WriteBlockProfile(file string) error {
|
||||
return writeProfile("block", file)
|
||||
}
|
||||
|
||||
// WriteMemProfile writes an allocation profile to the given file.
|
||||
// Note that the profiling rate cannot be set through the API,
|
||||
// it must be set on the command line.
|
||||
func (*HandlerT) WriteMemProfile(file string) error {
|
||||
return writeProfile("heap", file)
|
||||
}
|
||||
|
||||
// Stacks returns a printed representation of the stacks of all goroutines.
|
||||
func (*HandlerT) Stacks() string {
|
||||
buf := make([]byte, 1024*1024)
|
||||
buf = buf[:runtime.Stack(buf, true)]
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func writeProfile(name, file string) error {
|
||||
p := pprof.Lookup(name)
|
||||
glog.V(logger.Info).Infof("writing %d %s profile records to %s", p.Count(), name, file)
|
||||
f, err := os.Create(expandHome(file))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return p.WriteTo(f, 0)
|
||||
}
|
||||
|
||||
// expands home directory in file paths.
|
||||
// ~someuser/tmp will not be expanded.
|
||||
func expandHome(p string) string {
|
||||
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
if usr, err := user.Current(); err == nil {
|
||||
home = usr.HomeDir
|
||||
}
|
||||
}
|
||||
if home != "" {
|
||||
p = home + p[1:]
|
||||
}
|
||||
}
|
||||
return filepath.Clean(p)
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package debug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"runtime"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
var (
|
||||
verbosityFlag = cli.GenericFlag{
|
||||
Name: "verbosity",
|
||||
Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=core, 5=debug, 6=detail",
|
||||
Value: glog.GetVerbosity(),
|
||||
}
|
||||
vmoduleFlag = cli.GenericFlag{
|
||||
Name: "vmodule",
|
||||
Usage: "Per-module verbosity: comma-separated list of <pattern>=<level> (e.g. eth/*=6,p2p=5)",
|
||||
Value: glog.GetVModule(),
|
||||
}
|
||||
backtraceAtFlag = cli.GenericFlag{
|
||||
Name: "backtrace",
|
||||
Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")",
|
||||
Value: glog.GetTraceLocation(),
|
||||
}
|
||||
pprofFlag = cli.BoolFlag{
|
||||
Name: "pprof",
|
||||
Usage: "Enable the pprof HTTP server",
|
||||
}
|
||||
pprofPortFlag = cli.IntFlag{
|
||||
Name: "pprofport",
|
||||
Usage: "pprof HTTP server listening port",
|
||||
Value: 6060,
|
||||
}
|
||||
memprofilerateFlag = cli.IntFlag{
|
||||
Name: "memprofilerate",
|
||||
Usage: "Turn on memory profiling with the given rate",
|
||||
Value: runtime.MemProfileRate,
|
||||
}
|
||||
blockprofilerateFlag = cli.IntFlag{
|
||||
Name: "blockprofilerate",
|
||||
Usage: "Turn on block profiling with the given rate",
|
||||
}
|
||||
cpuprofileFlag = cli.StringFlag{
|
||||
Name: "cpuprofile",
|
||||
Usage: "Write CPU profile to the given file",
|
||||
}
|
||||
traceFlag = cli.StringFlag{
|
||||
Name: "trace",
|
||||
Usage: "Write execution trace to the given file",
|
||||
}
|
||||
)
|
||||
|
||||
// Flags holds all command-line flags required for debugging.
|
||||
var Flags = []cli.Flag{
|
||||
verbosityFlag, vmoduleFlag, backtraceAtFlag,
|
||||
pprofFlag, pprofPortFlag,
|
||||
memprofilerateFlag, blockprofilerateFlag, cpuprofileFlag, traceFlag,
|
||||
}
|
||||
|
||||
// Setup initializes profiling and logging based on the CLI flags.
|
||||
// It should be called as early as possible in the program.
|
||||
func Setup(ctx *cli.Context) error {
|
||||
// logging
|
||||
glog.CopyStandardLogTo("INFO")
|
||||
glog.SetToStderr(true)
|
||||
|
||||
// profiling, tracing
|
||||
runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name)
|
||||
Handler.SetBlockProfileRate(ctx.GlobalInt(blockprofilerateFlag.Name))
|
||||
if traceFile := ctx.GlobalString(traceFlag.Name); traceFile != "" {
|
||||
if err := Handler.StartGoTrace(traceFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cpuFile := ctx.GlobalString(cpuprofileFlag.Name); cpuFile != "" {
|
||||
if err := Handler.StartCPUProfile(cpuFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// pprof server
|
||||
if ctx.GlobalBool(pprofFlag.Name) {
|
||||
address := fmt.Sprintf("127.0.0.1:%d", ctx.GlobalInt(pprofPortFlag.Name))
|
||||
go func() {
|
||||
glog.V(logger.Info).Infof("starting pprof server at http://%s/debug/pprof", address)
|
||||
glog.Errorln(http.ListenAndServe(address, nil))
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exit stops all running profiles, flushing their output to the
|
||||
// respective file.
|
||||
func Exit() {
|
||||
Handler.StopCPUProfile()
|
||||
Handler.StopGoTrace()
|
||||
}
|
27
vendor/github.com/ethereum/go-ethereum/internal/debug/loudpanic.go
generated
vendored
Normal file
27
vendor/github.com/ethereum/go-ethereum/internal/debug/loudpanic.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build go1.6
|
||||
|
||||
package debug
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
// LoudPanic panics in a way that gets all goroutine stacks printed on stderr.
|
||||
func LoudPanic(x interface{}) {
|
||||
debug.SetTraceback("all")
|
||||
panic(x)
|
||||
}
|
24
vendor/github.com/ethereum/go-ethereum/internal/debug/loudpanic_fallback.go
generated
vendored
Normal file
24
vendor/github.com/ethereum/go-ethereum/internal/debug/loudpanic_fallback.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build !go1.6
|
||||
|
||||
package debug
|
||||
|
||||
// LoudPanic panics in a way that gets all goroutine stacks printed on stderr.
|
||||
func LoudPanic(x interface{}) {
|
||||
panic(x)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//+build go1.5
|
||||
|
||||
package debug
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"runtime/trace"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
// StartGoTrace turns on tracing, writing to the given file.
|
||||
func (h *HandlerT) StartGoTrace(file string) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.traceW != nil {
|
||||
return errors.New("trace already in progress")
|
||||
}
|
||||
f, err := os.Create(expandHome(file))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := trace.Start(f); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
h.traceW = f
|
||||
h.traceFile = file
|
||||
glog.V(logger.Info).Infoln("trace started, writing to", h.traceFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopTrace stops an ongoing trace.
|
||||
func (h *HandlerT) StopGoTrace() error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
trace.Stop()
|
||||
if h.traceW == nil {
|
||||
return errors.New("trace not in progress")
|
||||
}
|
||||
glog.V(logger.Info).Infoln("done writing trace to", h.traceFile)
|
||||
h.traceW.Close()
|
||||
h.traceW = nil
|
||||
h.traceFile = ""
|
||||
return nil
|
||||
}
|
31
vendor/github.com/ethereum/go-ethereum/internal/debug/trace_fallback.go
generated
vendored
Normal file
31
vendor/github.com/ethereum/go-ethereum/internal/debug/trace_fallback.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//+build !go1.5
|
||||
|
||||
// no-op implementation of tracing methods for Go < 1.5.
|
||||
|
||||
package debug
|
||||
|
||||
import "errors"
|
||||
|
||||
func (*HandlerT) StartGoTrace(string) error {
|
||||
return errors.New("tracing is not supported on Go < 1.5")
|
||||
}
|
||||
|
||||
func (*HandlerT) StopGoTrace() error {
|
||||
return errors.New("tracing is not supported on Go < 1.5")
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/rcrowley/go-metrics"
|
||||
)
|
||||
|
||||
// PrivateAdminAPI is the collection of administrative API methods exposed only
|
||||
// over a secure RPC channel.
|
||||
type PrivateAdminAPI struct {
|
||||
node *Node // Node interfaced by this API
|
||||
}
|
||||
|
||||
// NewPrivateAdminAPI creates a new API definition for the private admin methods
|
||||
// of the node itself.
|
||||
func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI {
|
||||
return &PrivateAdminAPI{node: node}
|
||||
}
|
||||
|
||||
// AddPeer requests connecting to a remote node, and also maintaining the new
|
||||
// connection at all times, even reconnecting if it is lost.
|
||||
func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
|
||||
// Make sure the server is running, fail otherwise
|
||||
server := api.node.Server()
|
||||
if server == nil {
|
||||
return false, ErrNodeStopped
|
||||
}
|
||||
// Try to add the url as a static peer and return
|
||||
node, err := discover.ParseNode(url)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid enode: %v", err)
|
||||
}
|
||||
server.AddPeer(node)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// StartRPC starts the HTTP RPC API server.
|
||||
func (api *PrivateAdminAPI) StartRPC(host *string, port *rpc.HexNumber, cors *string, apis *string) (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.httpHandler != nil {
|
||||
return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint)
|
||||
}
|
||||
|
||||
if host == nil {
|
||||
h := common.DefaultHTTPHost
|
||||
if api.node.httpHost != "" {
|
||||
h = api.node.httpHost
|
||||
}
|
||||
host = &h
|
||||
}
|
||||
if port == nil {
|
||||
port = rpc.NewHexNumber(api.node.httpPort)
|
||||
}
|
||||
if cors == nil {
|
||||
cors = &api.node.httpCors
|
||||
}
|
||||
|
||||
modules := api.node.httpWhitelist
|
||||
if apis != nil {
|
||||
modules = nil
|
||||
for _, m := range strings.Split(*apis, ",") {
|
||||
modules = append(modules, strings.TrimSpace(m))
|
||||
}
|
||||
}
|
||||
|
||||
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, port.Int()), api.node.rpcAPIs, modules, *cors); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// StopRPC terminates an already running HTTP RPC API endpoint.
|
||||
func (api *PrivateAdminAPI) StopRPC() (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.httpHandler == nil {
|
||||
return false, fmt.Errorf("HTTP RPC not running")
|
||||
}
|
||||
api.node.stopHTTP()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// StartWS starts the websocket RPC API server.
|
||||
func (api *PrivateAdminAPI) StartWS(host *string, port *rpc.HexNumber, allowedOrigins *string, apis *string) (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.wsHandler != nil {
|
||||
return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint)
|
||||
}
|
||||
|
||||
if host == nil {
|
||||
h := common.DefaultWSHost
|
||||
if api.node.wsHost != "" {
|
||||
h = api.node.wsHost
|
||||
}
|
||||
host = &h
|
||||
}
|
||||
if port == nil {
|
||||
port = rpc.NewHexNumber(api.node.wsPort)
|
||||
}
|
||||
if allowedOrigins == nil {
|
||||
allowedOrigins = &api.node.wsOrigins
|
||||
}
|
||||
|
||||
modules := api.node.wsWhitelist
|
||||
if apis != nil {
|
||||
modules = nil
|
||||
for _, m := range strings.Split(*apis, ",") {
|
||||
modules = append(modules, strings.TrimSpace(m))
|
||||
}
|
||||
}
|
||||
|
||||
if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, port.Int()), api.node.rpcAPIs, modules, *allowedOrigins); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// StopRPC terminates an already running websocket RPC API endpoint.
|
||||
func (api *PrivateAdminAPI) StopWS() (bool, error) {
|
||||
api.node.lock.Lock()
|
||||
defer api.node.lock.Unlock()
|
||||
|
||||
if api.node.wsHandler == nil {
|
||||
return false, fmt.Errorf("WebSocket RPC not running")
|
||||
}
|
||||
api.node.stopWS()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// PublicAdminAPI is the collection of administrative API methods exposed over
|
||||
// both secure and unsecure RPC channels.
|
||||
type PublicAdminAPI struct {
|
||||
node *Node // Node interfaced by this API
|
||||
}
|
||||
|
||||
// NewPublicAdminAPI creates a new API definition for the public admin methods
|
||||
// of the node itself.
|
||||
func NewPublicAdminAPI(node *Node) *PublicAdminAPI {
|
||||
return &PublicAdminAPI{node: node}
|
||||
}
|
||||
|
||||
// Peers retrieves all the information we know about each individual peer at the
|
||||
// protocol granularity.
|
||||
func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) {
|
||||
server := api.node.Server()
|
||||
if server == nil {
|
||||
return nil, ErrNodeStopped
|
||||
}
|
||||
return server.PeersInfo(), nil
|
||||
}
|
||||
|
||||
// NodeInfo retrieves all the information we know about the host node at the
|
||||
// protocol granularity.
|
||||
func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
|
||||
server := api.node.Server()
|
||||
if server == nil {
|
||||
return nil, ErrNodeStopped
|
||||
}
|
||||
return server.NodeInfo(), nil
|
||||
}
|
||||
|
||||
// Datadir retrieves the current data directory the node is using.
|
||||
func (api *PublicAdminAPI) Datadir() string {
|
||||
return api.node.DataDir()
|
||||
}
|
||||
|
||||
// PublicDebugAPI is the collection of debugging related API methods exposed over
|
||||
// both secure and unsecure RPC channels.
|
||||
type PublicDebugAPI struct {
|
||||
node *Node // Node interfaced by this API
|
||||
}
|
||||
|
||||
// NewPublicDebugAPI creates a new API definition for the public debug methods
|
||||
// of the node itself.
|
||||
func NewPublicDebugAPI(node *Node) *PublicDebugAPI {
|
||||
return &PublicDebugAPI{node: node}
|
||||
}
|
||||
|
||||
// Metrics retrieves all the known system metric collected by the node.
|
||||
func (api *PublicDebugAPI) Metrics(raw bool) (map[string]interface{}, error) {
|
||||
// Create a rate formatter
|
||||
units := []string{"", "K", "M", "G", "T", "E", "P"}
|
||||
round := func(value float64, prec int) string {
|
||||
unit := 0
|
||||
for value >= 1000 {
|
||||
unit, value, prec = unit+1, value/1000, 2
|
||||
}
|
||||
return fmt.Sprintf(fmt.Sprintf("%%.%df%s", prec, units[unit]), value)
|
||||
}
|
||||
format := func(total float64, rate float64) string {
|
||||
return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2))
|
||||
}
|
||||
// Iterate over all the metrics, and just dump for now
|
||||
counters := make(map[string]interface{})
|
||||
metrics.DefaultRegistry.Each(func(name string, metric interface{}) {
|
||||
// Create or retrieve the counter hierarchy for this metric
|
||||
root, parts := counters, strings.Split(name, "/")
|
||||
for _, part := range parts[:len(parts)-1] {
|
||||
if _, ok := root[part]; !ok {
|
||||
root[part] = make(map[string]interface{})
|
||||
}
|
||||
root = root[part].(map[string]interface{})
|
||||
}
|
||||
name = parts[len(parts)-1]
|
||||
|
||||
// Fill the counter with the metric details, formatting if requested
|
||||
if raw {
|
||||
switch metric := metric.(type) {
|
||||
case metrics.Meter:
|
||||
root[name] = map[string]interface{}{
|
||||
"AvgRate01Min": metric.Rate1(),
|
||||
"AvgRate05Min": metric.Rate5(),
|
||||
"AvgRate15Min": metric.Rate15(),
|
||||
"MeanRate": metric.RateMean(),
|
||||
"Overall": float64(metric.Count()),
|
||||
}
|
||||
|
||||
case metrics.Timer:
|
||||
root[name] = map[string]interface{}{
|
||||
"AvgRate01Min": metric.Rate1(),
|
||||
"AvgRate05Min": metric.Rate5(),
|
||||
"AvgRate15Min": metric.Rate15(),
|
||||
"MeanRate": metric.RateMean(),
|
||||
"Overall": float64(metric.Count()),
|
||||
"Percentiles": map[string]interface{}{
|
||||
"5": metric.Percentile(0.05),
|
||||
"20": metric.Percentile(0.2),
|
||||
"50": metric.Percentile(0.5),
|
||||
"80": metric.Percentile(0.8),
|
||||
"95": metric.Percentile(0.95),
|
||||
},
|
||||
}
|
||||
|
||||
default:
|
||||
root[name] = "Unknown metric type"
|
||||
}
|
||||
} else {
|
||||
switch metric := metric.(type) {
|
||||
case metrics.Meter:
|
||||
root[name] = map[string]interface{}{
|
||||
"Avg01Min": format(metric.Rate1()*60, metric.Rate1()),
|
||||
"Avg05Min": format(metric.Rate5()*300, metric.Rate5()),
|
||||
"Avg15Min": format(metric.Rate15()*900, metric.Rate15()),
|
||||
"Overall": format(float64(metric.Count()), metric.RateMean()),
|
||||
}
|
||||
|
||||
case metrics.Timer:
|
||||
root[name] = map[string]interface{}{
|
||||
"Avg01Min": format(metric.Rate1()*60, metric.Rate1()),
|
||||
"Avg05Min": format(metric.Rate5()*300, metric.Rate5()),
|
||||
"Avg15Min": format(metric.Rate15()*900, metric.Rate15()),
|
||||
"Overall": format(float64(metric.Count()), metric.RateMean()),
|
||||
"Maximum": time.Duration(metric.Max()).String(),
|
||||
"Minimum": time.Duration(metric.Min()).String(),
|
||||
"Percentiles": map[string]interface{}{
|
||||
"5": time.Duration(metric.Percentile(0.05)).String(),
|
||||
"20": time.Duration(metric.Percentile(0.2)).String(),
|
||||
"50": time.Duration(metric.Percentile(0.5)).String(),
|
||||
"80": time.Duration(metric.Percentile(0.8)).String(),
|
||||
"95": time.Duration(metric.Percentile(0.95)).String(),
|
||||
},
|
||||
}
|
||||
|
||||
default:
|
||||
root[name] = "Unknown metric type"
|
||||
}
|
||||
}
|
||||
})
|
||||
return counters, nil
|
||||
}
|
||||
|
||||
// PublicWeb3API offers helper utils
|
||||
type PublicWeb3API struct {
|
||||
stack *Node
|
||||
}
|
||||
|
||||
// NewPublicWeb3API creates a new Web3Service instance
|
||||
func NewPublicWeb3API(stack *Node) *PublicWeb3API {
|
||||
return &PublicWeb3API{stack}
|
||||
}
|
||||
|
||||
// ClientVersion returns the node name
|
||||
func (s *PublicWeb3API) ClientVersion() string {
|
||||
return s.stack.Server().Name
|
||||
}
|
||||
|
||||
// Sha3 applies the ethereum sha3 implementation on the input.
|
||||
// It assumes the input is hex encoded.
|
||||
func (s *PublicWeb3API) Sha3(input string) string {
|
||||
return common.ToHex(crypto.Keccak256(common.FromHex(input)))
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
)
|
||||
|
||||
var (
|
||||
datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key
|
||||
datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list
|
||||
datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list
|
||||
datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos
|
||||
)
|
||||
|
||||
// Config represents a small collection of configuration values to fine tune the
|
||||
// P2P network layer of a protocol stack. These values can be further extended by
|
||||
// all registered services.
|
||||
type Config struct {
|
||||
// DataDir is the file system folder the node should use for any data storage
|
||||
// requirements. The configured data directory will not be directly shared with
|
||||
// registered services, instead those can use utility methods to create/access
|
||||
// databases or flat files. This enables ephemeral nodes which can fully reside
|
||||
// in memory.
|
||||
DataDir string
|
||||
|
||||
// IPCPath is the requested location to place the IPC endpoint. If the path is
|
||||
// a simple file name, it is placed inside the data directory (or on the root
|
||||
// pipe path on Windows), whereas if it's a resolvable path name (absolute or
|
||||
// relative), then that specific path is enforced. An empty path disables IPC.
|
||||
IPCPath string
|
||||
|
||||
// This field should be a valid secp256k1 private key that will be used for both
|
||||
// remote peer identification as well as network traffic encryption. If no key
|
||||
// is configured, the preset one is loaded from the data dir, generating it if
|
||||
// needed.
|
||||
PrivateKey *ecdsa.PrivateKey
|
||||
|
||||
// Name sets the node name of this server. Use common.MakeName to create a name
|
||||
// that follows existing conventions.
|
||||
Name string
|
||||
|
||||
// NoDiscovery specifies whether the peer discovery mechanism should be started
|
||||
// or not. Disabling is usually useful for protocol debugging (manual topology).
|
||||
NoDiscovery bool
|
||||
|
||||
// Bootstrap nodes used to establish connectivity with the rest of the network.
|
||||
BootstrapNodes []*discover.Node
|
||||
|
||||
// Network interface address on which the node should listen for inbound peers.
|
||||
ListenAddr string
|
||||
|
||||
// If set to a non-nil value, the given NAT port mapper is used to make the
|
||||
// listening port available to the Internet.
|
||||
NAT nat.Interface
|
||||
|
||||
// If Dialer is set to a non-nil value, the given Dialer is used to dial outbound
|
||||
// peer connections.
|
||||
Dialer *net.Dialer
|
||||
|
||||
// If NoDial is true, the node will not dial any peers.
|
||||
NoDial bool
|
||||
|
||||
// MaxPeers is the maximum number of peers that can be connected. If this is
|
||||
// set to zero, then only the configured static and trusted peers can connect.
|
||||
MaxPeers int
|
||||
|
||||
// MaxPendingPeers is the maximum number of peers that can be pending in the
|
||||
// handshake phase, counted separately for inbound and outbound connections.
|
||||
// Zero defaults to preset values.
|
||||
MaxPendingPeers int
|
||||
|
||||
// HTTPHost is the host interface on which to start the HTTP RPC server. If this
|
||||
// field is empty, no HTTP API endpoint will be started.
|
||||
HTTPHost string
|
||||
|
||||
// HTTPPort is the TCP port number on which to start the HTTP RPC server. The
|
||||
// default zero value is/ valid and will pick a port number randomly (useful
|
||||
// for ephemeral nodes).
|
||||
HTTPPort int
|
||||
|
||||
// HTTPCors is the Cross-Origin Resource Sharing header to send to requesting
|
||||
// clients. Please be aware that CORS is a browser enforced security, it's fully
|
||||
// useless for custom HTTP clients.
|
||||
HTTPCors string
|
||||
|
||||
// HTTPModules is a list of API modules to expose via the HTTP RPC interface.
|
||||
// If the module list is empty, all RPC API endpoints designated public will be
|
||||
// exposed.
|
||||
HTTPModules []string
|
||||
|
||||
// WSHost is the host interface on which to start the websocket RPC server. If
|
||||
// this field is empty, no websocket API endpoint will be started.
|
||||
WSHost string
|
||||
|
||||
// WSPort is the TCP port number on which to start the websocket RPC server. The
|
||||
// default zero value is/ valid and will pick a port number randomly (useful for
|
||||
// ephemeral nodes).
|
||||
WSPort int
|
||||
|
||||
// WSOrigins is the list of domain to accept websocket requests from. Please be
|
||||
// aware that the server can only act upon the HTTP request the client sends and
|
||||
// cannot verify the validity of the request header.
|
||||
WSOrigins string
|
||||
|
||||
// WSModules is a list of API modules to expose via the websocket RPC interface.
|
||||
// If the module list is empty, all RPC API endpoints designated public will be
|
||||
// exposed.
|
||||
WSModules []string
|
||||
}
|
||||
|
||||
// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
|
||||
// account the set data folders as well as the designated platform we're currently
|
||||
// running on.
|
||||
func (c *Config) IPCEndpoint() string {
|
||||
// Short circuit if IPC has not been enabled
|
||||
if c.IPCPath == "" {
|
||||
return ""
|
||||
}
|
||||
// On windows we can only use plain top-level pipes
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) {
|
||||
return c.IPCPath
|
||||
}
|
||||
return `\\.\pipe\` + c.IPCPath
|
||||
}
|
||||
// Resolve names into the data directory full paths otherwise
|
||||
if filepath.Base(c.IPCPath) == c.IPCPath {
|
||||
if c.DataDir == "" {
|
||||
return filepath.Join(os.TempDir(), c.IPCPath)
|
||||
}
|
||||
return filepath.Join(c.DataDir, c.IPCPath)
|
||||
}
|
||||
return c.IPCPath
|
||||
}
|
||||
|
||||
// DefaultIPCEndpoint returns the IPC path used by default.
|
||||
func DefaultIPCEndpoint() string {
|
||||
config := &Config{DataDir: common.DefaultDataDir(), IPCPath: common.DefaultIPCSocket}
|
||||
return config.IPCEndpoint()
|
||||
}
|
||||
|
||||
// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface
|
||||
// and port parameters.
|
||||
func (c *Config) HTTPEndpoint() string {
|
||||
if c.HTTPHost == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort)
|
||||
}
|
||||
|
||||
// DefaultHTTPEndpoint returns the HTTP endpoint used by default.
|
||||
func DefaultHTTPEndpoint() string {
|
||||
config := &Config{HTTPHost: common.DefaultHTTPHost, HTTPPort: common.DefaultHTTPPort}
|
||||
return config.HTTPEndpoint()
|
||||
}
|
||||
|
||||
// WSEndpoint resolves an websocket endpoint based on the configured host interface
|
||||
// and port parameters.
|
||||
func (c *Config) WSEndpoint() string {
|
||||
if c.WSHost == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort)
|
||||
}
|
||||
|
||||
// DefaultWSEndpoint returns the websocket endpoint used by default.
|
||||
func DefaultWSEndpoint() string {
|
||||
config := &Config{WSHost: common.DefaultWSHost, WSPort: common.DefaultWSPort}
|
||||
return config.WSEndpoint()
|
||||
}
|
||||
|
||||
// NodeKey retrieves the currently configured private key of the node, checking
|
||||
// first any manually set key, falling back to the one found in the configured
|
||||
// data folder. If no key can be found, a new one is generated.
|
||||
func (c *Config) NodeKey() *ecdsa.PrivateKey {
|
||||
// Use any specifically configured key
|
||||
if c.PrivateKey != nil {
|
||||
return c.PrivateKey
|
||||
}
|
||||
// Generate ephemeral key if no datadir is being used
|
||||
if c.DataDir == "" {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to generate ephemeral node key: %v", err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
// Fall back to persistent key from the data directory
|
||||
keyfile := filepath.Join(c.DataDir, datadirPrivateKey)
|
||||
if key, err := crypto.LoadECDSA(keyfile); err == nil {
|
||||
return key
|
||||
}
|
||||
// No persistent key found, generate and store a new one
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to generate node key: %v", err)
|
||||
}
|
||||
if err := crypto.SaveECDSA(keyfile, key); err != nil {
|
||||
glog.V(logger.Error).Infof("Failed to persist node key: %v", err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// StaticNodes returns a list of node enode URLs configured as static nodes.
|
||||
func (c *Config) StaticNodes() []*discover.Node {
|
||||
return c.parsePersistentNodes(datadirStaticNodes)
|
||||
}
|
||||
|
||||
// TrusterNodes returns a list of node enode URLs configured as trusted nodes.
|
||||
func (c *Config) TrusterNodes() []*discover.Node {
|
||||
return c.parsePersistentNodes(datadirTrustedNodes)
|
||||
}
|
||||
|
||||
// parsePersistentNodes parses a list of discovery node URLs loaded from a .json
|
||||
// file from within the data directory.
|
||||
func (c *Config) parsePersistentNodes(file string) []*discover.Node {
|
||||
// Short circuit if no node config is present
|
||||
if c.DataDir == "" {
|
||||
return nil
|
||||
}
|
||||
path := filepath.Join(c.DataDir, file)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return nil
|
||||
}
|
||||
// Load the nodes from the config file
|
||||
blob, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("Failed to access nodes: %v", err)
|
||||
return nil
|
||||
}
|
||||
nodelist := []string{}
|
||||
if err := json.Unmarshal(blob, &nodelist); err != nil {
|
||||
glog.V(logger.Error).Infof("Failed to load nodes: %v", err)
|
||||
return nil
|
||||
}
|
||||
// Interpret the list as a discovery node array
|
||||
var nodes []*discover.Node
|
||||
for _, url := range nodelist {
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
node, err := discover.ParseNode(url)
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("Node URL %s: %v\n", url, err)
|
||||
continue
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
return nodes
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// DuplicateServiceError is returned during Node startup if a registered service
|
||||
// constructor returns a service of the same type that was already started.
|
||||
type DuplicateServiceError struct {
|
||||
Kind reflect.Type
|
||||
}
|
||||
|
||||
// Error generates a textual representation of the duplicate service error.
|
||||
func (e *DuplicateServiceError) Error() string {
|
||||
return fmt.Sprintf("duplicate service: %v", e.Kind)
|
||||
}
|
||||
|
||||
// StopError is returned if a Node fails to stop either any of its registered
|
||||
// services or itself.
|
||||
type StopError struct {
|
||||
Server error
|
||||
Services map[reflect.Type]error
|
||||
}
|
||||
|
||||
// Error generates a textual representation of the stop error.
|
||||
func (e *StopError) Error() string {
|
||||
return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services)
|
||||
}
|
|
@ -0,0 +1,604 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package node represents the Ethereum protocol stack container.
|
||||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDatadirUsed = errors.New("datadir already used")
|
||||
ErrNodeStopped = errors.New("node not started")
|
||||
ErrNodeRunning = errors.New("node already running")
|
||||
ErrServiceUnknown = errors.New("unknown service")
|
||||
|
||||
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
|
||||
)
|
||||
|
||||
// Node represents a P2P node into which arbitrary (uniquely typed) services might
|
||||
// be registered.
|
||||
type Node struct {
|
||||
datadir string // Path to the currently used data directory
|
||||
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
|
||||
|
||||
serverConfig *p2p.Server // Configuration of the underlying P2P networking layer
|
||||
server *p2p.Server // Currently running P2P networking layer
|
||||
|
||||
serviceFuncs []ServiceConstructor // Service constructors (in dependency order)
|
||||
services map[reflect.Type]Service // Currently running services
|
||||
|
||||
rpcAPIs []rpc.API // List of APIs currently provided by the node
|
||||
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
|
||||
|
||||
ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled)
|
||||
ipcListener net.Listener // IPC RPC listener socket to serve API requests
|
||||
ipcHandler *rpc.Server // IPC RPC request handler to process the API requests
|
||||
|
||||
httpHost string // HTTP hostname
|
||||
httpPort int // HTTP post
|
||||
httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
|
||||
httpWhitelist []string // HTTP RPC modules to allow through this endpoint
|
||||
httpCors string // HTTP RPC Cross-Origin Resource Sharing header
|
||||
httpListener net.Listener // HTTP RPC listener socket to server API requests
|
||||
httpHandler *rpc.Server // HTTP RPC request handler to process the API requests
|
||||
|
||||
wsHost string // Websocket host
|
||||
wsPort int // Websocket post
|
||||
wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled)
|
||||
wsWhitelist []string // Websocket RPC modules to allow through this endpoint
|
||||
wsOrigins string // Websocket RPC allowed origin domains
|
||||
wsListener net.Listener // Websocket RPC listener socket to server API requests
|
||||
wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
|
||||
|
||||
stop chan struct{} // Channel to wait for termination notifications
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates a new P2P node, ready for protocol registration.
|
||||
func New(conf *Config) (*Node, error) {
|
||||
// Ensure the data directory exists, failing if it cannot be created
|
||||
if conf.DataDir != "" {
|
||||
if err := os.MkdirAll(conf.DataDir, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Assemble the networking layer and the node itself
|
||||
nodeDbPath := ""
|
||||
if conf.DataDir != "" {
|
||||
nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase)
|
||||
}
|
||||
return &Node{
|
||||
datadir: conf.DataDir,
|
||||
serverConfig: &p2p.Server{
|
||||
PrivateKey: conf.NodeKey(),
|
||||
Name: conf.Name,
|
||||
Discovery: !conf.NoDiscovery,
|
||||
BootstrapNodes: conf.BootstrapNodes,
|
||||
StaticNodes: conf.StaticNodes(),
|
||||
TrustedNodes: conf.TrusterNodes(),
|
||||
NodeDatabase: nodeDbPath,
|
||||
ListenAddr: conf.ListenAddr,
|
||||
NAT: conf.NAT,
|
||||
Dialer: conf.Dialer,
|
||||
NoDial: conf.NoDial,
|
||||
MaxPeers: conf.MaxPeers,
|
||||
MaxPendingPeers: conf.MaxPendingPeers,
|
||||
},
|
||||
serviceFuncs: []ServiceConstructor{},
|
||||
ipcEndpoint: conf.IPCEndpoint(),
|
||||
httpHost: conf.HTTPHost,
|
||||
httpPort: conf.HTTPPort,
|
||||
httpEndpoint: conf.HTTPEndpoint(),
|
||||
httpWhitelist: conf.HTTPModules,
|
||||
httpCors: conf.HTTPCors,
|
||||
wsHost: conf.WSHost,
|
||||
wsPort: conf.WSPort,
|
||||
wsEndpoint: conf.WSEndpoint(),
|
||||
wsWhitelist: conf.WSModules,
|
||||
wsOrigins: conf.WSOrigins,
|
||||
eventmux: new(event.TypeMux),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Register injects a new service into the node's stack. The service created by
|
||||
// the passed constructor must be unique in its type with regard to sibling ones.
|
||||
func (n *Node) Register(constructor ServiceConstructor) error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
if n.server != nil {
|
||||
return ErrNodeRunning
|
||||
}
|
||||
n.serviceFuncs = append(n.serviceFuncs, constructor)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start create a live P2P node and starts running it.
|
||||
func (n *Node) Start() error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
// Short circuit if the node's already running
|
||||
if n.server != nil {
|
||||
return ErrNodeRunning
|
||||
}
|
||||
// Otherwise copy and specialize the P2P configuration
|
||||
running := new(p2p.Server)
|
||||
*running = *n.serverConfig
|
||||
|
||||
services := make(map[reflect.Type]Service)
|
||||
for _, constructor := range n.serviceFuncs {
|
||||
// Create a new context for the particular service
|
||||
ctx := &ServiceContext{
|
||||
datadir: n.datadir,
|
||||
services: make(map[reflect.Type]Service),
|
||||
EventMux: n.eventmux,
|
||||
}
|
||||
for kind, s := range services { // copy needed for threaded access
|
||||
ctx.services[kind] = s
|
||||
}
|
||||
// Construct and save the service
|
||||
service, err := constructor(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kind := reflect.TypeOf(service)
|
||||
if _, exists := services[kind]; exists {
|
||||
return &DuplicateServiceError{Kind: kind}
|
||||
}
|
||||
services[kind] = service
|
||||
}
|
||||
// Gather the protocols and start the freshly assembled P2P server
|
||||
for _, service := range services {
|
||||
running.Protocols = append(running.Protocols, service.Protocols()...)
|
||||
}
|
||||
if err := running.Start(); err != nil {
|
||||
if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] {
|
||||
return ErrDatadirUsed
|
||||
}
|
||||
return err
|
||||
}
|
||||
// Start each of the services
|
||||
started := []reflect.Type{}
|
||||
for kind, service := range services {
|
||||
// Start the next service, stopping all previous upon failure
|
||||
if err := service.Start(running); err != nil {
|
||||
for _, kind := range started {
|
||||
services[kind].Stop()
|
||||
}
|
||||
running.Stop()
|
||||
|
||||
return err
|
||||
}
|
||||
// Mark the service started for potential cleanup
|
||||
started = append(started, kind)
|
||||
}
|
||||
// Lastly start the configured RPC interfaces
|
||||
if err := n.startRPC(services); err != nil {
|
||||
for _, service := range services {
|
||||
service.Stop()
|
||||
}
|
||||
running.Stop()
|
||||
return err
|
||||
}
|
||||
// Finish initializing the startup
|
||||
n.services = services
|
||||
n.server = running
|
||||
n.stop = make(chan struct{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// startRPC is a helper method to start all the various RPC endpoint during node
|
||||
// startup. It's not meant to be called at any time afterwards as it makes certain
|
||||
// assumptions about the state of the node.
|
||||
func (n *Node) startRPC(services map[reflect.Type]Service) error {
|
||||
// Gather all the possible APIs to surface
|
||||
apis := n.apis()
|
||||
for _, service := range services {
|
||||
apis = append(apis, service.APIs()...)
|
||||
}
|
||||
// Start the various API endpoints, terminating all in case of errors
|
||||
if err := n.startInProc(apis); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.startIPC(apis); err != nil {
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startHTTP(n.httpEndpoint, apis, n.httpWhitelist, n.httpCors); err != nil {
|
||||
n.stopIPC()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
if err := n.startWS(n.wsEndpoint, apis, n.wsWhitelist, n.wsOrigins); err != nil {
|
||||
n.stopHTTP()
|
||||
n.stopIPC()
|
||||
n.stopInProc()
|
||||
return err
|
||||
}
|
||||
// All API endpoints started successfully
|
||||
n.rpcAPIs = apis
|
||||
return nil
|
||||
}
|
||||
|
||||
// startInProc initializes an in-process RPC endpoint.
|
||||
func (n *Node) startInProc(apis []rpc.API) error {
|
||||
// Register all the APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(logger.Debug).Infof("InProc registered %T under '%s'", api.Service, api.Namespace)
|
||||
}
|
||||
n.inprocHandler = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopInProc terminates the in-process RPC endpoint.
|
||||
func (n *Node) stopInProc() {
|
||||
if n.inprocHandler != nil {
|
||||
n.inprocHandler.Stop()
|
||||
n.inprocHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startIPC initializes and starts the IPC RPC endpoint.
|
||||
func (n *Node) startIPC(apis []rpc.API) error {
|
||||
// Short circuit if the IPC endpoint isn't being exposed
|
||||
if n.ipcEndpoint == "" {
|
||||
return nil
|
||||
}
|
||||
// Register all the APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(logger.Debug).Infof("IPC registered %T under '%s'", api.Service, api.Namespace)
|
||||
}
|
||||
// All APIs registered, start the IPC listener
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if listener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
glog.V(logger.Info).Infof("IPC endpoint opened: %s", n.ipcEndpoint)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
// Terminate if the listener was closed
|
||||
n.lock.RLock()
|
||||
closed := n.ipcListener == nil
|
||||
n.lock.RUnlock()
|
||||
if closed {
|
||||
return
|
||||
}
|
||||
// Not closed, just some error; report and continue
|
||||
glog.V(logger.Error).Infof("IPC accept failed: %v", err)
|
||||
continue
|
||||
}
|
||||
go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions)
|
||||
}
|
||||
}()
|
||||
// All listeners booted successfully
|
||||
n.ipcListener = listener
|
||||
n.ipcHandler = handler
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopIPC terminates the IPC RPC endpoint.
|
||||
func (n *Node) stopIPC() {
|
||||
if n.ipcListener != nil {
|
||||
n.ipcListener.Close()
|
||||
n.ipcListener = nil
|
||||
|
||||
glog.V(logger.Info).Infof("IPC endpoint closed: %s", n.ipcEndpoint)
|
||||
}
|
||||
if n.ipcHandler != nil {
|
||||
n.ipcHandler.Stop()
|
||||
n.ipcHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startHTTP initializes and starts the HTTP RPC endpoint.
|
||||
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors string) error {
|
||||
// Short circuit if the HTTP endpoint isn't being exposed
|
||||
if endpoint == "" {
|
||||
return nil
|
||||
}
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
for _, module := range modules {
|
||||
whitelist[module] = true
|
||||
}
|
||||
// Register all the APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(logger.Debug).Infof("HTTP registered %T under '%s'", api.Service, api.Namespace)
|
||||
}
|
||||
}
|
||||
// All APIs registered, start the HTTP listener
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
go rpc.NewHTTPServer(cors, handler).Serve(listener)
|
||||
glog.V(logger.Info).Infof("HTTP endpoint opened: http://%s", endpoint)
|
||||
|
||||
// All listeners booted successfully
|
||||
n.httpEndpoint = endpoint
|
||||
n.httpListener = listener
|
||||
n.httpHandler = handler
|
||||
n.httpCors = cors
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopHTTP terminates the HTTP RPC endpoint.
|
||||
func (n *Node) stopHTTP() {
|
||||
if n.httpListener != nil {
|
||||
n.httpListener.Close()
|
||||
n.httpListener = nil
|
||||
|
||||
glog.V(logger.Info).Infof("HTTP endpoint closed: http://%s", n.httpEndpoint)
|
||||
}
|
||||
if n.httpHandler != nil {
|
||||
n.httpHandler.Stop()
|
||||
n.httpHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startWS initializes and starts the websocket RPC endpoint.
|
||||
func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins string) error {
|
||||
// Short circuit if the WS endpoint isn't being exposed
|
||||
if endpoint == "" {
|
||||
return nil
|
||||
}
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
for _, module := range modules {
|
||||
whitelist[module] = true
|
||||
}
|
||||
// Register all the APIs exposed by the services
|
||||
handler := rpc.NewServer()
|
||||
for _, api := range apis {
|
||||
if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(logger.Debug).Infof("WebSocket registered %T under '%s'", api.Service, api.Namespace)
|
||||
}
|
||||
}
|
||||
// All APIs registered, start the HTTP listener
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
go rpc.NewWSServer(wsOrigins, handler).Serve(listener)
|
||||
glog.V(logger.Info).Infof("WebSocket endpoint opened: ws://%s", endpoint)
|
||||
|
||||
// All listeners booted successfully
|
||||
n.wsEndpoint = endpoint
|
||||
n.wsListener = listener
|
||||
n.wsHandler = handler
|
||||
n.wsOrigins = wsOrigins
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopWS terminates the websocket RPC endpoint.
|
||||
func (n *Node) stopWS() {
|
||||
if n.wsListener != nil {
|
||||
n.wsListener.Close()
|
||||
n.wsListener = nil
|
||||
|
||||
glog.V(logger.Info).Infof("WebSocket endpoint closed: ws://%s", n.wsEndpoint)
|
||||
}
|
||||
if n.wsHandler != nil {
|
||||
n.wsHandler.Stop()
|
||||
n.wsHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Stop terminates a running node along with all it's services. In the node was
|
||||
// not started, an error is returned.
|
||||
func (n *Node) Stop() error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
// Short circuit if the node's not running
|
||||
if n.server == nil {
|
||||
return ErrNodeStopped
|
||||
}
|
||||
// Otherwise terminate the API, all services and the P2P server too
|
||||
n.stopWS()
|
||||
n.stopHTTP()
|
||||
n.stopIPC()
|
||||
n.rpcAPIs = nil
|
||||
|
||||
failure := &StopError{
|
||||
Services: make(map[reflect.Type]error),
|
||||
}
|
||||
for kind, service := range n.services {
|
||||
if err := service.Stop(); err != nil {
|
||||
failure.Services[kind] = err
|
||||
}
|
||||
}
|
||||
n.server.Stop()
|
||||
|
||||
n.services = nil
|
||||
n.server = nil
|
||||
close(n.stop)
|
||||
|
||||
if len(failure.Services) > 0 {
|
||||
return failure
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait blocks the thread until the node is stopped. If the node is not running
|
||||
// at the time of invocation, the method immediately returns.
|
||||
func (n *Node) Wait() {
|
||||
n.lock.RLock()
|
||||
if n.server == nil {
|
||||
return
|
||||
}
|
||||
stop := n.stop
|
||||
n.lock.RUnlock()
|
||||
|
||||
<-stop
|
||||
}
|
||||
|
||||
// Restart terminates a running node and boots up a new one in its place. If the
|
||||
// node isn't running, an error is returned.
|
||||
func (n *Node) Restart() error {
|
||||
if err := n.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attach creates an RPC client attached to an in-process API handler.
|
||||
func (n *Node) Attach() (rpc.Client, error) {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
// Short circuit if the node's not running
|
||||
if n.server == nil {
|
||||
return nil, ErrNodeStopped
|
||||
}
|
||||
// Otherwise attach to the API and return
|
||||
return rpc.NewInProcRPCClient(n.inprocHandler), nil
|
||||
}
|
||||
|
||||
// Server retrieves the currently running P2P network layer. This method is meant
|
||||
// only to inspect fields of the currently running server, life cycle management
|
||||
// should be left to this Node entity.
|
||||
func (n *Node) Server() *p2p.Server {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
return n.server
|
||||
}
|
||||
|
||||
// Service retrieves a currently running service registered of a specific type.
|
||||
func (n *Node) Service(service interface{}) error {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
// Short circuit if the node's not running
|
||||
if n.server == nil {
|
||||
return ErrNodeStopped
|
||||
}
|
||||
// Otherwise try to find the service to return
|
||||
element := reflect.ValueOf(service).Elem()
|
||||
if running, ok := n.services[element.Type()]; ok {
|
||||
element.Set(reflect.ValueOf(running))
|
||||
return nil
|
||||
}
|
||||
return ErrServiceUnknown
|
||||
}
|
||||
|
||||
// DataDir retrieves the current datadir used by the protocol stack.
|
||||
func (n *Node) DataDir() string {
|
||||
return n.datadir
|
||||
}
|
||||
|
||||
// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack.
|
||||
func (n *Node) IPCEndpoint() string {
|
||||
return n.ipcEndpoint
|
||||
}
|
||||
|
||||
// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack.
|
||||
func (n *Node) HTTPEndpoint() string {
|
||||
return n.httpEndpoint
|
||||
}
|
||||
|
||||
// WSEndpoint retrieves the current WS endpoint used by the protocol stack.
|
||||
func (n *Node) WSEndpoint() string {
|
||||
return n.wsEndpoint
|
||||
}
|
||||
|
||||
// EventMux retrieves the event multiplexer used by all the network services in
|
||||
// the current protocol stack.
|
||||
func (n *Node) EventMux() *event.TypeMux {
|
||||
return n.eventmux
|
||||
}
|
||||
|
||||
// apis returns the collection of RPC descriptors this node offers.
|
||||
func (n *Node) apis() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "admin",
|
||||
Version: "1.0",
|
||||
Service: NewPrivateAdminAPI(n),
|
||||
}, {
|
||||
Namespace: "admin",
|
||||
Version: "1.0",
|
||||
Service: NewPublicAdminAPI(n),
|
||||
Public: true,
|
||||
}, {
|
||||
Namespace: "debug",
|
||||
Version: "1.0",
|
||||
Service: debug.Handler,
|
||||
}, {
|
||||
Namespace: "debug",
|
||||
Version: "1.0",
|
||||
Service: NewPublicDebugAPI(n),
|
||||
Public: true,
|
||||
}, {
|
||||
Namespace: "web3",
|
||||
Version: "1.0",
|
||||
Service: NewPublicWeb3API(n),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// ServiceContext is a collection of service independent options inherited from
|
||||
// the protocol stack, that is passed to all constructors to be optionally used;
|
||||
// as well as utility methods to operate on the service environment.
|
||||
type ServiceContext struct {
|
||||
datadir string // Data directory for protocol persistence
|
||||
services map[reflect.Type]Service // Index of the already constructed services
|
||||
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
||||
}
|
||||
|
||||
// OpenDatabase opens an existing database with the given name (or creates one
|
||||
// if no previous can be found) from within the node's data directory. If the
|
||||
// node is an ephemeral one, a memory database is returned.
|
||||
func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int) (ethdb.Database, error) {
|
||||
if ctx.datadir == "" {
|
||||
return ethdb.NewMemDatabase()
|
||||
}
|
||||
return ethdb.NewLDBDatabase(filepath.Join(ctx.datadir, name), cache, handles)
|
||||
}
|
||||
|
||||
// Service retrieves a currently running service registered of a specific type.
|
||||
func (ctx *ServiceContext) Service(service interface{}) error {
|
||||
element := reflect.ValueOf(service).Elem()
|
||||
if running, ok := ctx.services[element.Type()]; ok {
|
||||
element.Set(reflect.ValueOf(running))
|
||||
return nil
|
||||
}
|
||||
return ErrServiceUnknown
|
||||
}
|
||||
|
||||
// ServiceConstructor is the function signature of the constructors needed to be
|
||||
// registered for service instantiation.
|
||||
type ServiceConstructor func(ctx *ServiceContext) (Service, error)
|
||||
|
||||
// Service is an individual protocol that can be registered into a node.
|
||||
//
|
||||
// Notes:
|
||||
// - Service life-cycle management is delegated to the node. The service is
|
||||
// allowed to initialize itself upon creation, but no goroutines should be
|
||||
// spun up outside of the Start method.
|
||||
// - Restart logic is not required as the node will create a fresh instance
|
||||
// every time a service is started.
|
||||
type Service interface {
|
||||
// Protocols retrieves the P2P protocols the service wishes to start.
|
||||
Protocols() []p2p.Protocol
|
||||
|
||||
// APIs retrieves the list of RPC descriptors the service provides
|
||||
APIs() []rpc.API
|
||||
|
||||
// Start is called after all services have been constructed and the networking
|
||||
// layer was also initialized to spawn any goroutines required by the service.
|
||||
Start(server *p2p.Server) error
|
||||
|
||||
// Stop terminates all goroutines belonging to the service, blocking until they
|
||||
// are all terminated.
|
||||
Stop() error
|
||||
}
|
|
@ -34,7 +34,8 @@ const (
|
|||
|
||||
notificationBufferSize = 10000 // max buffered notifications before codec is closed
|
||||
|
||||
DefaultIPCApis = "admin,eth,debug,miner,net,shh,txpool,personal,web3"
|
||||
MetadataApi = "rpc"
|
||||
DefaultIPCApis = "admin,debug,eth,miner,net,personal,shh,txpool,web3"
|
||||
DefaultHTTPApis = "eth,net,web3"
|
||||
)
|
||||
|
||||
|
@ -61,7 +62,7 @@ func NewServer() *Server {
|
|||
// register a default service which will provide meta information about the RPC service such as the services and
|
||||
// methods it offers.
|
||||
rpcService := &RPCService{server}
|
||||
server.RegisterName("rpc", rpcService)
|
||||
server.RegisterName(MetadataApi, rpcService)
|
||||
|
||||
return server
|
||||
}
|
||||
|
|
|
@ -234,7 +234,7 @@ func SupportedModules(client Client) (map[string]string, error) {
|
|||
req := JSONRequest{
|
||||
Id: []byte("1"),
|
||||
Version: "2.0",
|
||||
Method: "rpc_modules",
|
||||
Method: MetadataApi + "_modules",
|
||||
}
|
||||
if err := client.Send(req); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -178,6 +178,9 @@ func (s *PublicWhisperAPI) Post(args PostArgs) (bool, error) {
|
|||
|
||||
// construct whisper message with transmission options
|
||||
message := NewMessage(common.FromHex(args.Payload))
|
||||
if len(message.Payload) == 0 && len(args.Payload) > 0 {
|
||||
message.Payload = []byte(args.Payload)
|
||||
}
|
||||
options := Options{
|
||||
To: crypto.ToECDSAPub(common.FromHex(args.To)),
|
||||
TTL: time.Duration(args.TTL) * time.Second,
|
||||
|
|
|
@ -18,6 +18,7 @@ package whisper
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -145,6 +146,20 @@ func (self *Whisper) GetIdentity(key *ecdsa.PublicKey) *ecdsa.PrivateKey {
|
|||
return self.keys[string(crypto.FromECDSAPub(key))]
|
||||
}
|
||||
|
||||
// InjectIdentity injects a manually added identity/key pair into the whisper keys
|
||||
func (self *Whisper) InjectIdentity(key *ecdsa.PrivateKey) error {
|
||||
|
||||
identity := string(crypto.FromECDSAPub(&key.PublicKey))
|
||||
self.keys[identity] = key
|
||||
if _, ok := self.keys[identity]; !ok {
|
||||
return fmt.Errorf("key insert into keys map failed")
|
||||
}
|
||||
|
||||
identityString := common.ToHex(crypto.FromECDSAPub(&key.PublicKey))
|
||||
fmt.Printf("Injected identity into whisper: %s\n", identityString)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch installs a new message handler to run in case a matching packet arrives
|
||||
// from the whisper network.
|
||||
func (self *Whisper) Watch(options Filter) int {
|
||||
|
@ -302,11 +317,16 @@ func (self *Whisper) open(envelope *Envelope) *Message {
|
|||
// Iterate over the keys and try to decrypt the message
|
||||
for _, key := range self.keys {
|
||||
message, err := envelope.Open(key)
|
||||
if err == nil {
|
||||
switch err {
|
||||
case nil:
|
||||
message.To = &key.PublicKey
|
||||
return message
|
||||
} else if err == ecies.ErrInvalidPublicKey {
|
||||
return message
|
||||
case ecies.ErrInvalidPublicKey:
|
||||
origMessage, err := envelope.Open(nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return origMessage
|
||||
}
|
||||
}
|
||||
// Failed to decrypt, don't return anything
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.3
|
||||
- release
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
|
@ -0,0 +1,10 @@
|
|||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
|
@ -0,0 +1 @@
|
|||
Paul Borman <borman@google.com>
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,13 @@
|
|||
This project was automatically exported from code.google.com/p/go-uuid
|
||||
|
||||
# uuid ![build status](https://travis-ci.org/pborman/uuid.svg?branch=master)
|
||||
The uuid package generates and inspects UUIDs based on [RFC 412](http://tools.ietf.org/html/rfc4122) and DCE 1.1: Authentication and Security Services.
|
||||
|
||||
###### Install
|
||||
`go get github.com/pborman/uuid`
|
||||
|
||||
###### Documentation
|
||||
[![GoDoc](https://godoc.org/github.com/pborman/uuid?status.svg)](http://godoc.org/github.com/pborman/uuid)
|
||||
|
||||
Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here:
|
||||
http://godoc.org/github.com/pborman/uuid
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) UUID {
|
||||
uuid := NewUUID()
|
||||
if uuid != nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCEPerson(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() UUID {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCEGroup(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() UUID {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID or false.
|
||||
func (uuid UUID) Domain() (Domain, bool) {
|
||||
if v, _ := uuid.Version(); v != 2 {
|
||||
return 0, false
|
||||
}
|
||||
return Domain(uuid[9]), true
|
||||
}
|
||||
|
||||
// Id returns the id for a Version 2 UUID or false.
|
||||
func (uuid UUID) Id() (uint32, bool) {
|
||||
if v, _ := uuid.Version(); v != 2 {
|
||||
return 0, false
|
||||
}
|
||||
return binary.BigEndian.Uint32(uuid[0:4]), true
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The uuid package generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
|
||||
package uuid
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known Name Space IDs and UUIDs
|
||||
var (
|
||||
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
||||
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
||||
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
||||
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||
NIL = Parse("00000000-0000-0000-0000-000000000000")
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space)
|
||||
h.Write([]byte(data))
|
||||
s := h.Sum(nil)
|
||||
uuid := make([]byte, 16)
|
||||
copy(uuid, s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data.
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data.
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "errors"
|
||||
|
||||
func (u UUID) MarshalJSON() ([]byte, error) {
|
||||
if len(u) != 16 {
|
||||
return []byte(`""`), nil
|
||||
}
|
||||
var js [38]byte
|
||||
js[0] = '"'
|
||||
encodeHex(js[1:], u)
|
||||
js[37] = '"'
|
||||
return js[:], nil
|
||||
}
|
||||
|
||||
func (u *UUID) UnmarshalJSON(data []byte) error {
|
||||
if string(data) == `""` {
|
||||
return nil
|
||||
}
|
||||
if data[0] != '"' {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
data = data[1 : len(data)-1]
|
||||
uu := Parse(string(data))
|
||||
if uu == nil {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
*u = uu
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeMu sync.Mutex
|
||||
interfaces []net.Interface // cached list of interfaces
|
||||
ifname string // name of interface being used
|
||||
nodeID []byte // hardware for version 1 UUIDs
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return setNodeInterface(name)
|
||||
}
|
||||
|
||||
func setNodeInterface(name string) bool {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil && name != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
if setNodeID(ifs.HardwareAddr) {
|
||||
ifname = ifs.Name
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
if nodeID == nil {
|
||||
nodeID = make([]byte, 6)
|
||||
}
|
||||
randomBits(nodeID)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if nodeID == nil {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nid := make([]byte, 6)
|
||||
copy(nid, nodeID)
|
||||
return nid
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if setNodeID(id) {
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
if nodeID == nil {
|
||||
nodeID = make([]byte, 6)
|
||||
}
|
||||
copy(nodeID, id)
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
if len(uuid) != 16 {
|
||||
return nil
|
||||
}
|
||||
node := make([]byte, 6)
|
||||
copy(node, uuid[10:])
|
||||
return node
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
switch src.(type) {
|
||||
case string:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if src.(string) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// see uuid.Parse for required string format
|
||||
parsed := Parse(src.(string))
|
||||
|
||||
if parsed == nil {
|
||||
return errors.New("Scan: invalid UUID format")
|
||||
}
|
||||
|
||||
*uuid = parsed
|
||||
case []byte:
|
||||
b := src.([]byte)
|
||||
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// assumes a simple slice of bytes if 16 bytes
|
||||
// otherwise attempts to parse
|
||||
if len(b) == 16 {
|
||||
*uuid = UUID(b)
|
||||
} else {
|
||||
u := Parse(string(b))
|
||||
|
||||
if u == nil {
|
||||
return errors.New("Scan: invalid UUID format")
|
||||
}
|
||||
|
||||
*uuid = u
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||
// transparently. Currently, UUIDs map to strings. Please consult
|
||||
// database-specific driver documentation for matching types.
|
||||
func (uuid UUID) Value() (driver.Value, error) {
|
||||
return uuid.String(), nil
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
timeMu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clock_seq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clock_seq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clock_seq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence a new random
|
||||
// clock sequence is generated the first time a clock sequence is requested by
|
||||
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
|
||||
// for
|
||||
func ClockSequence() int {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clock_seq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clock_seq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
old_seq := clock_seq
|
||||
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if old_seq != clock_seq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. It returns false if uuid is not valid. The time is only well defined
|
||||
// for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) Time() (Time, bool) {
|
||||
if len(uuid) != 16 {
|
||||
return 0, false
|
||||
}
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
return Time(time), true
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid. It returns false
|
||||
// if uuid is not valid. The clock sequence is only well defined for version 1
|
||||
// and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() (int, bool) {
|
||||
if len(uuid) != 16 {
|
||||
return 0, false
|
||||
}
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// randomBits completely fills slice b with random data.
|
||||
func randomBits(b []byte) {
|
||||
if _, err := io.ReadFull(rander, b); err != nil {
|
||||
panic(err.Error()) // rand should never fail
|
||||
}
|
||||
}
|
||||
|
||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||
var xvalues = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
// xtob converts the the first two hex bytes of x into a byte.
|
||||
func xtob(x string) (byte, bool) {
|
||||
b1 := xvalues[x[0]]
|
||||
b2 := xvalues[x[1]]
|
||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Array is a pass-by-value UUID that can be used as an effecient key in a map.
|
||||
type Array [16]byte
|
||||
|
||||
// UUID converts uuid into a slice.
|
||||
func (uuid Array) UUID() UUID {
|
||||
return uuid[:]
|
||||
}
|
||||
|
||||
// String returns the string representation of uuid,
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
|
||||
func (uuid Array) String() string {
|
||||
return uuid.UUID().String()
|
||||
}
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
// 4122.
|
||||
type UUID []byte
|
||||
|
||||
// A Version represents a UUIDs version.
|
||||
type Version byte
|
||||
|
||||
// A Variant represents a UUIDs variant.
|
||||
type Variant byte
|
||||
|
||||
// Constants returned by Variant.
|
||||
const (
|
||||
Invalid = Variant(iota) // Invalid UUID
|
||||
RFC4122 // The variant specified in RFC4122
|
||||
Reserved // Reserved, NCS backward compatibility.
|
||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
var rander = rand.Reader // random function
|
||||
|
||||
// New returns a new random (version 4) UUID as a string. It is a convenience
|
||||
// function for NewRandom().String().
|
||||
func New() string {
|
||||
return NewRandom().String()
|
||||
}
|
||||
|
||||
// Parse decodes s into a UUID or returns nil. Both the UUID form of
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
|
||||
func Parse(s string) UUID {
|
||||
if len(s) == 36+9 {
|
||||
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||
return nil
|
||||
}
|
||||
s = s[9:]
|
||||
} else if len(s) != 36 {
|
||||
return nil
|
||||
}
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return nil
|
||||
}
|
||||
var uuid [16]byte
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34} {
|
||||
if v, ok := xtob(s[x:]); !ok {
|
||||
return nil
|
||||
} else {
|
||||
uuid[i] = v
|
||||
}
|
||||
}
|
||||
return uuid[:]
|
||||
}
|
||||
|
||||
// Equal returns true if uuid1 and uuid2 are equal.
|
||||
func Equal(uuid1, uuid2 UUID) bool {
|
||||
return bytes.Equal(uuid1, uuid2)
|
||||
}
|
||||
|
||||
// Array returns an array representation of uuid that can be used as a map key.
|
||||
// Array panics if uuid is not valid.
|
||||
func (uuid UUID) Array() Array {
|
||||
if len(uuid) != 16 {
|
||||
panic("invalid uuid")
|
||||
}
|
||||
var a Array
|
||||
copy(a[:], uuid)
|
||||
return a
|
||||
}
|
||||
|
||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// , or "" if uuid is invalid.
|
||||
func (uuid UUID) String() string {
|
||||
if len(uuid) != 16 {
|
||||
return ""
|
||||
}
|
||||
var buf [36]byte
|
||||
encodeHex(buf[:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
// URN returns the RFC 2141 URN form of uuid,
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||
func (uuid UUID) URN() string {
|
||||
if len(uuid) != 16 {
|
||||
return ""
|
||||
}
|
||||
var buf [36 + 9]byte
|
||||
copy(buf[:], "urn:uuid:")
|
||||
encodeHex(buf[9:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
func encodeHex(dst []byte, uuid UUID) {
|
||||
hex.Encode(dst[:], uuid[:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], uuid[4:6])
|
||||
dst[13] = '-'
|
||||
hex.Encode(dst[14:18], uuid[6:8])
|
||||
dst[18] = '-'
|
||||
hex.Encode(dst[19:23], uuid[8:10])
|
||||
dst[23] = '-'
|
||||
hex.Encode(dst[24:], uuid[10:])
|
||||
}
|
||||
|
||||
// Variant returns the variant encoded in uuid. It returns Invalid if
|
||||
// uuid is invalid.
|
||||
func (uuid UUID) Variant() Variant {
|
||||
if len(uuid) != 16 {
|
||||
return Invalid
|
||||
}
|
||||
switch {
|
||||
case (uuid[8] & 0xc0) == 0x80:
|
||||
return RFC4122
|
||||
case (uuid[8] & 0xe0) == 0xc0:
|
||||
return Microsoft
|
||||
case (uuid[8] & 0xe0) == 0xe0:
|
||||
return Future
|
||||
default:
|
||||
return Reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the version of uuid. It returns false if uuid is not
|
||||
// valid.
|
||||
func (uuid UUID) Version() (Version, bool) {
|
||||
if len(uuid) != 16 {
|
||||
return 0, false
|
||||
}
|
||||
return Version(uuid[6] >> 4), true
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v > 15 {
|
||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||
}
|
||||
return fmt.Sprintf("VERSION_%d", v)
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
switch v {
|
||||
case RFC4122:
|
||||
return "RFC4122"
|
||||
case Reserved:
|
||||
return "Reserved"
|
||||
case Microsoft:
|
||||
return "Microsoft"
|
||||
case Future:
|
||||
return "Future"
|
||||
case Invalid:
|
||||
return "Invalid"
|
||||
}
|
||||
return fmt.Sprintf("BadVariant%d", int(v))
|
||||
}
|
||||
|
||||
// SetRand sets the random number generator to r, which implents io.Reader.
|
||||
// If r.Read returns an error when the package requests random data then
|
||||
// a panic will be issued.
|
||||
//
|
||||
// Calling SetRand with nil sets the random number generator to the default
|
||||
// generator.
|
||||
func SetRand(r io.Reader) {
|
||||
if r == nil {
|
||||
rander = rand.Reader
|
||||
return
|
||||
}
|
||||
rander = r
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewUUID returns nil.
|
||||
func NewUUID() UUID {
|
||||
if nodeID == nil {
|
||||
SetNodeInterface("")
|
||||
}
|
||||
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid := make([]byte, 16)
|
||||
|
||||
time_low := uint32(now & 0xffffffff)
|
||||
time_mid := uint16((now >> 32) & 0xffff)
|
||||
time_hi := uint16((now >> 48) & 0x0fff)
|
||||
time_hi |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uuid[0:], time_low)
|
||||
binary.BigEndian.PutUint16(uuid[4:], time_mid)
|
||||
binary.BigEndian.PutUint16(uuid[6:], time_hi)
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
copy(uuid[10:], nodeID)
|
||||
|
||||
return uuid
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
// Random returns a Random (Version 4) UUID or panics.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// A note about uniqueness derived from from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() UUID {
|
||||
uuid := make([]byte, 16)
|
||||
randomBits([]byte(uuid))
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
# Created by https://www.gitignore.io
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
|
||||
### Go ###
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
|
||||
### vim ###
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
*.un~
|
||||
Session.vim
|
||||
.netrwhist
|
||||
*~
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.6
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
go: 1.6
|
||||
env:
|
||||
- GOFLAGS="-tags kqueue"
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOBIN=$HOME/bin
|
||||
- PATH=$HOME/bin:$PATH
|
||||
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go tool vet -all .
|
||||
- go install $GOFLAGS ./...
|
||||
- go test -v -race $GOFLAGS ./...
|
|
@ -0,0 +1,10 @@
|
|||
# List of individuals who contributed to the Notify package.
|
||||
#
|
||||
# The up-to-date list of the authors one may obtain with:
|
||||
#
|
||||
# ~ $ git shortlog -es | cut -f2 | rev | uniq -f1 | rev
|
||||
#
|
||||
|
||||
Pawel Blaszczyk <blaszczykpb@gmail.com>
|
||||
Pawel Knap <pawelknap88@gmail.com>
|
||||
Rafal Jeczalik <rjeczalik@gmail.com>
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2015 The Notify Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,21 @@
|
|||
notify [![GoDoc](https://godoc.org/github.com/rjeczalik/notify?status.svg)](https://godoc.org/github.com/rjeczalik/notify) [![Build Status](https://img.shields.io/travis/rjeczalik/notify/master.svg)](https://travis-ci.org/rjeczalik/notify "inotify + FSEvents + kqueue") [![Build status](https://img.shields.io/appveyor/ci/rjeczalik/notify-246.svg)](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [![Coverage Status](https://img.shields.io/coveralls/rjeczalik/notify/master.svg)](https://coveralls.io/r/rjeczalik/notify?branch=master)
|
||||
======
|
||||
|
||||
Filesystem event notification library on steroids. (under active development)
|
||||
|
||||
*Documentation*
|
||||
|
||||
[godoc.org/github.com/rjeczalik/notify](https://godoc.org/github.com/rjeczalik/notify)
|
||||
|
||||
*Installation*
|
||||
|
||||
```
|
||||
~ $ go get -u github.com/rjeczalik/notify
|
||||
```
|
||||
|
||||
*Projects using notify*
|
||||
|
||||
- [github.com/rjeczalik/cmd/notify](https://godoc.org/github.com/rjeczalik/cmd/notify)
|
||||
- [github.com/cortesi/devd](https://github.com/cortesi/devd)
|
||||
- [github.com/cortesi/modd](https://github.com/cortesi/modd)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
version: "{build}"
|
||||
|
||||
os: Windows Server 2012 R2
|
||||
|
||||
clone_folder: c:\projects\src\github.com\rjeczalik\notify
|
||||
|
||||
environment:
|
||||
PATH: c:\projects\bin;%PATH%
|
||||
GOPATH: c:\projects
|
||||
NOTIFY_TIMEOUT: 5s
|
||||
|
||||
install:
|
||||
- go version
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get -v -t ./...
|
||||
|
||||
build_script:
|
||||
- go tool vet -all .
|
||||
- go build ./...
|
||||
- go test -v -race ./...
|
||||
|
||||
test: off
|
||||
|
||||
deploy: off
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build !debug
|
||||
|
||||
package notify
|
||||
|
||||
func dbgprint(...interface{}) {}
|
||||
|
||||
func dbgprintf(string, ...interface{}) {}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build debug
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func dbgprint(v ...interface{}) {
|
||||
fmt.Printf("[D] ")
|
||||
fmt.Print(v...)
|
||||
fmt.Printf("\n\n")
|
||||
}
|
||||
|
||||
func dbgprintf(format string, v ...interface{}) {
|
||||
fmt.Printf("[D] ")
|
||||
fmt.Printf(format, v...)
|
||||
fmt.Printf("\n\n")
|
||||
}
|
||||
|
||||
func dbgcallstack(max int) []string {
|
||||
pc, stack := make([]uintptr, max), make([]string, 0, max)
|
||||
runtime.Callers(2, pc)
|
||||
for _, pc := range pc {
|
||||
if f := runtime.FuncForPC(pc); f != nil {
|
||||
fname := f.Name()
|
||||
idx := strings.LastIndex(fname, string(os.PathSeparator))
|
||||
if idx != -1 {
|
||||
stack = append(stack, fname[idx+1:])
|
||||
} else {
|
||||
stack = append(stack, fname)
|
||||
}
|
||||
}
|
||||
}
|
||||
return stack
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Package notify implements access to filesystem events.
|
||||
//
|
||||
// Notify is a high-level abstraction over filesystem watchers like inotify,
|
||||
// kqueue, FSEvents, FEN or ReadDirectoryChangesW. Watcher implementations are
|
||||
// split into two groups: ones that natively support recursive notifications
|
||||
// (FSEvents and ReadDirectoryChangesW) and ones that do not (inotify, kqueue, FEN).
|
||||
// For more details see watcher and recursiveWatcher interfaces in watcher.go
|
||||
// source file.
|
||||
//
|
||||
// On top of filesystem watchers notify maintains a watchpoint tree, which provides
|
||||
// strategy for creating and closing filesystem watches and dispatching filesystem
|
||||
// events to user channels.
|
||||
//
|
||||
// An event set is just an event list joint using bitwise OR operator
|
||||
// into a single event value.
|
||||
//
|
||||
// A filesystem watch or just a watch is platform-specific entity which represents
|
||||
// a single path registered for notifications for specific event set. Setting a watch
|
||||
// means using platform-specific API calls for creating / initializing said watch.
|
||||
// For each watcher the API call is:
|
||||
//
|
||||
// - FSEvents: FSEventStreamCreate
|
||||
// - inotify: notify_add_watch
|
||||
// - kqueue: kevent
|
||||
// - ReadDirectoryChangesW: CreateFile+ReadDirectoryChangesW
|
||||
// - FEN: port_get
|
||||
//
|
||||
// To rewatch means to either shrink or expand an event set that was previously
|
||||
// registered during watch operation for particular filesystem watch.
|
||||
//
|
||||
// A watchpoint is a list of user channel and event set pairs for particular
|
||||
// path (watchpoint tree's node). A single watchpoint can contain multiple
|
||||
// different user channels registered to listen for one or more events. A single
|
||||
// user channel can be registered in one or more watchpoints, recurisve and
|
||||
// non-recursive ones as well.
|
||||
package notify
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Event represents the type of filesystem action.
|
||||
//
|
||||
// Number of available event values is dependent on the target system or the
|
||||
// watcher implmenetation used (e.g. it's possible to use either kqueue or
|
||||
// FSEvents on Darwin).
|
||||
//
|
||||
// Please consult documentation for your target platform to see list of all
|
||||
// available events.
|
||||
type Event uint32
|
||||
|
||||
// Create, Remove, Write and Rename are the only event values guaranteed to be
|
||||
// present on all platforms.
|
||||
const (
|
||||
Create = osSpecificCreate
|
||||
Remove = osSpecificRemove
|
||||
Write = osSpecificWrite
|
||||
Rename = osSpecificRename
|
||||
|
||||
// All is handful alias for all platform-independent event values.
|
||||
All = Create | Remove | Write | Rename
|
||||
)
|
||||
|
||||
const internal = recursive | omit
|
||||
|
||||
// String implements fmt.Stringer interface.
|
||||
func (e Event) String() string {
|
||||
var s []string
|
||||
for _, strmap := range []map[Event]string{estr, osestr} {
|
||||
for ev, str := range strmap {
|
||||
if e&ev == ev {
|
||||
s = append(s, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(s, "|")
|
||||
}
|
||||
|
||||
// EventInfo describes an event reported by the underlying filesystem notification
|
||||
// subsystem.
|
||||
//
|
||||
// It always describes single event, even if the OS reported a coalesced action.
|
||||
// Reported path is absolute and clean.
|
||||
//
|
||||
// For non-recursive watchpoints its base is always equal to the path passed
|
||||
// to corresponding Watch call.
|
||||
//
|
||||
// The value of Sys if system-dependent and can be nil.
|
||||
//
|
||||
// Sys
|
||||
//
|
||||
// Under Darwin (FSEvents) Sys() always returns a non-nil *notify.FSEvent value,
|
||||
// which is defined as:
|
||||
//
|
||||
// type FSEvent struct {
|
||||
// Path string // real path of the file or directory
|
||||
// ID uint64 // ID of the event (FSEventStreamEventId)
|
||||
// Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
|
||||
// }
|
||||
//
|
||||
// For possible values of Flags see Darwin godoc for notify or FSEvents
|
||||
// documentation for FSEventStreamEventFlags constants:
|
||||
//
|
||||
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags
|
||||
//
|
||||
// Under Linux (inotify) Sys() always returns a non-nil *syscall.InotifyEvent
|
||||
// value, defined as:
|
||||
//
|
||||
// type InotifyEvent struct {
|
||||
// Wd int32 // Watch descriptor
|
||||
// Mask uint32 // Mask describing event
|
||||
// Cookie uint32 // Unique cookie associating related events (for rename(2))
|
||||
// Len uint32 // Size of name field
|
||||
// Name [0]uint8 // Optional null-terminated name
|
||||
// }
|
||||
//
|
||||
// More information about inotify masks and the usage of inotify_event structure
|
||||
// can be found at:
|
||||
//
|
||||
// http://man7.org/linux/man-pages/man7/inotify.7.html
|
||||
//
|
||||
// Under Darwin, DragonFlyBSD, FreeBSD, NetBSD, OpenBSD (kqueue) Sys() always
|
||||
// returns a non-nil *notify.Kevent value, which is defined as:
|
||||
//
|
||||
// type Kevent struct {
|
||||
// Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure
|
||||
// FI os.FileInfo // FI describes file/dir
|
||||
// }
|
||||
//
|
||||
// More information about syscall.Kevent_t can be found at:
|
||||
//
|
||||
// https://www.freebsd.org/cgi/man.cgi?query=kqueue
|
||||
//
|
||||
// Under Windows (ReadDirectoryChangesW) Sys() always returns nil. The documentation
|
||||
// of watcher's WinAPI function can be found at:
|
||||
//
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx
|
||||
type EventInfo interface {
|
||||
Event() Event // event value for the filesystem action
|
||||
Path() string // real path of the file or directory
|
||||
Sys() interface{} // underlying data source (can return nil)
|
||||
}
|
||||
|
||||
type isDirer interface {
|
||||
isDir() (bool, error)
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = (*event)(nil)
|
||||
var _ isDirer = (*event)(nil)
|
||||
|
||||
// String implements fmt.Stringer interface.
|
||||
func (e *event) String() string {
|
||||
return e.Event().String() + `: "` + e.Path() + `"`
|
||||
}
|
||||
|
||||
var estr = map[Event]string{
|
||||
Create: "notify.Create",
|
||||
Remove: "notify.Remove",
|
||||
Write: "notify.Write",
|
||||
Rename: "notify.Rename",
|
||||
// Display name for recursive event is added only for debugging
|
||||
// purposes. It's an internal event after all and won't be exposed to the
|
||||
// user. Having Recursive event printable is helpful, e.g. for reading
|
||||
// testing failure messages:
|
||||
//
|
||||
// --- FAIL: TestWatchpoint (0.00 seconds)
|
||||
// watchpoint_test.go:64: want diff=[notify.Remove notify.Create|notify.Remove];
|
||||
// got [notify.Remove notify.Remove|notify.Create] (i=1)
|
||||
//
|
||||
// Yup, here the diff have Recursive event inside. Go figure.
|
||||
recursive: "recursive",
|
||||
omit: "omit",
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package notify
|
||||
|
||||
const (
|
||||
osSpecificCreate Event = 0x00000100 << iota
|
||||
osSpecificRemove
|
||||
osSpecificWrite
|
||||
osSpecificRename
|
||||
// internal
|
||||
// recursive is used to distinguish recursive eventsets from non-recursive ones
|
||||
recursive
|
||||
// omit is used for dispatching internal events; only those events are sent
|
||||
// for which both the event and the watchpoint has omit in theirs event sets.
|
||||
omit
|
||||
)
|
||||
|
||||
const (
|
||||
FileAccess = fileAccess
|
||||
FileModified = fileModified
|
||||
FileAttrib = fileAttrib
|
||||
FileDelete = fileDelete
|
||||
FileRenameTo = fileRenameTo
|
||||
FileRenameFrom = fileRenameFrom
|
||||
FileTrunc = fileTrunc
|
||||
FileNoFollow = fileNoFollow
|
||||
Unmounted = unmounted
|
||||
MountedOver = mountedOver
|
||||
)
|
||||
|
||||
var osestr = map[Event]string{
|
||||
FileAccess: "notify.FileAccess",
|
||||
FileModified: "notify.FileModified",
|
||||
FileAttrib: "notify.FileAttrib",
|
||||
FileDelete: "notify.FileDelete",
|
||||
FileRenameTo: "notify.FileRenameTo",
|
||||
FileRenameFrom: "notify.FileRenameFrom",
|
||||
FileTrunc: "notify.FileTrunc",
|
||||
FileNoFollow: "notify.FileNoFollow",
|
||||
Unmounted: "notify.Unmounted",
|
||||
MountedOver: "notify.MountedOver",
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin,!kqueue
|
||||
|
||||
package notify
|
||||
|
||||
const (
|
||||
osSpecificCreate = Event(FSEventsCreated)
|
||||
osSpecificRemove = Event(FSEventsRemoved)
|
||||
osSpecificWrite = Event(FSEventsModified)
|
||||
osSpecificRename = Event(FSEventsRenamed)
|
||||
// internal = Event(0x100000)
|
||||
// recursive is used to distinguish recursive eventsets from non-recursive ones
|
||||
recursive = Event(0x200000)
|
||||
// omit is used for dispatching internal events; only those events are sent
|
||||
// for which both the event and the watchpoint has omit in theirs event sets.
|
||||
omit = Event(0x400000)
|
||||
)
|
||||
|
||||
// FSEvents specific event values.
|
||||
const (
|
||||
FSEventsMustScanSubDirs Event = 0x00001
|
||||
FSEventsUserDropped = 0x00002
|
||||
FSEventsKernelDropped = 0x00004
|
||||
FSEventsEventIdsWrapped = 0x00008
|
||||
FSEventsHistoryDone = 0x00010
|
||||
FSEventsRootChanged = 0x00020
|
||||
FSEventsMount = 0x00040
|
||||
FSEventsUnmount = 0x00080
|
||||
FSEventsCreated = 0x00100
|
||||
FSEventsRemoved = 0x00200
|
||||
FSEventsInodeMetaMod = 0x00400
|
||||
FSEventsRenamed = 0x00800
|
||||
FSEventsModified = 0x01000
|
||||
FSEventsFinderInfoMod = 0x02000
|
||||
FSEventsChangeOwner = 0x04000
|
||||
FSEventsXattrMod = 0x08000
|
||||
FSEventsIsFile = 0x10000
|
||||
FSEventsIsDir = 0x20000
|
||||
FSEventsIsSymlink = 0x40000
|
||||
)
|
||||
|
||||
var osestr = map[Event]string{
|
||||
FSEventsMustScanSubDirs: "notify.FSEventsMustScanSubDirs",
|
||||
FSEventsUserDropped: "notify.FSEventsUserDropped",
|
||||
FSEventsKernelDropped: "notify.FSEventsKernelDropped",
|
||||
FSEventsEventIdsWrapped: "notify.FSEventsEventIdsWrapped",
|
||||
FSEventsHistoryDone: "notify.FSEventsHistoryDone",
|
||||
FSEventsRootChanged: "notify.FSEventsRootChanged",
|
||||
FSEventsMount: "notify.FSEventsMount",
|
||||
FSEventsUnmount: "notify.FSEventsUnmount",
|
||||
FSEventsInodeMetaMod: "notify.FSEventsInodeMetaMod",
|
||||
FSEventsFinderInfoMod: "notify.FSEventsFinderInfoMod",
|
||||
FSEventsChangeOwner: "notify.FSEventsChangeOwner",
|
||||
FSEventsXattrMod: "notify.FSEventsXattrMod",
|
||||
FSEventsIsFile: "notify.FSEventsIsFile",
|
||||
FSEventsIsDir: "notify.FSEventsIsDir",
|
||||
FSEventsIsSymlink: "notify.FSEventsIsSymlink",
|
||||
}
|
||||
|
||||
type event struct {
|
||||
fse FSEvent
|
||||
event Event
|
||||
}
|
||||
|
||||
func (ei *event) Event() Event { return ei.event }
|
||||
func (ei *event) Path() string { return ei.fse.Path }
|
||||
func (ei *event) Sys() interface{} { return &ei.fse }
|
||||
func (ei *event) isDir() (bool, error) { return ei.fse.Flags&FSEventsIsDir != 0, nil }
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package notify
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Platform independent event values.
|
||||
const (
|
||||
osSpecificCreate Event = 0x100000 << iota
|
||||
osSpecificRemove
|
||||
osSpecificWrite
|
||||
osSpecificRename
|
||||
// internal
|
||||
// recursive is used to distinguish recursive eventsets from non-recursive ones
|
||||
recursive
|
||||
// omit is used for dispatching internal events; only those events are sent
|
||||
// for which both the event and the watchpoint has omit in theirs event sets.
|
||||
omit
|
||||
)
|
||||
|
||||
// Inotify specific masks are legal, implemented events that are guaranteed to
|
||||
// work with notify package on linux-based systems.
|
||||
const (
|
||||
InAccess = Event(syscall.IN_ACCESS) // File was accessed
|
||||
InModify = Event(syscall.IN_MODIFY) // File was modified
|
||||
InAttrib = Event(syscall.IN_ATTRIB) // Metadata changed
|
||||
InCloseWrite = Event(syscall.IN_CLOSE_WRITE) // Writtable file was closed
|
||||
InCloseNowrite = Event(syscall.IN_CLOSE_NOWRITE) // Unwrittable file closed
|
||||
InOpen = Event(syscall.IN_OPEN) // File was opened
|
||||
InMovedFrom = Event(syscall.IN_MOVED_FROM) // File was moved from X
|
||||
InMovedTo = Event(syscall.IN_MOVED_TO) // File was moved to Y
|
||||
InCreate = Event(syscall.IN_CREATE) // Subfile was created
|
||||
InDelete = Event(syscall.IN_DELETE) // Subfile was deleted
|
||||
InDeleteSelf = Event(syscall.IN_DELETE_SELF) // Self was deleted
|
||||
InMoveSelf = Event(syscall.IN_MOVE_SELF) // Self was moved
|
||||
)
|
||||
|
||||
var osestr = map[Event]string{
|
||||
InAccess: "notify.InAccess",
|
||||
InModify: "notify.InModify",
|
||||
InAttrib: "notify.InAttrib",
|
||||
InCloseWrite: "notify.InCloseWrite",
|
||||
InCloseNowrite: "notify.InCloseNowrite",
|
||||
InOpen: "notify.InOpen",
|
||||
InMovedFrom: "notify.InMovedFrom",
|
||||
InMovedTo: "notify.InMovedTo",
|
||||
InCreate: "notify.InCreate",
|
||||
InDelete: "notify.InDelete",
|
||||
InDeleteSelf: "notify.InDeleteSelf",
|
||||
InMoveSelf: "notify.InMoveSelf",
|
||||
}
|
||||
|
||||
// Inotify behavior events are not **currently** supported by notify package.
|
||||
const (
|
||||
inDontFollow = Event(syscall.IN_DONT_FOLLOW)
|
||||
inExclUnlink = Event(syscall.IN_EXCL_UNLINK)
|
||||
inMaskAdd = Event(syscall.IN_MASK_ADD)
|
||||
inOneshot = Event(syscall.IN_ONESHOT)
|
||||
inOnlydir = Event(syscall.IN_ONLYDIR)
|
||||
)
|
||||
|
||||
type event struct {
|
||||
sys syscall.InotifyEvent
|
||||
path string
|
||||
event Event
|
||||
}
|
||||
|
||||
func (e *event) Event() Event { return e.event }
|
||||
func (e *event) Path() string { return e.path }
|
||||
func (e *event) Sys() interface{} { return &e.sys }
|
||||
func (e *event) isDir() (bool, error) { return e.sys.Mask&syscall.IN_ISDIR != 0, nil }
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin,kqueue dragonfly freebsd netbsd openbsd
|
||||
|
||||
package notify
|
||||
|
||||
import "syscall"
|
||||
|
||||
// TODO(pblaszczyk): ensure in runtime notify built-in event values do not
|
||||
// overlap with platform-defined ones.
|
||||
|
||||
// Platform independent event values.
|
||||
const (
|
||||
osSpecificCreate Event = 0x0100 << iota
|
||||
osSpecificRemove
|
||||
osSpecificWrite
|
||||
osSpecificRename
|
||||
// internal
|
||||
// recursive is used to distinguish recursive eventsets from non-recursive ones
|
||||
recursive
|
||||
// omit is used for dispatching internal events; only those events are sent
|
||||
// for which both the event and the watchpoint has omit in theirs event sets.
|
||||
omit
|
||||
)
|
||||
|
||||
const (
|
||||
// NoteDelete is an even reported when the unlink() system call was called
|
||||
// on the file referenced by the descriptor.
|
||||
NoteDelete = Event(syscall.NOTE_DELETE)
|
||||
// NoteWrite is an event reported when a write occurred on the file
|
||||
// referenced by the descriptor.
|
||||
NoteWrite = Event(syscall.NOTE_WRITE)
|
||||
// NoteExtend is an event reported when the file referenced by the
|
||||
// descriptor was extended.
|
||||
NoteExtend = Event(syscall.NOTE_EXTEND)
|
||||
// NoteAttrib is an event reported when the file referenced
|
||||
// by the descriptor had its attributes changed.
|
||||
NoteAttrib = Event(syscall.NOTE_ATTRIB)
|
||||
// NoteLink is an event reported when the link count on the file changed.
|
||||
NoteLink = Event(syscall.NOTE_LINK)
|
||||
// NoteRename is an event reported when the file referenced
|
||||
// by the descriptor was renamed.
|
||||
NoteRename = Event(syscall.NOTE_RENAME)
|
||||
// NoteRevoke is an event reported when access to the file was revoked via
|
||||
// revoke(2) or the underlying file system was unmounted.
|
||||
NoteRevoke = Event(syscall.NOTE_REVOKE)
|
||||
)
|
||||
|
||||
var osestr = map[Event]string{
|
||||
NoteDelete: "notify.NoteDelete",
|
||||
NoteWrite: "notify.NoteWrite",
|
||||
NoteExtend: "notify.NoteExtend",
|
||||
NoteAttrib: "notify.NoteAttrib",
|
||||
NoteLink: "notify.NoteLink",
|
||||
NoteRename: "notify.NoteRename",
|
||||
NoteRevoke: "notify.NoteRevoke",
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Platform independent event values.
|
||||
const (
|
||||
osSpecificCreate Event = 1 << (20 + iota)
|
||||
osSpecificRemove
|
||||
osSpecificWrite
|
||||
osSpecificRename
|
||||
// recursive is used to distinguish recursive eventsets from non-recursive ones
|
||||
recursive
|
||||
// omit is used for dispatching internal events; only those events are sent
|
||||
// for which both the event and the watchpoint has omit in theirs event sets.
|
||||
omit
|
||||
// dirmarker TODO(pknap)
|
||||
dirmarker
|
||||
)
|
||||
|
||||
// ReadDirectoryChangesW filters.
|
||||
const (
|
||||
FileNotifyChangeFileName = Event(syscall.FILE_NOTIFY_CHANGE_FILE_NAME)
|
||||
FileNotifyChangeDirName = Event(syscall.FILE_NOTIFY_CHANGE_DIR_NAME)
|
||||
FileNotifyChangeAttributes = Event(syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES)
|
||||
FileNotifyChangeSize = Event(syscall.FILE_NOTIFY_CHANGE_SIZE)
|
||||
FileNotifyChangeLastWrite = Event(syscall.FILE_NOTIFY_CHANGE_LAST_WRITE)
|
||||
FileNotifyChangeLastAccess = Event(syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS)
|
||||
FileNotifyChangeCreation = Event(syscall.FILE_NOTIFY_CHANGE_CREATION)
|
||||
FileNotifyChangeSecurity = Event(syscallFileNotifyChangeSecurity)
|
||||
)
|
||||
|
||||
const (
|
||||
fileNotifyChangeAll = 0x17f // logical sum of all FileNotifyChange* events.
|
||||
fileNotifyChangeModified = fileNotifyChangeAll &^ (FileNotifyChangeFileName | FileNotifyChangeDirName)
|
||||
)
|
||||
|
||||
// according to: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx
|
||||
// this flag should be declared in: http://golang.org/src/pkg/syscall/ztypes_windows.go
|
||||
const syscallFileNotifyChangeSecurity = 0x00000100
|
||||
|
||||
// ReadDirectoryChangesW actions.
|
||||
const (
|
||||
FileActionAdded = Event(syscall.FILE_ACTION_ADDED) << 12
|
||||
FileActionRemoved = Event(syscall.FILE_ACTION_REMOVED) << 12
|
||||
FileActionModified = Event(syscall.FILE_ACTION_MODIFIED) << 14
|
||||
FileActionRenamedOldName = Event(syscall.FILE_ACTION_RENAMED_OLD_NAME) << 15
|
||||
FileActionRenamedNewName = Event(syscall.FILE_ACTION_RENAMED_NEW_NAME) << 16
|
||||
)
|
||||
|
||||
const fileActionAll = 0x7f000 // logical sum of all FileAction* events.
|
||||
|
||||
var osestr = map[Event]string{
|
||||
FileNotifyChangeFileName: "notify.FileNotifyChangeFileName",
|
||||
FileNotifyChangeDirName: "notify.FileNotifyChangeDirName",
|
||||
FileNotifyChangeAttributes: "notify.FileNotifyChangeAttributes",
|
||||
FileNotifyChangeSize: "notify.FileNotifyChangeSize",
|
||||
FileNotifyChangeLastWrite: "notify.FileNotifyChangeLastWrite",
|
||||
FileNotifyChangeLastAccess: "notify.FileNotifyChangeLastAccess",
|
||||
FileNotifyChangeCreation: "notify.FileNotifyChangeCreation",
|
||||
FileNotifyChangeSecurity: "notify.FileNotifyChangeSecurity",
|
||||
|
||||
FileActionAdded: "notify.FileActionAdded",
|
||||
FileActionRemoved: "notify.FileActionRemoved",
|
||||
FileActionModified: "notify.FileActionModified",
|
||||
FileActionRenamedOldName: "notify.FileActionRenamedOldName",
|
||||
FileActionRenamedNewName: "notify.FileActionRenamedNewName",
|
||||
}
|
||||
|
||||
const (
|
||||
fTypeUnknown uint8 = iota
|
||||
fTypeFile
|
||||
fTypeDirectory
|
||||
)
|
||||
|
||||
// TODO(ppknap) : doc.
|
||||
type event struct {
|
||||
pathw []uint16
|
||||
name string
|
||||
ftype uint8
|
||||
action uint32
|
||||
filter uint32
|
||||
e Event
|
||||
}
|
||||
|
||||
func (e *event) Event() Event { return e.e }
|
||||
func (e *event) Path() string { return filepath.Join(syscall.UTF16ToString(e.pathw), e.name) }
|
||||
func (e *event) Sys() interface{} { return e.ftype }
|
||||
|
||||
func (e *event) isDir() (bool, error) {
|
||||
if e.ftype != fTypeUnknown {
|
||||
return e.ftype == fTypeDirectory, nil
|
||||
}
|
||||
fi, err := os.Stat(e.Path())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return fi.IsDir(), nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows
|
||||
// +build !kqueue,!solaris
|
||||
|
||||
package notify
|
||||
|
||||
// Platform independent event values.
|
||||
const (
|
||||
osSpecificCreate Event = 1 << iota
|
||||
osSpecificRemove
|
||||
osSpecificWrite
|
||||
osSpecificRename
|
||||
// internal
|
||||
// recursive is used to distinguish recursive eventsets from non-recursive ones
|
||||
recursive
|
||||
// omit is used for dispatching internal events; only those events are sent
|
||||
// for which both the event and the watchpoint has omit in theirs event sets.
|
||||
omit
|
||||
)
|
||||
|
||||
var osestr = map[Event]string{}
|
||||
|
||||
type event struct{}
|
||||
|
||||
func (e *event) Event() (_ Event) { return }
|
||||
func (e *event) Path() (_ string) { return }
|
||||
func (e *event) Sys() (_ interface{}) { return }
|
||||
func (e *event) isDir() (_ bool, _ error) { return }
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
|
||||
|
||||
package notify
|
||||
|
||||
type event struct {
|
||||
p string
|
||||
e Event
|
||||
d bool
|
||||
pe interface{}
|
||||
}
|
||||
|
||||
func (e *event) Event() Event { return e.e }
|
||||
|
||||
func (e *event) Path() string { return e.p }
|
||||
|
||||
func (e *event) Sys() interface{} { return e.pe }
|
||||
|
||||
func (e *event) isDir() (bool, error) { return e.d, nil }
|
|
@ -0,0 +1,271 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var errSkip = errors.New("notify: skip")
|
||||
|
||||
type walkPathFunc func(nd node, isbase bool) error
|
||||
|
||||
type walkFunc func(node) error
|
||||
|
||||
func errnotexist(name string) error {
|
||||
return &os.PathError{
|
||||
Op: "Node",
|
||||
Path: name,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
type node struct {
|
||||
Name string
|
||||
Watch watchpoint
|
||||
Child map[string]node
|
||||
}
|
||||
|
||||
func newnode(name string) node {
|
||||
return node{
|
||||
Name: name,
|
||||
Watch: make(watchpoint),
|
||||
Child: make(map[string]node),
|
||||
}
|
||||
}
|
||||
|
||||
func (nd node) addchild(name, base string) node {
|
||||
child, ok := nd.Child[base]
|
||||
if !ok {
|
||||
child = newnode(name)
|
||||
nd.Child[base] = child
|
||||
}
|
||||
return child
|
||||
}
|
||||
|
||||
func (nd node) Add(name string) node {
|
||||
i := indexbase(nd.Name, name)
|
||||
if i == -1 {
|
||||
return node{}
|
||||
}
|
||||
for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
|
||||
nd = nd.addchild(name[:i+j], name[i:i+j])
|
||||
i += j + 1
|
||||
}
|
||||
return nd.addchild(name, name[i:])
|
||||
}
|
||||
|
||||
func (nd node) AddDir(fn walkFunc) error {
|
||||
stack := []node{nd}
|
||||
Traverse:
|
||||
for n := len(stack); n != 0; n = len(stack) {
|
||||
nd, stack = stack[n-1], stack[:n-1]
|
||||
switch err := fn(nd); err {
|
||||
case nil:
|
||||
case errSkip:
|
||||
continue Traverse
|
||||
default:
|
||||
return err
|
||||
}
|
||||
// TODO(rjeczalik): tolerate open failures - add failed names to
|
||||
// AddDirError and notify users which names are not added to the tree.
|
||||
fi, err := ioutil.ReadDir(nd.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fi := range fi {
|
||||
if fi.Mode()&(os.ModeSymlink|os.ModeDir) == os.ModeDir {
|
||||
name := filepath.Join(nd.Name, fi.Name())
|
||||
stack = append(stack, nd.addchild(name, name[len(nd.Name)+1:]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nd node) Get(name string) (node, error) {
|
||||
i := indexbase(nd.Name, name)
|
||||
if i == -1 {
|
||||
return node{}, errnotexist(name)
|
||||
}
|
||||
ok := false
|
||||
for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
|
||||
if nd, ok = nd.Child[name[i:i+j]]; !ok {
|
||||
return node{}, errnotexist(name)
|
||||
}
|
||||
i += j + 1
|
||||
}
|
||||
if nd, ok = nd.Child[name[i:]]; !ok {
|
||||
return node{}, errnotexist(name)
|
||||
}
|
||||
return nd, nil
|
||||
}
|
||||
|
||||
func (nd node) Del(name string) error {
|
||||
i := indexbase(nd.Name, name)
|
||||
if i == -1 {
|
||||
return errnotexist(name)
|
||||
}
|
||||
stack := []node{nd}
|
||||
ok := false
|
||||
for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
|
||||
if nd, ok = nd.Child[name[i:i+j]]; !ok {
|
||||
return errnotexist(name[:i+j])
|
||||
}
|
||||
stack = append(stack, nd)
|
||||
}
|
||||
if nd, ok = nd.Child[name[i:]]; !ok {
|
||||
return errnotexist(name)
|
||||
}
|
||||
nd.Child = nil
|
||||
nd.Watch = nil
|
||||
for name, i = base(nd.Name), len(stack); i != 0; name, i = base(nd.Name), i-1 {
|
||||
nd = stack[i-1]
|
||||
if nd := nd.Child[name]; len(nd.Watch) > 1 || len(nd.Child) != 0 {
|
||||
break
|
||||
} else {
|
||||
nd.Child = nil
|
||||
nd.Watch = nil
|
||||
}
|
||||
delete(nd.Child, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nd node) Walk(fn walkFunc) error {
|
||||
stack := []node{nd}
|
||||
Traverse:
|
||||
for n := len(stack); n != 0; n = len(stack) {
|
||||
nd, stack = stack[n-1], stack[:n-1]
|
||||
switch err := fn(nd); err {
|
||||
case nil:
|
||||
case errSkip:
|
||||
continue Traverse
|
||||
default:
|
||||
return err
|
||||
}
|
||||
for name, nd := range nd.Child {
|
||||
if name == "" {
|
||||
// Node storing inactive watchpoints has empty name, skip it
|
||||
// form traversing. Root node has also an empty name, but it
|
||||
// never has a parent node.
|
||||
continue
|
||||
}
|
||||
stack = append(stack, nd)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nd node) WalkPath(name string, fn walkPathFunc) error {
|
||||
i := indexbase(nd.Name, name)
|
||||
if i == -1 {
|
||||
return errnotexist(name)
|
||||
}
|
||||
ok := false
|
||||
for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
|
||||
switch err := fn(nd, false); err {
|
||||
case nil:
|
||||
case errSkip:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
if nd, ok = nd.Child[name[i:i+j]]; !ok {
|
||||
return errnotexist(name[:i+j])
|
||||
}
|
||||
i += j + 1
|
||||
}
|
||||
switch err := fn(nd, false); err {
|
||||
case nil:
|
||||
case errSkip:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
if nd, ok = nd.Child[name[i:]]; !ok {
|
||||
return errnotexist(name)
|
||||
}
|
||||
switch err := fn(nd, true); err {
|
||||
case nil, errSkip:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
type root struct {
|
||||
nd node
|
||||
}
|
||||
|
||||
func (r root) addroot(name string) node {
|
||||
if vol := filepath.VolumeName(name); vol != "" {
|
||||
root, ok := r.nd.Child[vol]
|
||||
if !ok {
|
||||
root = r.nd.addchild(vol, vol)
|
||||
}
|
||||
return root
|
||||
}
|
||||
return r.nd
|
||||
}
|
||||
|
||||
func (r root) root(name string) (node, error) {
|
||||
if vol := filepath.VolumeName(name); vol != "" {
|
||||
nd, ok := r.nd.Child[vol]
|
||||
if !ok {
|
||||
return node{}, errnotexist(name)
|
||||
}
|
||||
return nd, nil
|
||||
}
|
||||
return r.nd, nil
|
||||
}
|
||||
|
||||
func (r root) Add(name string) node {
|
||||
return r.addroot(name).Add(name)
|
||||
}
|
||||
|
||||
func (r root) AddDir(dir string, fn walkFunc) error {
|
||||
return r.Add(dir).AddDir(fn)
|
||||
}
|
||||
|
||||
func (r root) Del(name string) error {
|
||||
nd, err := r.root(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nd.Del(name)
|
||||
}
|
||||
|
||||
func (r root) Get(name string) (node, error) {
|
||||
nd, err := r.root(name)
|
||||
if err != nil {
|
||||
return node{}, err
|
||||
}
|
||||
if nd.Name != name {
|
||||
if nd, err = nd.Get(name); err != nil {
|
||||
return node{}, err
|
||||
}
|
||||
}
|
||||
return nd, nil
|
||||
}
|
||||
|
||||
func (r root) Walk(name string, fn walkFunc) error {
|
||||
nd, err := r.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nd.Walk(fn)
|
||||
}
|
||||
|
||||
func (r root) WalkPath(name string, fn walkPathFunc) error {
|
||||
nd, err := r.root(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nd.WalkPath(name, fn)
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// BUG(rjeczalik): Notify does not collect watchpoints, when underlying watches
|
||||
// were removed by their os-specific watcher implementations. Instead users are
|
||||
// advised to listen on persistant paths to have guarantee they receive events
|
||||
// for the whole lifetime of their applications (to discuss see #69).
|
||||
|
||||
// BUG(ppknap): Linux (inotify) does not support watcher behavior masks like
|
||||
// InOneshot, InOnlydir etc. Instead users are advised to perform the filtering
|
||||
// themselves (to discuss see #71).
|
||||
|
||||
// BUG(ppknap): Notify was not tested for short path name support under Windows
|
||||
// (ReadDirectoryChangesW).
|
||||
|
||||
// BUG(ppknap): Windows (ReadDirectoryChangesW) cannot recognize which notification
|
||||
// triggers FileActionModified event. (to discuss see #75).
|
||||
|
||||
package notify
|
||||
|
||||
var defaultTree = newTree()
|
||||
|
||||
// Watch sets up a watchpoint on path listening for events given by the events
|
||||
// argument.
|
||||
//
|
||||
// File or directory given by the path must exist, otherwise Watch will fail
|
||||
// with non-nil error. Notify resolves, for its internal purpose, any symlinks
|
||||
// the provided path may contain, so it may fail if the symlinks form a cycle.
|
||||
// It does so, since not all watcher implementations treat passed paths as-is.
|
||||
// E.g. FSEvents reports a real path for every event, setting a watchpoint
|
||||
// on /tmp will report events with paths rooted at /private/tmp etc.
|
||||
//
|
||||
// The c almost always is a buffered channel. Watch will not block sending to c
|
||||
// - the caller must ensure that c has sufficient buffer space to keep up with
|
||||
// the expected event rate.
|
||||
//
|
||||
// It is allowed to pass the same channel multiple times with different event
|
||||
// list or different paths. Calling Watch with different event lists for a single
|
||||
// watchpoint expands its event set. The only way to shrink it, is to call
|
||||
// Stop on its channel.
|
||||
//
|
||||
// Calling Watch with empty event list does expand nor shrink watchpoint's event
|
||||
// set. If c is the first channel to listen for events on the given path, Watch
|
||||
// will seamlessly create a watch on the filesystem.
|
||||
//
|
||||
// Notify dispatches copies of single filesystem event to all channels registered
|
||||
// for each path. If a single filesystem event contains multiple coalesced events,
|
||||
// each of them is dispatched separately. E.g. the following filesystem change:
|
||||
//
|
||||
// ~ $ echo Hello > Notify.txt
|
||||
//
|
||||
// dispatches two events - notify.Create and notify.Write. However, it may depend
|
||||
// on the underlying watcher implementation whether OS reports both of them.
|
||||
//
|
||||
// Windows and recursive watches
|
||||
//
|
||||
// If a directory which path was used to create recursive watch under Windows
|
||||
// gets deleted, the OS will not report such event. It is advised to keep in
|
||||
// mind this limitation while setting recursive watchpoints for your application,
|
||||
// e.g. use persistant paths like %userprofile% or watch additionally parent
|
||||
// directory of a recursive watchpoint in order to receive delete events for it.
|
||||
func Watch(path string, c chan<- EventInfo, events ...Event) error {
|
||||
return defaultTree.Watch(path, c, events...)
|
||||
}
|
||||
|
||||
// Stop removes all watchpoints registered for c. All underlying watches are
|
||||
// also removed, for which c was the last channel listening for events.
|
||||
//
|
||||
// Stop does not close c. When Stop returns, it is guranteed that c will
|
||||
// receive no more signals.
|
||||
func Stop(c chan<- EventInfo) {
|
||||
defaultTree.Stop(c)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package notify
|
||||
|
||||
const buffer = 128
|
||||
|
||||
type tree interface {
|
||||
Watch(string, chan<- EventInfo, ...Event) error
|
||||
Stop(chan<- EventInfo)
|
||||
Close() error
|
||||
}
|
||||
|
||||
func newTree() tree {
|
||||
c := make(chan EventInfo, buffer)
|
||||
w := newWatcher(c)
|
||||
if rw, ok := w.(recursiveWatcher); ok {
|
||||
return newRecursiveTree(rw, c)
|
||||
}
|
||||
return newNonrecursiveTree(w, c, make(chan EventInfo, buffer))
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package notify
|
||||
|
||||
import "sync"
|
||||
|
||||
// nonrecursiveTree TODO(rjeczalik)
|
||||
type nonrecursiveTree struct {
|
||||
rw sync.RWMutex // protects root
|
||||
root root
|
||||
w watcher
|
||||
c chan EventInfo
|
||||
rec chan EventInfo
|
||||
}
|
||||
|
||||
// newNonrecursiveTree TODO(rjeczalik)
|
||||
func newNonrecursiveTree(w watcher, c, rec chan EventInfo) *nonrecursiveTree {
|
||||
if rec == nil {
|
||||
rec = make(chan EventInfo, buffer)
|
||||
}
|
||||
t := &nonrecursiveTree{
|
||||
root: root{nd: newnode("")},
|
||||
w: w,
|
||||
c: c,
|
||||
rec: rec,
|
||||
}
|
||||
go t.dispatch(c)
|
||||
go t.internal(rec)
|
||||
return t
|
||||
}
|
||||
|
||||
// dispatch TODO(rjeczalik)
|
||||
func (t *nonrecursiveTree) dispatch(c <-chan EventInfo) {
|
||||
for ei := range c {
|
||||
dbgprintf("dispatching %v on %q", ei.Event(), ei.Path())
|
||||
go func(ei EventInfo) {
|
||||
var nd node
|
||||
var isrec bool
|
||||
dir, base := split(ei.Path())
|
||||
fn := func(it node, isbase bool) error {
|
||||
isrec = isrec || it.Watch.IsRecursive()
|
||||
if isbase {
|
||||
nd = it
|
||||
} else {
|
||||
it.Watch.Dispatch(ei, recursive)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
t.rw.RLock()
|
||||
// Notify recursive watchpoints found on the path.
|
||||
if err := t.root.WalkPath(dir, fn); err != nil {
|
||||
dbgprint("dispatch did not reach leaf:", err)
|
||||
t.rw.RUnlock()
|
||||
return
|
||||
}
|
||||
// Notify parent watchpoint.
|
||||
nd.Watch.Dispatch(ei, 0)
|
||||
isrec = isrec || nd.Watch.IsRecursive()
|
||||
// If leaf watchpoint exists, notify it.
|
||||
if nd, ok := nd.Child[base]; ok {
|
||||
isrec = isrec || nd.Watch.IsRecursive()
|
||||
nd.Watch.Dispatch(ei, 0)
|
||||
}
|
||||
t.rw.RUnlock()
|
||||
// If the event describes newly leaf directory created within
|
||||
if !isrec || ei.Event() != Create {
|
||||
return
|
||||
}
|
||||
if ok, err := ei.(isDirer).isDir(); !ok || err != nil {
|
||||
return
|
||||
}
|
||||
t.rec <- ei
|
||||
}(ei)
|
||||
}
|
||||
}
|
||||
|
||||
// internal TODO(rjeczalik)
|
||||
func (t *nonrecursiveTree) internal(rec <-chan EventInfo) {
|
||||
for ei := range rec {
|
||||
var nd node
|
||||
var eset = internal
|
||||
t.rw.Lock()
|
||||
t.root.WalkPath(ei.Path(), func(it node, _ bool) error {
|
||||
if e := it.Watch[t.rec]; e != 0 && e > eset {
|
||||
eset = e
|
||||
}
|
||||
nd = it
|
||||
return nil
|
||||
})
|
||||
if eset == internal {
|
||||
t.rw.Unlock()
|
||||
continue
|
||||
}
|
||||
err := nd.Add(ei.Path()).AddDir(t.recFunc(eset))
|
||||
t.rw.Unlock()
|
||||
if err != nil {
|
||||
dbgprintf("internal(%p) error: %v", rec, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// watchAdd TODO(rjeczalik)
|
||||
func (t *nonrecursiveTree) watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff {
|
||||
if e&recursive != 0 {
|
||||
diff := nd.Watch.Add(t.rec, e|Create|omit)
|
||||
nd.Watch.Add(c, e)
|
||||
return diff
|
||||
}
|
||||
return nd.Watch.Add(c, e)
|
||||
}
|
||||
|
||||
// watchDelMin TODO(rjeczalik)
|
||||
func (t *nonrecursiveTree) watchDelMin(min Event, nd node, c chan<- EventInfo, e Event) eventDiff {
|
||||
old, ok := nd.Watch[t.rec]
|
||||
if ok {
|
||||
nd.Watch[t.rec] = min
|
||||
}
|
||||
diff := nd.Watch.Del(c, e)
|
||||
if ok {
|
||||
switch old &^= diff[0] &^ diff[1]; {
|
||||
case old|internal == internal:
|
||||
delete(nd.Watch, t.rec)
|
||||
if set, ok := nd.Watch[nil]; ok && len(nd.Watch) == 1 && set == 0 {
|
||||
delete(nd.Watch, nil)
|
||||
}
|
||||
default:
|
||||
nd.Watch.Add(t.rec, old|Create)
|
||||
switch {
|
||||
case diff == none:
|
||||
case diff[1]|Create == diff[0]:
|
||||
diff = none
|
||||
default:
|
||||
diff[1] |= Create
|
||||
}
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// watchDel TODO(rjeczalik)
|
||||
func (t *nonrecursiveTree) watchDel(nd node, c chan<- EventInfo, e Event) eventDiff {
|
||||
return t.watchDelMin(0, nd, c, e)
|
||||
}
|
||||
|
||||
// Watch TODO(rjeczalik)
|
||||
func (t *nonrecursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error {
|
||||
if c == nil {
|
||||
panic("notify: Watch using nil channel")
|
||||
}
|
||||
// Expanding with empty event set is a nop.
|
||||
if len(events) == 0 {
|
||||
return nil
|
||||
}
|
||||
path, isrec, err := cleanpath(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eset := joinevents(events)
|
||||
t.rw.Lock()
|
||||
defer t.rw.Unlock()
|
||||
nd := t.root.Add(path)
|
||||
if isrec {
|
||||
return t.watchrec(nd, c, eset|recursive)
|
||||
}
|
||||
return t.watch(nd, c, eset)
|
||||
}
|
||||
|
||||
func (t *nonrecursiveTree) watch(nd node, c chan<- EventInfo, e Event) (err error) {
|
||||
diff := nd.Watch.Add(c, e)
|
||||
switch {
|
||||
case diff == none:
|
||||
return nil
|
||||
case diff[1] == 0:
|
||||
// TODO(rjeczalik): cleanup this panic after implementation is stable
|
||||
panic("eset is empty: " + nd.Name)
|
||||
case diff[0] == 0:
|
||||
err = t.w.Watch(nd.Name, diff[1])
|
||||
default:
|
||||
err = t.w.Rewatch(nd.Name, diff[0], diff[1])
|
||||
}
|
||||
if err != nil {
|
||||
nd.Watch.Del(c, diff.Event())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *nonrecursiveTree) recFunc(e Event) walkFunc {
|
||||
return func(nd node) error {
|
||||
switch diff := nd.Watch.Add(t.rec, e|omit|Create); {
|
||||
case diff == none:
|
||||
case diff[1] == 0:
|
||||
// TODO(rjeczalik): cleanup this panic after implementation is stable
|
||||
panic("eset is empty: " + nd.Name)
|
||||
case diff[0] == 0:
|
||||
t.w.Watch(nd.Name, diff[1])
|
||||
default:
|
||||
t.w.Rewatch(nd.Name, diff[0], diff[1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *nonrecursiveTree) watchrec(nd node, c chan<- EventInfo, e Event) error {
|
||||
var traverse func(walkFunc) error
|
||||
// Non-recursive tree listens on Create event for every recursive
|
||||
// watchpoint in order to automagically set a watch for every
|
||||
// created directory.
|
||||
switch diff := nd.Watch.dryAdd(t.rec, e|Create); {
|
||||
case diff == none:
|
||||
t.watchAdd(nd, c, e)
|
||||
nd.Watch.Add(t.rec, e|omit|Create)
|
||||
return nil
|
||||
case diff[1] == 0:
|
||||
// TODO(rjeczalik): cleanup this panic after implementation is stable
|
||||
panic("eset is empty: " + nd.Name)
|
||||
case diff[0] == 0:
|
||||
// TODO(rjeczalik): BFS into directories and skip subtree as soon as first
|
||||
// recursive watchpoint is encountered.
|
||||
traverse = nd.AddDir
|
||||
default:
|
||||
traverse = nd.Walk
|
||||
}
|
||||
// TODO(rjeczalik): account every path that failed to be (re)watched
|
||||
// and retry.
|
||||
if err := traverse(t.recFunc(e)); err != nil {
|
||||
return err
|
||||
}
|
||||
t.watchAdd(nd, c, e)
|
||||
return nil
|
||||
}
|
||||
|
||||
type walkWatchpointFunc func(Event, node) error
|
||||
|
||||
func (t *nonrecursiveTree) walkWatchpoint(nd node, fn walkWatchpointFunc) error {
|
||||
type minode struct {
|
||||
min Event
|
||||
nd node
|
||||
}
|
||||
mnd := minode{nd: nd}
|
||||
stack := []minode{mnd}
|
||||
Traverse:
|
||||
for n := len(stack); n != 0; n = len(stack) {
|
||||
mnd, stack = stack[n-1], stack[:n-1]
|
||||
// There must be no recursive watchpoints if the node has no watchpoints
|
||||
// itself (every node in subtree rooted at recursive watchpoints must
|
||||
// have at least nil (total) and t.rec watchpoints).
|
||||
if len(mnd.nd.Watch) != 0 {
|
||||
switch err := fn(mnd.min, mnd.nd); err {
|
||||
case nil:
|
||||
case errSkip:
|
||||
continue Traverse
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, nd := range mnd.nd.Child {
|
||||
stack = append(stack, minode{mnd.nd.Watch[t.rec], nd})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop TODO(rjeczalik)
|
||||
func (t *nonrecursiveTree) Stop(c chan<- EventInfo) {
|
||||
fn := func(min Event, nd node) error {
|
||||
// TODO(rjeczalik): aggregate watcher errors and retry; in worst case
|
||||
// forward to the user.
|
||||
switch diff := t.watchDelMin(min, nd, c, all); {
|
||||
case diff == none:
|
||||
return nil
|
||||
case diff[1] == 0:
|
||||
t.w.Unwatch(nd.Name)
|
||||
default:
|
||||
t.w.Rewatch(nd.Name, diff[0], diff[1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
t.rw.Lock()
|
||||
err := t.walkWatchpoint(t.root.nd, fn) // TODO(rjeczalik): store max root per c
|
||||
t.rw.Unlock()
|
||||
dbgprintf("Stop(%p) error: %v\n", c, err)
|
||||
}
|
||||
|
||||
// Close TODO(rjeczalik)
|
||||
func (t *nonrecursiveTree) Close() error {
|
||||
err := t.w.Close()
|
||||
close(t.c)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,354 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package notify
|
||||
|
||||
import "sync"
|
||||
|
||||
// watchAdd TODO(rjeczalik)
|
||||
func watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff {
|
||||
diff := nd.Watch.Add(c, e)
|
||||
if wp := nd.Child[""].Watch; len(wp) != 0 {
|
||||
e = wp.Total()
|
||||
diff[0] |= e
|
||||
diff[1] |= e
|
||||
if diff[0] == diff[1] {
|
||||
return none
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// watchAddInactive TODO(rjeczalik)
|
||||
func watchAddInactive(nd node, c chan<- EventInfo, e Event) eventDiff {
|
||||
wp := nd.Child[""].Watch
|
||||
if wp == nil {
|
||||
wp = make(watchpoint)
|
||||
nd.Child[""] = node{Watch: wp}
|
||||
}
|
||||
diff := wp.Add(c, e)
|
||||
e = nd.Watch.Total()
|
||||
diff[0] |= e
|
||||
diff[1] |= e
|
||||
if diff[0] == diff[1] {
|
||||
return none
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// watchCopy TODO(rjeczalik)
|
||||
func watchCopy(src, dst node) {
|
||||
for c, e := range src.Watch {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
watchAddInactive(dst, c, e)
|
||||
}
|
||||
if wpsrc := src.Child[""].Watch; len(wpsrc) != 0 {
|
||||
wpdst := dst.Child[""].Watch
|
||||
for c, e := range wpsrc {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
wpdst.Add(c, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// watchDel TODO(rjeczalik)
|
||||
func watchDel(nd node, c chan<- EventInfo, e Event) eventDiff {
|
||||
diff := nd.Watch.Del(c, e)
|
||||
if wp := nd.Child[""].Watch; len(wp) != 0 {
|
||||
diffInactive := wp.Del(c, e)
|
||||
e = wp.Total()
|
||||
// TODO(rjeczalik): add e if e != all?
|
||||
diff[0] |= diffInactive[0] | e
|
||||
diff[1] |= diffInactive[1] | e
|
||||
if diff[0] == diff[1] {
|
||||
return none
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// watchTotal TODO(rjeczalik)
|
||||
func watchTotal(nd node) Event {
|
||||
e := nd.Watch.Total()
|
||||
if wp := nd.Child[""].Watch; len(wp) != 0 {
|
||||
e |= wp.Total()
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// watchIsRecursive TODO(rjeczalik)
|
||||
func watchIsRecursive(nd node) bool {
|
||||
ok := nd.Watch.IsRecursive()
|
||||
// TODO(rjeczalik): add a test for len(wp) != 0 change the condition.
|
||||
if wp := nd.Child[""].Watch; len(wp) != 0 {
|
||||
// If a watchpoint holds inactive watchpoints, it means it's a parent
|
||||
// one, which is recursive by nature even though it may be not recursive
|
||||
// itself.
|
||||
ok = true
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// recursiveTree TODO(rjeczalik)
|
||||
type recursiveTree struct {
|
||||
rw sync.RWMutex // protects root
|
||||
root root
|
||||
// TODO(rjeczalik): merge watcher + recursiveWatcher after #5 and #6
|
||||
w interface {
|
||||
watcher
|
||||
recursiveWatcher
|
||||
}
|
||||
c chan EventInfo
|
||||
}
|
||||
|
||||
// newRecursiveTree TODO(rjeczalik)
|
||||
func newRecursiveTree(w recursiveWatcher, c chan EventInfo) *recursiveTree {
|
||||
t := &recursiveTree{
|
||||
root: root{nd: newnode("")},
|
||||
w: struct {
|
||||
watcher
|
||||
recursiveWatcher
|
||||
}{w.(watcher), w},
|
||||
c: c,
|
||||
}
|
||||
go t.dispatch()
|
||||
return t
|
||||
}
|
||||
|
||||
// dispatch TODO(rjeczalik)
|
||||
func (t *recursiveTree) dispatch() {
|
||||
for ei := range t.c {
|
||||
dbgprintf("dispatching %v on %q", ei.Event(), ei.Path())
|
||||
go func(ei EventInfo) {
|
||||
nd, ok := node{}, false
|
||||
dir, base := split(ei.Path())
|
||||
fn := func(it node, isbase bool) error {
|
||||
if isbase {
|
||||
nd = it
|
||||
} else {
|
||||
it.Watch.Dispatch(ei, recursive)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
t.rw.RLock()
|
||||
defer t.rw.RUnlock()
|
||||
// Notify recursive watchpoints found on the path.
|
||||
if err := t.root.WalkPath(dir, fn); err != nil {
|
||||
dbgprint("dispatch did not reach leaf:", err)
|
||||
return
|
||||
}
|
||||
// Notify parent watchpoint.
|
||||
nd.Watch.Dispatch(ei, 0)
|
||||
// If leaf watchpoint exists, notify it.
|
||||
if nd, ok = nd.Child[base]; ok {
|
||||
nd.Watch.Dispatch(ei, 0)
|
||||
}
|
||||
}(ei)
|
||||
}
|
||||
}
|
||||
|
||||
// Watch TODO(rjeczalik)
|
||||
func (t *recursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error {
|
||||
if c == nil {
|
||||
panic("notify: Watch using nil channel")
|
||||
}
|
||||
// Expanding with empty event set is a nop.
|
||||
if len(events) == 0 {
|
||||
return nil
|
||||
}
|
||||
path, isrec, err := cleanpath(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eventset := joinevents(events)
|
||||
if isrec {
|
||||
eventset |= recursive
|
||||
}
|
||||
t.rw.Lock()
|
||||
defer t.rw.Unlock()
|
||||
// case 1: cur is a child
|
||||
//
|
||||
// Look for parent watch which already covers the given path.
|
||||
parent := node{}
|
||||
self := false
|
||||
err = t.root.WalkPath(path, func(nd node, isbase bool) error {
|
||||
if watchTotal(nd) != 0 {
|
||||
parent = nd
|
||||
self = isbase
|
||||
return errSkip
|
||||
}
|
||||
return nil
|
||||
})
|
||||
cur := t.root.Add(path) // add after the walk, so it's less to traverse
|
||||
if err == nil && parent.Watch != nil {
|
||||
// Parent watch found. Register inactive watchpoint, so we have enough
|
||||
// information to shrink the eventset on eventual Stop.
|
||||
// return t.resetwatchpoint(parent, parent, c, eventset|inactive)
|
||||
var diff eventDiff
|
||||
if self {
|
||||
diff = watchAdd(cur, c, eventset)
|
||||
} else {
|
||||
diff = watchAddInactive(parent, c, eventset)
|
||||
}
|
||||
switch {
|
||||
case diff == none:
|
||||
// the parent watchpoint already covers requested subtree with its
|
||||
// eventset
|
||||
case diff[0] == 0:
|
||||
// TODO(rjeczalik): cleanup this panic after implementation is stable
|
||||
panic("dangling watchpoint: " + parent.Name)
|
||||
default:
|
||||
if isrec || watchIsRecursive(parent) {
|
||||
err = t.w.RecursiveRewatch(parent.Name, parent.Name, diff[0], diff[1])
|
||||
} else {
|
||||
err = t.w.Rewatch(parent.Name, diff[0], diff[1])
|
||||
}
|
||||
if err != nil {
|
||||
watchDel(parent, c, diff.Event())
|
||||
return err
|
||||
}
|
||||
watchAdd(cur, c, eventset)
|
||||
// TODO(rjeczalik): account top-most path for c
|
||||
return nil
|
||||
}
|
||||
if !self {
|
||||
watchAdd(cur, c, eventset)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// case 2: cur is new parent
|
||||
//
|
||||
// Look for children nodes, unwatch n-1 of them and rewatch the last one.
|
||||
var children []node
|
||||
fn := func(nd node) error {
|
||||
if len(nd.Watch) == 0 {
|
||||
return nil
|
||||
}
|
||||
children = append(children, nd)
|
||||
return errSkip
|
||||
}
|
||||
switch must(cur.Walk(fn)); len(children) {
|
||||
case 0:
|
||||
// no child watches, cur holds a new watch
|
||||
case 1:
|
||||
watchAdd(cur, c, eventset) // TODO(rjeczalik): update cache c subtree root?
|
||||
watchCopy(children[0], cur)
|
||||
err = t.w.RecursiveRewatch(children[0].Name, cur.Name, watchTotal(children[0]),
|
||||
watchTotal(cur))
|
||||
if err != nil {
|
||||
// Clean inactive watchpoint. The c chan did not exist before.
|
||||
cur.Child[""] = node{}
|
||||
delete(cur.Watch, c)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
watchAdd(cur, c, eventset)
|
||||
// Copy children inactive watchpoints to the new parent.
|
||||
for _, nd := range children {
|
||||
watchCopy(nd, cur)
|
||||
}
|
||||
// Watch parent subtree.
|
||||
if err = t.w.RecursiveWatch(cur.Name, watchTotal(cur)); err != nil {
|
||||
// Clean inactive watchpoint. The c chan did not exist before.
|
||||
cur.Child[""] = node{}
|
||||
delete(cur.Watch, c)
|
||||
return err
|
||||
}
|
||||
// Unwatch children subtrees.
|
||||
var e error
|
||||
for _, nd := range children {
|
||||
if watchIsRecursive(nd) {
|
||||
e = t.w.RecursiveUnwatch(nd.Name)
|
||||
} else {
|
||||
e = t.w.Unwatch(nd.Name)
|
||||
}
|
||||
if e != nil {
|
||||
err = nonil(err, e)
|
||||
// TODO(rjeczalik): child is still watched, warn all its watchpoints
|
||||
// about possible duplicate events via Error event
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
// case 3: cur is new, alone node
|
||||
switch diff := watchAdd(cur, c, eventset); {
|
||||
case diff == none:
|
||||
// TODO(rjeczalik): cleanup this panic after implementation is stable
|
||||
panic("watch requested but no parent watchpoint found: " + cur.Name)
|
||||
case diff[0] == 0:
|
||||
if isrec {
|
||||
err = t.w.RecursiveWatch(cur.Name, diff[1])
|
||||
} else {
|
||||
err = t.w.Watch(cur.Name, diff[1])
|
||||
}
|
||||
if err != nil {
|
||||
watchDel(cur, c, diff.Event())
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// TODO(rjeczalik): cleanup this panic after implementation is stable
|
||||
panic("watch requested but no parent watchpoint found: " + cur.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop TODO(rjeczalik)
|
||||
//
|
||||
// TODO(rjeczalik): Split parent watchpoint - transfer watches to children
|
||||
// if parent is no longer needed. This carries a risk that underlying
|
||||
// watcher calls could fail - reconsider if it's worth the effort.
|
||||
func (t *recursiveTree) Stop(c chan<- EventInfo) {
|
||||
var err error
|
||||
fn := func(nd node) (e error) {
|
||||
diff := watchDel(nd, c, all)
|
||||
switch {
|
||||
case diff == none && watchTotal(nd) == 0:
|
||||
// TODO(rjeczalik): There's no watchpoints deeper in the tree,
|
||||
// probably we should remove the nodes as well.
|
||||
return nil
|
||||
case diff == none:
|
||||
// Removing c from nd does not require shrinking its eventset.
|
||||
case diff[1] == 0:
|
||||
if watchIsRecursive(nd) {
|
||||
e = t.w.RecursiveUnwatch(nd.Name)
|
||||
} else {
|
||||
e = t.w.Unwatch(nd.Name)
|
||||
}
|
||||
default:
|
||||
if watchIsRecursive(nd) {
|
||||
e = t.w.RecursiveRewatch(nd.Name, nd.Name, diff[0], diff[1])
|
||||
} else {
|
||||
e = t.w.Rewatch(nd.Name, diff[0], diff[1])
|
||||
}
|
||||
}
|
||||
fn := func(nd node) error {
|
||||
watchDel(nd, c, all)
|
||||
return nil
|
||||
}
|
||||
err = nonil(err, e, nd.Walk(fn))
|
||||
// TODO(rjeczalik): if e != nil store dummy chan in nd.Watch just to
|
||||
// retry un/rewatching next time and/or let the user handle the failure
|
||||
// vie Error event?
|
||||
return errSkip
|
||||
}
|
||||
t.rw.Lock()
|
||||
e := t.root.Walk("", fn) // TODO(rjeczalik): use max root per c
|
||||
t.rw.Unlock()
|
||||
if e != nil {
|
||||
err = nonil(err, e)
|
||||
}
|
||||
dbgprintf("Stop(%p) error: %v\n", c, err)
|
||||
}
|
||||
|
||||
// Close TODO(rjeczalik)
|
||||
func (t *recursiveTree) Close() error {
|
||||
err := t.w.Close()
|
||||
close(t.c)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const all = ^Event(0)
|
||||
const sep = string(os.PathSeparator)
|
||||
|
||||
var errDepth = errors.New("exceeded allowed iteration count (circular symlink?)")
|
||||
|
||||
func min(i, j int) int {
|
||||
if i > j {
|
||||
return j
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func max(i, j int) int {
|
||||
if i < j {
|
||||
return j
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// must panics if err is non-nil.
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// nonil gives first non-nil error from the given arguments.
|
||||
func nonil(err ...error) error {
|
||||
for _, err := range err {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanpath(path string) (realpath string, isrec bool, err error) {
|
||||
if strings.HasSuffix(path, "...") {
|
||||
isrec = true
|
||||
path = path[:len(path)-3]
|
||||
}
|
||||
if path, err = filepath.Abs(path); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if path, err = canonical(path); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return path, isrec, nil
|
||||
}
|
||||
|
||||
// canonical resolves any symlink in the given path and returns it in a clean form.
|
||||
// It expects the path to be absolute. It fails to resolve circular symlinks by
|
||||
// maintaining a simple iteration limit.
|
||||
func canonical(p string) (string, error) {
|
||||
p, err := filepath.Abs(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for i, j, depth := 1, 0, 1; i < len(p); i, depth = i+1, depth+1 {
|
||||
if depth > 128 {
|
||||
return "", &os.PathError{Op: "canonical", Path: p, Err: errDepth}
|
||||
}
|
||||
if j = strings.IndexRune(p[i:], '/'); j == -1 {
|
||||
j, i = i, len(p)
|
||||
} else {
|
||||
j, i = i, i+j
|
||||
}
|
||||
fi, err := os.Lstat(p[:i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
s, err := os.Readlink(p[:i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if filepath.IsAbs(s) {
|
||||
p = "/" + s + p[i:]
|
||||
} else {
|
||||
p = p[:j] + s + p[i:]
|
||||
}
|
||||
i = 1 // no guarantee s is canonical, start all over
|
||||
}
|
||||
}
|
||||
return filepath.Clean(p), nil
|
||||
}
|
||||
|
||||
func joinevents(events []Event) (e Event) {
|
||||
if len(events) == 0 {
|
||||
e = All
|
||||
} else {
|
||||
for _, event := range events {
|
||||
e |= event
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func split(s string) (string, string) {
|
||||
if i := lastIndexSep(s); i != -1 {
|
||||
return s[:i], s[i+1:]
|
||||
}
|
||||
return "", s
|
||||
}
|
||||
|
||||
func base(s string) string {
|
||||
if i := lastIndexSep(s); i != -1 {
|
||||
return s[i+1:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func indexbase(root, name string) int {
|
||||
if n, m := len(root), len(name); m >= n && name[:n] == root &&
|
||||
(n == m || name[n] == os.PathSeparator) {
|
||||
return min(n+1, m)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func indexSep(s string) int {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == os.PathSeparator {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func lastIndexSep(s string) int {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == os.PathSeparator {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package notify
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errAlreadyWatched = errors.New("path is already watched")
|
||||
errNotWatched = errors.New("path is not being watched")
|
||||
errInvalidEventSet = errors.New("invalid event set provided")
|
||||
)
|
||||
|
||||
// Watcher is a intermediate interface for wrapping inotify, ReadDirChangesW,
|
||||
// FSEvents, kqueue and poller implementations.
|
||||
//
|
||||
// The watcher implementation is expected to do its own mapping between paths and
|
||||
// create watchers if underlying event notification does not support it. For
|
||||
// the ease of implementation it is guaranteed that paths provided via Watch and
|
||||
// Unwatch methods are absolute and clean.
|
||||
type watcher interface {
|
||||
// Watch requests a watcher creation for the given path and given event set.
|
||||
Watch(path string, event Event) error
|
||||
|
||||
// Unwatch requests a watcher deletion for the given path and given event set.
|
||||
Unwatch(path string) error
|
||||
|
||||
// Rewatch provides a functionality for modifying existing watch-points, like
|
||||
// expanding its event set.
|
||||
//
|
||||
// Rewatch modifies existing watch-point under for the given path. It passes
|
||||
// the existing event set currently registered for the given path, and the
|
||||
// new, requested event set.
|
||||
//
|
||||
// It is guaranteed that Tree will not pass to Rewatch zero value for any
|
||||
// of its arguments. If old == new and watcher can be upgraded to
|
||||
// recursiveWatcher interface, a watch for the corresponding path is expected
|
||||
// to be changed from recursive to the non-recursive one.
|
||||
Rewatch(path string, old, new Event) error
|
||||
|
||||
// Close unwatches all paths that are registered. When Close returns, it
|
||||
// is expected it will report no more events.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// RecursiveWatcher is an interface for a Watcher for those OS, which do support
|
||||
// recursive watching over directories.
|
||||
type recursiveWatcher interface {
|
||||
RecursiveWatch(path string, event Event) error
|
||||
|
||||
// RecursiveUnwatch removes a recursive watch-point given by the path. For
|
||||
// native recursive implementation there is no difference in functionality
|
||||
// between Unwatch and RecursiveUnwatch, however for those platforms, that
|
||||
// requires emulation for recursive watch-points, the implementation differs.
|
||||
RecursiveUnwatch(path string) error
|
||||
|
||||
// RecursiveRewatcher provides a functionality for modifying and/or relocating
|
||||
// existing recursive watch-points.
|
||||
//
|
||||
// To relocate a watch-point means to unwatch oldpath and set a watch-point on
|
||||
// newpath.
|
||||
//
|
||||
// To modify a watch-point means either to expand or shrink its event set.
|
||||
//
|
||||
// Tree can want to either relocate, modify or relocate and modify a watch-point
|
||||
// via single RecursiveRewatch call.
|
||||
//
|
||||
// If oldpath == newpath, the watch-point is expected to change its event set value
|
||||
// from oldevent to newevent.
|
||||
//
|
||||
// If oldevent == newevent, the watch-point is expected to relocate from oldpath
|
||||
// to the newpath.
|
||||
//
|
||||
// If oldpath != newpath and oldevent != newevent, the watch-point is expected
|
||||
// to relocate from oldpath to the newpath first and then change its event set
|
||||
// value from oldevent to the newevent. In other words the end result must be
|
||||
// a watch-point set on newpath with newevent value of its event set.
|
||||
//
|
||||
// It is guaranteed that Tree will not pass to RecurisveRewatcha zero value
|
||||
// for any of its arguments. If oldpath == newpath and oldevent == newevent,
|
||||
// a watch for the corresponding path is expected to be changed for
|
||||
// non-recursive to the recursive one.
|
||||
RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// newTrigger returns implementation of trigger.
|
||||
func newTrigger(pthLkp map[string]*watched) trigger {
|
||||
return &fen{
|
||||
pthLkp: pthLkp,
|
||||
cf: newCfen(),
|
||||
}
|
||||
}
|
||||
|
||||
// fen is a structure implementing trigger for FEN.
|
||||
type fen struct {
|
||||
// p is a FEN port identifier
|
||||
p int
|
||||
// pthLkp is a structure mapping monitored files/dir with data about them,
|
||||
// shared with parent trg structure
|
||||
pthLkp map[string]*watched
|
||||
// cf wraps C operations for FEN
|
||||
cf cfen
|
||||
}
|
||||
|
||||
// watched is a data structure representing watched file/directory.
|
||||
type watched struct {
|
||||
// p is a path to watched file/directory
|
||||
p string
|
||||
// fi provides information about watched file/dir
|
||||
fi os.FileInfo
|
||||
// eDir represents events watched directly
|
||||
eDir Event
|
||||
// eNonDir represents events watched indirectly
|
||||
eNonDir Event
|
||||
}
|
||||
|
||||
// Stop implements trigger.
|
||||
func (f *fen) Stop() error {
|
||||
return f.cf.port_alert(f.p)
|
||||
}
|
||||
|
||||
// Close implements trigger.
|
||||
func (f *fen) Close() (err error) {
|
||||
return syscall.Close(f.p)
|
||||
}
|
||||
|
||||
// NewWatched implements trigger.
|
||||
func (*fen) NewWatched(p string, fi os.FileInfo) (*watched, error) {
|
||||
return &watched{p: p, fi: fi}, nil
|
||||
}
|
||||
|
||||
// Record implements trigger.
|
||||
func (f *fen) Record(w *watched) {
|
||||
f.pthLkp[w.p] = w
|
||||
}
|
||||
|
||||
// Del implements trigger.
|
||||
func (f *fen) Del(w *watched) {
|
||||
delete(f.pthLkp, w.p)
|
||||
}
|
||||
|
||||
func inter2pe(n interface{}) PortEvent {
|
||||
pe, ok := n.(PortEvent)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("fen: type should be PortEvent, %T instead", n))
|
||||
}
|
||||
return pe
|
||||
}
|
||||
|
||||
// Watched implements trigger.
|
||||
func (f *fen) Watched(n interface{}) (*watched, int64, error) {
|
||||
pe := inter2pe(n)
|
||||
fo, ok := pe.PortevObject.(*FileObj)
|
||||
if !ok || fo == nil {
|
||||
panic(fmt.Sprintf("fen: type should be *FileObj, %T instead", fo))
|
||||
}
|
||||
w, ok := f.pthLkp[fo.Name]
|
||||
if !ok {
|
||||
return nil, 0, errNotWatched
|
||||
}
|
||||
return w, int64(pe.PortevEvents), nil
|
||||
}
|
||||
|
||||
// init initializes FEN.
|
||||
func (f *fen) Init() (err error) {
|
||||
f.p, err = f.cf.port_create()
|
||||
return
|
||||
}
|
||||
|
||||
func fi2fo(fi os.FileInfo, p string) FileObj {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("fen: type should be *syscall.Stat_t, %T instead", st))
|
||||
}
|
||||
return FileObj{Name: p, Atim: st.Atim, Mtim: st.Mtim, Ctim: st.Ctim}
|
||||
}
|
||||
|
||||
// Unwatch implements trigger.
|
||||
func (f *fen) Unwatch(w *watched) error {
|
||||
return f.cf.port_dissociate(f.p, FileObj{Name: w.p})
|
||||
}
|
||||
|
||||
// Watch implements trigger.
|
||||
func (f *fen) Watch(fi os.FileInfo, w *watched, e int64) error {
|
||||
return f.cf.port_associate(f.p, fi2fo(fi, w.p), int(e))
|
||||
}
|
||||
|
||||
// Wait implements trigger.
|
||||
func (f *fen) Wait() (interface{}, error) {
|
||||
var (
|
||||
pe PortEvent
|
||||
err error
|
||||
)
|
||||
err = f.cf.port_get(f.p, &pe)
|
||||
return pe, err
|
||||
}
|
||||
|
||||
// IsStop implements trigger.
|
||||
func (f *fen) IsStop(n interface{}, err error) bool {
|
||||
return err == syscall.EBADF || inter2pe(n).PortevSource == srcAlert
|
||||
}
|
||||
|
||||
func init() {
|
||||
encode = func(e Event) (o int64) {
|
||||
// Create event is not supported by FEN. Instead FileModified event will
|
||||
// be registered. If this event will be reported on dir which is to be
|
||||
// monitored for Create, dir will be rescanned and Create events will
|
||||
// be generated and returned for new files. In case of files,
|
||||
// if not requested FileModified event is reported, it will be ignored.
|
||||
if e&Create != 0 {
|
||||
o = (o &^ int64(Create)) | int64(FileModified)
|
||||
}
|
||||
if e&Write != 0 {
|
||||
o = (o &^ int64(Write)) | int64(FileModified)
|
||||
}
|
||||
// Following events are 'exception events' and as such cannot be requested
|
||||
// explicitly for monitoring or filtered out. If the will be reported
|
||||
// by FEN and not subscribed with by user, they will be filtered out by
|
||||
// watcher's logic.
|
||||
o &= int64(^Rename & ^Remove &^ FileDelete &^ FileRenameTo &^
|
||||
FileRenameFrom &^ Unmounted &^ MountedOver)
|
||||
return
|
||||
}
|
||||
nat2not = map[Event]Event{
|
||||
FileModified: Write,
|
||||
FileRenameFrom: Rename,
|
||||
FileDelete: Remove,
|
||||
FileAccess: Event(0),
|
||||
FileAttrib: Event(0),
|
||||
FileRenameTo: Event(0),
|
||||
FileTrunc: Event(0),
|
||||
FileNoFollow: Event(0),
|
||||
Unmounted: Event(0),
|
||||
MountedOver: Event(0),
|
||||
}
|
||||
not2nat = map[Event]Event{
|
||||
Write: FileModified,
|
||||
Rename: FileRenameFrom,
|
||||
Remove: FileDelete,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package notify
|
||||
|
||||
// #include <port.h>
|
||||
// #include <stdio.h>
|
||||
// #include <stdlib.h>
|
||||
// struct file_obj* newFo() { return (struct file_obj*) malloc(sizeof(struct file_obj)); }
|
||||
// port_event_t* newPe() { return (port_event_t*) malloc(sizeof(port_event_t)); }
|
||||
// uintptr_t conv(struct file_obj* fo) { return (uintptr_t) fo; }
|
||||
// struct file_obj* dconv(uintptr_t fo) { return (struct file_obj*) fo; }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
fileAccess = Event(C.FILE_ACCESS)
|
||||
fileModified = Event(C.FILE_MODIFIED)
|
||||
fileAttrib = Event(C.FILE_ATTRIB)
|
||||
fileDelete = Event(C.FILE_DELETE)
|
||||
fileRenameTo = Event(C.FILE_RENAME_TO)
|
||||
fileRenameFrom = Event(C.FILE_RENAME_FROM)
|
||||
fileTrunc = Event(C.FILE_TRUNC)
|
||||
fileNoFollow = Event(C.FILE_NOFOLLOW)
|
||||
unmounted = Event(C.UNMOUNTED)
|
||||
mountedOver = Event(C.MOUNTEDOVER)
|
||||
)
|
||||
|
||||
// PortEvent is a notify's equivalent of port_event_t.
|
||||
type PortEvent struct {
|
||||
PortevEvents int // PortevEvents is an equivalent of portev_events.
|
||||
PortevSource uint8 // PortevSource is an equivalent of portev_source.
|
||||
PortevPad uint8 // Portevpad is an equivalent of portev_pad.
|
||||
PortevObject interface{} // PortevObject is an equivalent of portev_object.
|
||||
PortevUser uintptr // PortevUser is an equivalent of portev_user.
|
||||
}
|
||||
|
||||
// FileObj is a notify's equivalent of file_obj.
|
||||
type FileObj struct {
|
||||
Atim syscall.Timespec // Atim is an equivalent of fo_atime.
|
||||
Mtim syscall.Timespec // Mtim is an equivalent of fo_mtime.
|
||||
Ctim syscall.Timespec // Ctim is an equivalent of fo_ctime.
|
||||
Pad [3]uintptr // Pad is an equivalent of fo_pad.
|
||||
Name string // Name is an equivalent of fo_name.
|
||||
}
|
||||
|
||||
type cfen struct {
|
||||
p2pe map[string]*C.port_event_t
|
||||
p2fo map[string]*C.struct_file_obj
|
||||
}
|
||||
|
||||
func newCfen() cfen {
|
||||
return cfen{
|
||||
p2pe: make(map[string]*C.port_event_t),
|
||||
p2fo: make(map[string]*C.struct_file_obj),
|
||||
}
|
||||
}
|
||||
|
||||
func unix2C(sec int64, nsec int64) (C.time_t, C.long) {
|
||||
return C.time_t(sec), C.long(nsec)
|
||||
}
|
||||
|
||||
func (c *cfen) port_associate(p int, fo FileObj, e int) (err error) {
|
||||
cfo := C.newFo()
|
||||
cfo.fo_atime.tv_sec, cfo.fo_atime.tv_nsec = unix2C(fo.Atim.Unix())
|
||||
cfo.fo_mtime.tv_sec, cfo.fo_mtime.tv_nsec = unix2C(fo.Mtim.Unix())
|
||||
cfo.fo_ctime.tv_sec, cfo.fo_ctime.tv_nsec = unix2C(fo.Ctim.Unix())
|
||||
cfo.fo_name = C.CString(fo.Name)
|
||||
c.p2fo[fo.Name] = cfo
|
||||
_, err = C.port_associate(C.int(p), srcFile, C.conv(cfo), C.int(e), nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cfen) port_dissociate(port int, fo FileObj) (err error) {
|
||||
cfo, ok := c.p2fo[fo.Name]
|
||||
if !ok {
|
||||
return errNotWatched
|
||||
}
|
||||
_, err = C.port_dissociate(C.int(port), srcFile, C.conv(cfo))
|
||||
C.free(unsafe.Pointer(cfo.fo_name))
|
||||
C.free(unsafe.Pointer(cfo))
|
||||
delete(c.p2fo, fo.Name)
|
||||
return
|
||||
}
|
||||
|
||||
const srcAlert = C.PORT_SOURCE_ALERT
|
||||
const srcFile = C.PORT_SOURCE_FILE
|
||||
const alertSet = C.PORT_ALERT_SET
|
||||
|
||||
func cfo2fo(cfo *C.struct_file_obj) *FileObj {
|
||||
// Currently remaining attributes are not used.
|
||||
if cfo == nil {
|
||||
return nil
|
||||
}
|
||||
var fo FileObj
|
||||
fo.Name = C.GoString(cfo.fo_name)
|
||||
return &fo
|
||||
}
|
||||
|
||||
func (c *cfen) port_get(port int, pe *PortEvent) (err error) {
|
||||
cpe := C.newPe()
|
||||
if _, err = C.port_get(C.int(port), cpe, nil); err != nil {
|
||||
C.free(unsafe.Pointer(cpe))
|
||||
return
|
||||
}
|
||||
pe.PortevEvents, pe.PortevSource, pe.PortevPad =
|
||||
int(cpe.portev_events), uint8(cpe.portev_source), uint8(cpe.portev_pad)
|
||||
pe.PortevObject = cfo2fo(C.dconv(cpe.portev_object))
|
||||
pe.PortevUser = uintptr(cpe.portev_user)
|
||||
C.free(unsafe.Pointer(cpe))
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cfen) port_create() (int, error) {
|
||||
p, err := C.port_create()
|
||||
return int(p), err
|
||||
}
|
||||
|
||||
func (c *cfen) port_alert(p int) (err error) {
|
||||
_, err = C.port_alert(C.int(p), alertSet, C.int(666), nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cfen) free() {
|
||||
for i := range c.p2fo {
|
||||
C.free(unsafe.Pointer(c.p2fo[i].fo_name))
|
||||
C.free(unsafe.Pointer(c.p2fo[i]))
|
||||
}
|
||||
for i := range c.p2pe {
|
||||
C.free(unsafe.Pointer(c.p2pe[i]))
|
||||
}
|
||||
c.p2fo = make(map[string]*C.struct_file_obj)
|
||||
c.p2pe = make(map[string]*C.port_event_t)
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin,!kqueue
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// TODO(rjeczalik): get rid of calls to canonical, it's tree responsibility
|
||||
|
||||
const (
|
||||
failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
|
||||
filter = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
|
||||
FSEventsModified | FSEventsInodeMetaMod)
|
||||
)
|
||||
|
||||
// FSEvent represents single file event. It is created out of values passed by
|
||||
// FSEvents to FSEventStreamCallback function.
|
||||
type FSEvent struct {
|
||||
Path string // real path of the file or directory
|
||||
ID uint64 // ID of the event (FSEventStreamEventId)
|
||||
Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
|
||||
}
|
||||
|
||||
// splitflags separates event flags from single set into slice of flags.
|
||||
func splitflags(set uint32) (e []uint32) {
|
||||
for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
|
||||
if (set & 1) != 0 {
|
||||
e = append(e, i)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// watch represents a filesystem watchpoint. It is a higher level abstraction
|
||||
// over FSEvents' stream, which implements filtering of file events based
|
||||
// on path and event set. It emulates non-recursive watch-point by filtering out
|
||||
// events which paths are more than 1 level deeper than the watched path.
|
||||
type watch struct {
|
||||
// prev stores last event set per path in order to filter out old flags
|
||||
// for new events, which appratenly FSEvents likes to retain. It's a disgusting
|
||||
// hack, it should be researched how to get rid of it.
|
||||
prev map[string]uint32
|
||||
c chan<- EventInfo
|
||||
stream *stream
|
||||
path string
|
||||
events uint32
|
||||
isrec int32
|
||||
flushed bool
|
||||
}
|
||||
|
||||
// Example format:
|
||||
//
|
||||
// ~ $ (trigger command) # (event set) -> (effective event set)
|
||||
//
|
||||
// Heuristics:
|
||||
//
|
||||
// 1. Create event is removed when it was present in previous event set.
|
||||
// Example:
|
||||
//
|
||||
// ~ $ echo > file # Create|Write -> Create|Write
|
||||
// ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
|
||||
//
|
||||
// 2. Remove event is removed if it was present in previouse event set.
|
||||
// Example:
|
||||
//
|
||||
// ~ $ touch file # Create -> Create
|
||||
// ~ $ rm file # Create|Remove -> Remove
|
||||
// ~ $ touch file # Create|Remove -> Create
|
||||
//
|
||||
// 3. Write event is removed if not followed by InodeMetaMod on existing
|
||||
// file. Example:
|
||||
//
|
||||
// ~ $ echo > file # Create|Write -> Create|Write
|
||||
// ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
|
||||
//
|
||||
// 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
|
||||
// Example:
|
||||
//
|
||||
// ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
|
||||
// ~ $ rm file # Remove|Write|InodeMetaMod -> Remove
|
||||
//
|
||||
func (w *watch) strip(base string, set uint32) uint32 {
|
||||
const (
|
||||
write = FSEventsModified | FSEventsInodeMetaMod
|
||||
both = FSEventsCreated | FSEventsRemoved
|
||||
)
|
||||
switch w.prev[base] {
|
||||
case FSEventsCreated:
|
||||
set &^= FSEventsCreated
|
||||
if set&FSEventsRemoved != 0 {
|
||||
w.prev[base] = FSEventsRemoved
|
||||
set &^= write
|
||||
}
|
||||
case FSEventsRemoved:
|
||||
set &^= FSEventsRemoved
|
||||
if set&FSEventsCreated != 0 {
|
||||
w.prev[base] = FSEventsCreated
|
||||
}
|
||||
default:
|
||||
switch set & both {
|
||||
case FSEventsCreated:
|
||||
w.prev[base] = FSEventsCreated
|
||||
case FSEventsRemoved:
|
||||
w.prev[base] = FSEventsRemoved
|
||||
set &^= write
|
||||
}
|
||||
}
|
||||
dbgprintf("split()=%v\n", Event(set))
|
||||
return set
|
||||
}
|
||||
|
||||
// Dispatch is a stream function which forwards given file events for the watched
|
||||
// path to underlying FileInfo channel.
|
||||
func (w *watch) Dispatch(ev []FSEvent) {
|
||||
events := atomic.LoadUint32(&w.events)
|
||||
isrec := (atomic.LoadInt32(&w.isrec) == 1)
|
||||
for i := range ev {
|
||||
if ev[i].Flags&FSEventsHistoryDone != 0 {
|
||||
w.flushed = true
|
||||
continue
|
||||
}
|
||||
if !w.flushed {
|
||||
continue
|
||||
}
|
||||
dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
|
||||
ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
|
||||
if ev[i].Flags&failure != 0 {
|
||||
// TODO(rjeczalik): missing error handling
|
||||
panic("unhandled error: " + Event(ev[i].Flags).String())
|
||||
}
|
||||
if !strings.HasPrefix(ev[i].Path, w.path) {
|
||||
continue
|
||||
}
|
||||
n := len(w.path)
|
||||
base := ""
|
||||
if len(ev[i].Path) > n {
|
||||
if ev[i].Path[n] != '/' {
|
||||
continue
|
||||
}
|
||||
base = ev[i].Path[n+1:]
|
||||
if !isrec && strings.IndexByte(base, '/') != -1 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// TODO(rjeczalik): get diff only from filtered events?
|
||||
e := w.strip(string(base), ev[i].Flags) & events
|
||||
if e == 0 {
|
||||
continue
|
||||
}
|
||||
for _, e := range splitflags(e) {
|
||||
dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
|
||||
w.c <- &event{
|
||||
fse: ev[i],
|
||||
event: Event(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop closes underlying FSEvents stream and stops dispatching events.
|
||||
func (w *watch) Stop() {
|
||||
w.stream.Stop()
|
||||
// TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
|
||||
// so the following hack can be removed. It should flush all the streams
|
||||
// concurrently as we care not to block too much here.
|
||||
atomic.StoreUint32(&w.events, 0)
|
||||
atomic.StoreInt32(&w.isrec, 0)
|
||||
}
|
||||
|
||||
// fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
|
||||
// framework.
|
||||
type fsevents struct {
|
||||
watches map[string]*watch
|
||||
c chan<- EventInfo
|
||||
}
|
||||
|
||||
func newWatcher(c chan<- EventInfo) watcher {
|
||||
return &fsevents{
|
||||
watches: make(map[string]*watch),
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
|
||||
if path, err = canonical(path); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := fse.watches[path]; ok {
|
||||
return errAlreadyWatched
|
||||
}
|
||||
w := &watch{
|
||||
prev: make(map[string]uint32),
|
||||
c: fse.c,
|
||||
path: path,
|
||||
events: uint32(event),
|
||||
isrec: isrec,
|
||||
}
|
||||
w.stream = newStream(path, w.Dispatch)
|
||||
if err = w.stream.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
fse.watches[path] = w
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fse *fsevents) unwatch(path string) (err error) {
|
||||
if path, err = canonical(path); err != nil {
|
||||
return
|
||||
}
|
||||
w, ok := fse.watches[path]
|
||||
if !ok {
|
||||
return errNotWatched
|
||||
}
|
||||
w.stream.Stop()
|
||||
delete(fse.watches, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch implements Watcher interface. It fails with non-nil error when setting
|
||||
// the watch-point by FSEvents fails or with errAlreadyWatched error when
|
||||
// the given path is already watched.
|
||||
func (fse *fsevents) Watch(path string, event Event) error {
|
||||
return fse.watch(path, event, 0)
|
||||
}
|
||||
|
||||
// Unwatch implements Watcher interface. It fails with errNotWatched when
|
||||
// the given path is not being watched.
|
||||
func (fse *fsevents) Unwatch(path string) error {
|
||||
return fse.unwatch(path)
|
||||
}
|
||||
|
||||
// Rewatch implements Watcher interface. It fails with errNotWatched when
|
||||
// the given path is not being watched or with errInvalidEventSet when oldevent
|
||||
// does not match event set the watch-point currently holds.
|
||||
func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
|
||||
w, ok := fse.watches[path]
|
||||
if !ok {
|
||||
return errNotWatched
|
||||
}
|
||||
if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
|
||||
return errInvalidEventSet
|
||||
}
|
||||
atomic.StoreInt32(&w.isrec, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
|
||||
// error when setting the watch-point by FSEvents fails or with errAlreadyWatched
|
||||
// error when the given path is already watched.
|
||||
func (fse *fsevents) RecursiveWatch(path string, event Event) error {
|
||||
return fse.watch(path, event, 1)
|
||||
}
|
||||
|
||||
// RecursiveUnwatch implements RecursiveWatcher interface. It fails with
|
||||
// errNotWatched when the given path is not being watched.
|
||||
//
|
||||
// TODO(rjeczalik): fail if w.isrec == 0?
|
||||
func (fse *fsevents) RecursiveUnwatch(path string) error {
|
||||
return fse.unwatch(path)
|
||||
}
|
||||
|
||||
// RecrusiveRewatch implements RecursiveWatcher interface. It fails:
|
||||
//
|
||||
// * with errNotWatched when the given path is not being watched
|
||||
// * with errInvalidEventSet when oldevent does not match the current event set
|
||||
// * with errAlreadyWatched when watch-point given by the oldpath was meant to
|
||||
// be relocated to newpath, but the newpath is already watched
|
||||
// * a non-nil error when setting the watch-point with FSEvents fails
|
||||
//
|
||||
// TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
|
||||
// that follows.
|
||||
func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
|
||||
switch [2]bool{oldpath == newpath, oldevent == newevent} {
|
||||
case [2]bool{true, true}:
|
||||
w, ok := fse.watches[oldpath]
|
||||
if !ok {
|
||||
return errNotWatched
|
||||
}
|
||||
atomic.StoreInt32(&w.isrec, 1)
|
||||
return nil
|
||||
case [2]bool{true, false}:
|
||||
w, ok := fse.watches[oldpath]
|
||||
if !ok {
|
||||
return errNotWatched
|
||||
}
|
||||
if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
|
||||
return errors.New("invalid event state diff")
|
||||
}
|
||||
atomic.StoreInt32(&w.isrec, 1)
|
||||
return nil
|
||||
default:
|
||||
// TODO(rjeczalik): rewatch newpath only if exists?
|
||||
// TODO(rjeczalik): migrate w.prev to new watch?
|
||||
if _, ok := fse.watches[newpath]; ok {
|
||||
return errAlreadyWatched
|
||||
}
|
||||
if err := fse.Unwatch(oldpath); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(rjeczalik): revert unwatch if watch fails?
|
||||
return fse.watch(newpath, newevent, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Close unwatches all watch-points.
|
||||
func (fse *fsevents) Close() error {
|
||||
for _, w := range fse.watches {
|
||||
w.Stop()
|
||||
}
|
||||
fse.watches = nil
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin,!kqueue
|
||||
|
||||
package notify
|
||||
|
||||
/*
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
typedef void (*CFRunLoopPerformCallBack)(void*);
|
||||
|
||||
void gosource(void *);
|
||||
void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t);
|
||||
|
||||
static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) {
|
||||
context->info = (void*) info;
|
||||
return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags);
|
||||
}
|
||||
|
||||
#cgo LDFLAGS: -framework CoreServices
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var nilstream C.FSEventStreamRef
|
||||
|
||||
// Default arguments for FSEventStreamCreate function.
|
||||
var (
|
||||
latency C.CFTimeInterval
|
||||
flags = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer)
|
||||
since = uint64(C.FSEventsGetCurrentEventId())
|
||||
)
|
||||
|
||||
var runloop C.CFRunLoopRef // global runloop which all streams are registered with
|
||||
var wg sync.WaitGroup // used to wait until the runloop starts
|
||||
|
||||
// source is used for synchronization purposes - it signals when runloop has
|
||||
// started and is ready via the wg. It also serves purpose of a dummy source,
|
||||
// thanks to it the runloop does not return as it also has at least one source
|
||||
// registered.
|
||||
var source = C.CFRunLoopSourceCreate(nil, 0, &C.CFRunLoopSourceContext{
|
||||
perform: (C.CFRunLoopPerformCallBack)(C.gosource),
|
||||
})
|
||||
|
||||
// Errors returned when FSEvents functions fail.
|
||||
var (
|
||||
errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL"))
|
||||
errStart = os.NewSyscallError("FSEventStreamStart", errors.New("false"))
|
||||
)
|
||||
|
||||
// initializes the global runloop and ensures any created stream awaits its
|
||||
// readiness.
|
||||
func init() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
runloop = C.CFRunLoopGetCurrent()
|
||||
C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode)
|
||||
C.CFRunLoopRun()
|
||||
panic("runloop has just unexpectedly stopped")
|
||||
}()
|
||||
C.CFRunLoopSourceSignal(source)
|
||||
}
|
||||
|
||||
//export gosource
|
||||
func gosource(unsafe.Pointer) {
|
||||
time.Sleep(time.Second)
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
//export gostream
|
||||
func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) {
|
||||
const (
|
||||
offchar = unsafe.Sizeof((*C.char)(nil))
|
||||
offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0))
|
||||
offid = unsafe.Sizeof(C.FSEventStreamEventId(0))
|
||||
)
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
ev := make([]FSEvent, 0, int(n))
|
||||
for i := uintptr(0); i < uintptr(n); i++ {
|
||||
switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
|
||||
case flags&uint32(FSEventsEventIdsWrapped) != 0:
|
||||
atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
|
||||
default:
|
||||
ev = append(ev, FSEvent{
|
||||
Path: C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
|
||||
Flags: flags,
|
||||
ID: *(*uint64)(unsafe.Pointer(ids + i*offid)),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
streamFuncs.get(info)(ev)
|
||||
}
|
||||
|
||||
// StreamFunc is a callback called when stream receives file events.
|
||||
type streamFunc func([]FSEvent)
|
||||
|
||||
var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}}
|
||||
|
||||
type streamFuncRegistry struct {
|
||||
mu sync.Mutex
|
||||
m map[uintptr]streamFunc
|
||||
i uintptr
|
||||
}
|
||||
|
||||
func (r *streamFuncRegistry) get(id uintptr) streamFunc {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.m[id]
|
||||
}
|
||||
|
||||
func (r *streamFuncRegistry) add(fn streamFunc) uintptr {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.i++
|
||||
r.m[r.i] = fn
|
||||
return r.i
|
||||
}
|
||||
|
||||
func (r *streamFuncRegistry) delete(id uintptr) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
delete(r.m, id)
|
||||
}
|
||||
|
||||
// Stream represents single watch-point which listens for events scheduled by
|
||||
// the global runloop.
|
||||
type stream struct {
|
||||
path string
|
||||
ref C.FSEventStreamRef
|
||||
info uintptr
|
||||
}
|
||||
|
||||
// NewStream creates a stream for given path, listening for file events and
|
||||
// calling fn upon receving any.
|
||||
func newStream(path string, fn streamFunc) *stream {
|
||||
return &stream{
|
||||
path: path,
|
||||
info: streamFuncs.add(fn),
|
||||
}
|
||||
}
|
||||
|
||||
// Start creates a FSEventStream for the given path and schedules it with
|
||||
// global runloop. It's a nop if the stream was already started.
|
||||
func (s *stream) Start() error {
|
||||
if s.ref != nilstream {
|
||||
return nil
|
||||
}
|
||||
wg.Wait()
|
||||
p := C.CFStringCreateWithCStringNoCopy(nil, C.CString(s.path), C.kCFStringEncodingUTF8, nil)
|
||||
path := C.CFArrayCreate(nil, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
|
||||
ctx := C.FSEventStreamContext{}
|
||||
ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
|
||||
if ref == nilstream {
|
||||
return errCreate
|
||||
}
|
||||
C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
|
||||
if C.FSEventStreamStart(ref) == C.Boolean(0) {
|
||||
C.FSEventStreamInvalidate(ref)
|
||||
return errStart
|
||||
}
|
||||
C.CFRunLoopWakeUp(runloop)
|
||||
s.ref = ref
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops underlying FSEventStream and unregisters it from global runloop.
|
||||
func (s *stream) Stop() {
|
||||
if s.ref == nilstream {
|
||||
return
|
||||
}
|
||||
wg.Wait()
|
||||
C.FSEventStreamStop(s.ref)
|
||||
C.FSEventStreamInvalidate(s.ref)
|
||||
C.CFRunLoopWakeUp(runloop)
|
||||
s.ref = nilstream
|
||||
streamFuncs.delete(s.info)
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// eventBufferSize defines the size of the buffer given to read(2) function. One
|
||||
// should not depend on this value, since it was arbitrary chosen and may be
|
||||
// changed in the future.
|
||||
const eventBufferSize = 64 * (syscall.SizeofInotifyEvent + syscall.PathMax + 1)
|
||||
|
||||
// consumersCount defines the number of consumers in producer-consumer based
|
||||
// implementation. Each consumer is run in a separate goroutine and has read
|
||||
// access to watched files map.
|
||||
const consumersCount = 2
|
||||
|
||||
const invalidDescriptor = -1
|
||||
|
||||
// watched is a pair of file path and inotify mask used as a value in
|
||||
// watched files map.
|
||||
type watched struct {
|
||||
path string
|
||||
mask uint32
|
||||
}
|
||||
|
||||
// inotify implements Watcher interface.
|
||||
type inotify struct {
|
||||
sync.RWMutex // protects inotify.m map
|
||||
m map[int32]*watched // watch descriptor to watched object
|
||||
fd int32 // inotify file descriptor
|
||||
pipefd []int // pipe's read and write descriptors
|
||||
epfd int // epoll descriptor
|
||||
epes []syscall.EpollEvent // epoll events
|
||||
buffer [eventBufferSize]byte // inotify event buffer
|
||||
wg sync.WaitGroup // wait group used to close main loop
|
||||
c chan<- EventInfo // event dispatcher channel
|
||||
}
|
||||
|
||||
// NewWatcher creates new non-recursive inotify backed by inotify.
|
||||
func newWatcher(c chan<- EventInfo) watcher {
|
||||
i := &inotify{
|
||||
m: make(map[int32]*watched),
|
||||
fd: invalidDescriptor,
|
||||
pipefd: []int{invalidDescriptor, invalidDescriptor},
|
||||
epfd: invalidDescriptor,
|
||||
epes: make([]syscall.EpollEvent, 0),
|
||||
c: c,
|
||||
}
|
||||
runtime.SetFinalizer(i, func(i *inotify) {
|
||||
i.epollclose()
|
||||
if i.fd != invalidDescriptor {
|
||||
syscall.Close(int(i.fd))
|
||||
}
|
||||
})
|
||||
return i
|
||||
}
|
||||
|
||||
// Watch implements notify.watcher interface.
|
||||
func (i *inotify) Watch(path string, e Event) error {
|
||||
return i.watch(path, e)
|
||||
}
|
||||
|
||||
// Rewatch implements notify.watcher interface.
|
||||
func (i *inotify) Rewatch(path string, _, newevent Event) error {
|
||||
return i.watch(path, newevent)
|
||||
}
|
||||
|
||||
// watch adds a new watcher to the set of watched objects or modifies the existing
|
||||
// one. If called for the first time, this function initializes inotify filesystem
|
||||
// monitor and starts producer-consumers goroutines.
|
||||
func (i *inotify) watch(path string, e Event) (err error) {
|
||||
if e&^(All|Event(syscall.IN_ALL_EVENTS)) != 0 {
|
||||
return errors.New("notify: unknown event")
|
||||
}
|
||||
if err = i.lazyinit(); err != nil {
|
||||
return
|
||||
}
|
||||
iwd, err := syscall.InotifyAddWatch(int(i.fd), path, encode(e))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
i.RLock()
|
||||
wd := i.m[int32(iwd)]
|
||||
i.RUnlock()
|
||||
if wd == nil {
|
||||
i.Lock()
|
||||
if i.m[int32(iwd)] == nil {
|
||||
i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)}
|
||||
}
|
||||
i.Unlock()
|
||||
} else {
|
||||
i.Lock()
|
||||
wd.mask = uint32(e)
|
||||
i.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lazyinit sets up all required file descriptors and starts 1+consumersCount
|
||||
// goroutines. The producer goroutine blocks until file-system notifications
|
||||
// occur. Then, all events are read from system buffer and sent to consumer
|
||||
// goroutines which construct valid notify events. This method uses
|
||||
// Double-Checked Locking optimization.
|
||||
func (i *inotify) lazyinit() error {
|
||||
if atomic.LoadInt32(&i.fd) == invalidDescriptor {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
if atomic.LoadInt32(&i.fd) == invalidDescriptor {
|
||||
fd, err := syscall.InotifyInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.fd = int32(fd)
|
||||
if err = i.epollinit(); err != nil {
|
||||
_, _ = i.epollclose(), syscall.Close(int(fd)) // Ignore errors.
|
||||
i.fd = invalidDescriptor
|
||||
return err
|
||||
}
|
||||
esch := make(chan []*event)
|
||||
go i.loop(esch)
|
||||
i.wg.Add(consumersCount)
|
||||
for n := 0; n < consumersCount; n++ {
|
||||
go i.send(esch)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// epollinit opens an epoll file descriptor and creates a pipe which will be
|
||||
// used to wake up the epoll_wait(2) function. Then, file descriptor associated
|
||||
// with inotify event queue and the read end of the pipe are added to epoll set.
|
||||
// Note that `fd` member must be set before this function is called.
|
||||
func (i *inotify) epollinit() (err error) {
|
||||
if i.epfd, err = syscall.EpollCreate1(0); err != nil {
|
||||
return
|
||||
}
|
||||
if err = syscall.Pipe(i.pipefd); err != nil {
|
||||
return
|
||||
}
|
||||
i.epes = []syscall.EpollEvent{
|
||||
{Events: syscall.EPOLLIN, Fd: i.fd},
|
||||
{Events: syscall.EPOLLIN, Fd: int32(i.pipefd[0])},
|
||||
}
|
||||
if err = syscall.EpollCtl(i.epfd, syscall.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil {
|
||||
return
|
||||
}
|
||||
return syscall.EpollCtl(i.epfd, syscall.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1])
|
||||
}
|
||||
|
||||
// epollclose closes the file descriptor created by the call to epoll_create(2)
|
||||
// and two file descriptors opened by pipe(2) function.
|
||||
func (i *inotify) epollclose() (err error) {
|
||||
if i.epfd != invalidDescriptor {
|
||||
if err = syscall.Close(i.epfd); err == nil {
|
||||
i.epfd = invalidDescriptor
|
||||
}
|
||||
}
|
||||
for n, fd := range i.pipefd {
|
||||
if fd != invalidDescriptor {
|
||||
switch e := syscall.Close(fd); {
|
||||
case e != nil && err == nil:
|
||||
err = e
|
||||
case e == nil:
|
||||
i.pipefd[n] = invalidDescriptor
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// loop blocks until either inotify or pipe file descriptor is ready for I/O.
|
||||
// All read operations triggered by filesystem notifications are forwarded to
|
||||
// one of the event's consumers. If pipe fd became ready, loop function closes
|
||||
// all file descriptors opened by lazyinit method and returns afterwards.
|
||||
func (i *inotify) loop(esch chan<- []*event) {
|
||||
epes := make([]syscall.EpollEvent, 1)
|
||||
fd := atomic.LoadInt32(&i.fd)
|
||||
for {
|
||||
switch _, err := syscall.EpollWait(i.epfd, epes, -1); err {
|
||||
case nil:
|
||||
switch epes[0].Fd {
|
||||
case fd:
|
||||
esch <- i.read()
|
||||
epes[0].Fd = 0
|
||||
case int32(i.pipefd[0]):
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
if err = syscall.Close(int(fd)); err != nil && err != syscall.EINTR {
|
||||
panic("notify: close(2) error " + err.Error())
|
||||
}
|
||||
atomic.StoreInt32(&i.fd, invalidDescriptor)
|
||||
if err = i.epollclose(); err != nil && err != syscall.EINTR {
|
||||
panic("notify: epollclose error " + err.Error())
|
||||
}
|
||||
close(esch)
|
||||
return
|
||||
}
|
||||
case syscall.EINTR:
|
||||
continue
|
||||
default: // We should never reach this line.
|
||||
panic("notify: epoll_wait(2) error " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read reads events from an inotify file descriptor. It does not handle errors
|
||||
// returned from read(2) function since they are not critical to watcher logic.
|
||||
func (i *inotify) read() (es []*event) {
|
||||
n, err := syscall.Read(int(i.fd), i.buffer[:])
|
||||
if err != nil || n < syscall.SizeofInotifyEvent {
|
||||
return
|
||||
}
|
||||
var sys *syscall.InotifyEvent
|
||||
nmin := n - syscall.SizeofInotifyEvent
|
||||
for pos, path := 0, ""; pos <= nmin; {
|
||||
sys = (*syscall.InotifyEvent)(unsafe.Pointer(&i.buffer[pos]))
|
||||
pos += syscall.SizeofInotifyEvent
|
||||
if path = ""; sys.Len > 0 {
|
||||
endpos := pos + int(sys.Len)
|
||||
path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00"))
|
||||
pos = endpos
|
||||
}
|
||||
es = append(es, &event{
|
||||
sys: syscall.InotifyEvent{
|
||||
Wd: sys.Wd,
|
||||
Mask: sys.Mask,
|
||||
Cookie: sys.Cookie,
|
||||
},
|
||||
path: path,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// send is a consumer function which sends events to event dispatcher channel.
|
||||
// It is run in a separate goroutine in order to not block loop method when
|
||||
// possibly expensive write operations are performed on inotify map.
|
||||
func (i *inotify) send(esch <-chan []*event) {
|
||||
for es := range esch {
|
||||
for _, e := range i.transform(es) {
|
||||
if e != nil {
|
||||
i.c <- e
|
||||
}
|
||||
}
|
||||
}
|
||||
i.wg.Done()
|
||||
}
|
||||
|
||||
// transform prepares events read from inotify file descriptor for sending to
|
||||
// user. It removes invalid events and these which are no longer present in
|
||||
// inotify map. This method may also split one raw event into two different ones
|
||||
// when system-dependent result is required.
|
||||
func (i *inotify) transform(es []*event) []*event {
|
||||
var multi []*event
|
||||
i.RLock()
|
||||
for idx, e := range es {
|
||||
if e.sys.Mask&(syscall.IN_IGNORED|syscall.IN_Q_OVERFLOW) != 0 {
|
||||
es[idx] = nil
|
||||
continue
|
||||
}
|
||||
wd, ok := i.m[e.sys.Wd]
|
||||
if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 {
|
||||
es[idx] = nil
|
||||
continue
|
||||
}
|
||||
if e.path == "" {
|
||||
e.path = wd.path
|
||||
} else {
|
||||
e.path = filepath.Join(wd.path, e.path)
|
||||
}
|
||||
multi = append(multi, decode(Event(wd.mask), e))
|
||||
if e.event == 0 {
|
||||
es[idx] = nil
|
||||
}
|
||||
}
|
||||
i.RUnlock()
|
||||
es = append(es, multi...)
|
||||
return es
|
||||
}
|
||||
|
||||
// encode converts notify system-independent events to valid inotify mask
|
||||
// which can be passed to inotify_add_watch(2) function.
|
||||
func encode(e Event) uint32 {
|
||||
if e&Create != 0 {
|
||||
e = (e ^ Create) | InCreate | InMovedTo
|
||||
}
|
||||
if e&Remove != 0 {
|
||||
e = (e ^ Remove) | InDelete | InDeleteSelf
|
||||
}
|
||||
if e&Write != 0 {
|
||||
e = (e ^ Write) | InModify
|
||||
}
|
||||
if e&Rename != 0 {
|
||||
e = (e ^ Rename) | InMovedFrom | InMoveSelf
|
||||
}
|
||||
return uint32(e)
|
||||
}
|
||||
|
||||
// decode uses internally stored mask to distinguish whether system-independent
|
||||
// or system-dependent event is requested. The first one is created by modifying
|
||||
// `e` argument. decode method sets e.event value to 0 when an event should be
|
||||
// skipped. System-dependent event is set as the function's return value which
|
||||
// can be nil when the event should not be passed on.
|
||||
func decode(mask Event, e *event) (syse *event) {
|
||||
if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 {
|
||||
syse = &event{sys: syscall.InotifyEvent{
|
||||
Wd: e.sys.Wd,
|
||||
Mask: e.sys.Mask,
|
||||
Cookie: e.sys.Cookie,
|
||||
}, event: Event(sysmask), path: e.path}
|
||||
}
|
||||
imask := encode(mask)
|
||||
switch {
|
||||
case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0:
|
||||
e.event = Create
|
||||
case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0:
|
||||
e.event = Remove
|
||||
case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0:
|
||||
e.event = Write
|
||||
case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0:
|
||||
e.event = Rename
|
||||
default:
|
||||
e.event = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Unwatch implements notify.watcher interface. It looks for watch descriptor
|
||||
// related to registered path and if found, calls inotify_rm_watch(2) function.
|
||||
// This method is allowed to return EINVAL error when concurrently requested to
|
||||
// delete identical path.
|
||||
func (i *inotify) Unwatch(path string) (err error) {
|
||||
iwd := int32(invalidDescriptor)
|
||||
i.RLock()
|
||||
for iwdkey, wd := range i.m {
|
||||
if wd.path == path {
|
||||
iwd = iwdkey
|
||||
break
|
||||
}
|
||||
}
|
||||
i.RUnlock()
|
||||
if iwd == invalidDescriptor {
|
||||
return errors.New("notify: path " + path + " is already watched")
|
||||
}
|
||||
fd := atomic.LoadInt32(&i.fd)
|
||||
if _, err = syscall.InotifyRmWatch(int(fd), uint32(iwd)); err != nil {
|
||||
return
|
||||
}
|
||||
i.Lock()
|
||||
delete(i.m, iwd)
|
||||
i.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements notify.watcher interface. It removes all existing watch
|
||||
// descriptors and wakes up producer goroutine by sending data to the write end
|
||||
// of the pipe. The function waits for a signal from producer which means that
|
||||
// all operations on current monitoring instance are done.
|
||||
func (i *inotify) Close() (err error) {
|
||||
i.Lock()
|
||||
if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor {
|
||||
i.Unlock()
|
||||
return nil
|
||||
}
|
||||
for iwd := range i.m {
|
||||
if _, e := syscall.InotifyRmWatch(int(i.fd), uint32(iwd)); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
delete(i.m, iwd)
|
||||
}
|
||||
switch _, errwrite := syscall.Write(i.pipefd[1], []byte{0x00}); {
|
||||
case errwrite != nil && err == nil:
|
||||
err = errwrite
|
||||
fallthrough
|
||||
case errwrite != nil:
|
||||
i.Unlock()
|
||||
default:
|
||||
i.Unlock()
|
||||
i.wg.Wait()
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin,kqueue dragonfly freebsd netbsd openbsd
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// newTrigger returns implementation of trigger.
|
||||
func newTrigger(pthLkp map[string]*watched) trigger {
|
||||
return &kq{
|
||||
pthLkp: pthLkp,
|
||||
idLkp: make(map[int]*watched),
|
||||
}
|
||||
}
|
||||
|
||||
// kq is a structure implementing trigger for kqueue.
|
||||
type kq struct {
|
||||
// fd is a kqueue file descriptor
|
||||
fd int
|
||||
// pipefds are file descriptors used to stop `Kevent` call.
|
||||
pipefds [2]int
|
||||
// idLkp is a data structure mapping file descriptors with data about watching
|
||||
// represented by them files/directories.
|
||||
idLkp map[int]*watched
|
||||
// pthLkp is a structure mapping monitored files/dir with data about them,
|
||||
// shared with parent trg structure
|
||||
pthLkp map[string]*watched
|
||||
}
|
||||
|
||||
// watched is a data structure representing watched file/directory.
|
||||
type watched struct {
|
||||
// p is a path to watched file/directory.
|
||||
p string
|
||||
// fd is a file descriptor for watched file/directory.
|
||||
fd int
|
||||
// fi provides information about watched file/dir.
|
||||
fi os.FileInfo
|
||||
// eDir represents events watched directly.
|
||||
eDir Event
|
||||
// eNonDir represents events watched indirectly.
|
||||
eNonDir Event
|
||||
}
|
||||
|
||||
// Stop implements trigger.
|
||||
func (k *kq) Stop() (err error) {
|
||||
// trigger event used to interrupt Kevent call.
|
||||
_, err = syscall.Write(k.pipefds[1], []byte{0x00})
|
||||
return
|
||||
}
|
||||
|
||||
// Close implements trigger.
|
||||
func (k *kq) Close() error {
|
||||
return syscall.Close(k.fd)
|
||||
}
|
||||
|
||||
// NewWatched implements trigger.
|
||||
func (*kq) NewWatched(p string, fi os.FileInfo) (*watched, error) {
|
||||
fd, err := syscall.Open(p, syscall.O_NONBLOCK|syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &watched{fd: fd, p: p, fi: fi}, nil
|
||||
}
|
||||
|
||||
// Record implements trigger.
|
||||
func (k *kq) Record(w *watched) {
|
||||
k.idLkp[w.fd], k.pthLkp[w.p] = w, w
|
||||
}
|
||||
|
||||
// Del implements trigger.
|
||||
func (k *kq) Del(w *watched) {
|
||||
syscall.Close(w.fd)
|
||||
delete(k.idLkp, w.fd)
|
||||
delete(k.pthLkp, w.p)
|
||||
}
|
||||
|
||||
func inter2kq(n interface{}) syscall.Kevent_t {
|
||||
kq, ok := n.(syscall.Kevent_t)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("kqueue: type should be Kevent_t, %T instead", n))
|
||||
}
|
||||
return kq
|
||||
}
|
||||
|
||||
// Init implements trigger.
|
||||
func (k *kq) Init() (err error) {
|
||||
if k.fd, err = syscall.Kqueue(); err != nil {
|
||||
return
|
||||
}
|
||||
// Creates pipe used to stop `Kevent` call by registering it,
|
||||
// watching read end and writing to other end of it.
|
||||
if err = syscall.Pipe(k.pipefds[:]); err != nil {
|
||||
return nonil(err, k.Close())
|
||||
}
|
||||
var kevn [1]syscall.Kevent_t
|
||||
syscall.SetKevent(&kevn[0], k.pipefds[0], syscall.EVFILT_READ, syscall.EV_ADD)
|
||||
if _, err = syscall.Kevent(k.fd, kevn[:], nil, nil); err != nil {
|
||||
return nonil(err, k.Close())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Unwatch implements trigger.
|
||||
func (k *kq) Unwatch(w *watched) (err error) {
|
||||
var kevn [1]syscall.Kevent_t
|
||||
syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
|
||||
|
||||
_, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch implements trigger.
|
||||
func (k *kq) Watch(fi os.FileInfo, w *watched, e int64) (err error) {
|
||||
var kevn [1]syscall.Kevent_t
|
||||
syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE,
|
||||
syscall.EV_ADD|syscall.EV_CLEAR)
|
||||
kevn[0].Fflags = uint32(e)
|
||||
|
||||
_, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait implements trigger.
|
||||
func (k *kq) Wait() (interface{}, error) {
|
||||
var (
|
||||
kevn [1]syscall.Kevent_t
|
||||
err error
|
||||
)
|
||||
kevn[0] = syscall.Kevent_t{}
|
||||
_, err = syscall.Kevent(k.fd, nil, kevn[:], nil)
|
||||
|
||||
return kevn[0], err
|
||||
}
|
||||
|
||||
// Watched implements trigger.
|
||||
func (k *kq) Watched(n interface{}) (*watched, int64, error) {
|
||||
kevn, ok := n.(syscall.Kevent_t)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("kq: type should be syscall.Kevent_t, %T instead", kevn))
|
||||
}
|
||||
if _, ok = k.idLkp[int(kevn.Ident)]; !ok {
|
||||
return nil, 0, errNotWatched
|
||||
}
|
||||
return k.idLkp[int(kevn.Ident)], int64(kevn.Fflags), nil
|
||||
}
|
||||
|
||||
// IsStop implements trigger.
|
||||
func (k *kq) IsStop(n interface{}, err error) bool {
|
||||
return int(inter2kq(n).Ident) == k.pipefds[0]
|
||||
}
|
||||
|
||||
func init() {
|
||||
encode = func(e Event) (o int64) {
|
||||
// Create event is not supported by kqueue. Instead NoteWrite event will
|
||||
// be registered. If this event will be reported on dir which is to be
|
||||
// monitored for Create, dir will be rescanned and Create events will
|
||||
// be generated and returned for new files. In case of files,
|
||||
// if not requested NoteRename event is reported, it will be ignored.
|
||||
o = int64(e &^ Create)
|
||||
if e&Write != 0 {
|
||||
o = (o &^ int64(Write)) | int64(NoteWrite)
|
||||
}
|
||||
if e&Rename != 0 {
|
||||
o = (o &^ int64(Rename)) | int64(NoteRename)
|
||||
}
|
||||
if e&Remove != 0 {
|
||||
o = (o &^ int64(Remove)) | int64(NoteDelete)
|
||||
}
|
||||
return
|
||||
}
|
||||
nat2not = map[Event]Event{
|
||||
NoteWrite: Write,
|
||||
NoteRename: Rename,
|
||||
NoteDelete: Remove,
|
||||
NoteExtend: Event(0),
|
||||
NoteAttrib: Event(0),
|
||||
NoteRevoke: Event(0),
|
||||
NoteLink: Event(0),
|
||||
}
|
||||
not2nat = map[Event]Event{
|
||||
Write: NoteWrite,
|
||||
Rename: NoteRename,
|
||||
Remove: NoteDelete,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,574 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// readBufferSize defines the size of an array in which read statuses are stored.
|
||||
// The buffer have to be DWORD-aligned and, if notify is used in monitoring a
|
||||
// directory over the network, its size must not be greater than 64KB. Each of
|
||||
// watched directories uses its own buffer for storing events.
|
||||
const readBufferSize = 4096
|
||||
|
||||
// Since all operations which go through the Windows completion routine are done
|
||||
// asynchronously, filter may set one of the constants belor. They were defined
|
||||
// in order to distinguish whether current folder should be re-registered in
|
||||
// ReadDirectoryChangesW function or some control operations need to be executed.
|
||||
const (
|
||||
stateRewatch uint32 = 1 << (28 + iota)
|
||||
stateUnwatch
|
||||
stateCPClose
|
||||
)
|
||||
|
||||
// Filter used in current implementation was split into four segments:
|
||||
// - bits 0-11 store ReadDirectoryChangesW filters,
|
||||
// - bits 12-19 store File notify actions,
|
||||
// - bits 20-27 store notify specific events and flags,
|
||||
// - bits 28-31 store states which are used in loop's FSM.
|
||||
// Constants below are used as masks to retrieve only specific filter parts.
|
||||
const (
|
||||
onlyNotifyChanges uint32 = 0x00000FFF
|
||||
onlyNGlobalEvents uint32 = 0x0FF00000
|
||||
onlyMachineStates uint32 = 0xF0000000
|
||||
)
|
||||
|
||||
// grip represents a single watched directory. It stores the data required by
|
||||
// ReadDirectoryChangesW function. Only the filter, recursive, and handle members
|
||||
// may by modified by watcher implementation. Rest of the them have to remain
|
||||
// constant since they are used by Windows completion routine. This indicates that
|
||||
// grip can be removed only when all operations on the file handle are finished.
|
||||
type grip struct {
|
||||
handle syscall.Handle
|
||||
filter uint32
|
||||
recursive bool
|
||||
pathw []uint16
|
||||
buffer [readBufferSize]byte
|
||||
parent *watched
|
||||
ovlapped *overlappedEx
|
||||
}
|
||||
|
||||
// overlappedEx stores information used in asynchronous input and output.
|
||||
// Additionally, overlappedEx contains a pointer to 'grip' item which is used in
|
||||
// order to gather the structure in which the overlappedEx object was created.
|
||||
type overlappedEx struct {
|
||||
syscall.Overlapped
|
||||
parent *grip
|
||||
}
|
||||
|
||||
// newGrip creates a new file handle that can be used in overlapped operations.
|
||||
// Then, the handle is associated with I/O completion port 'cph' and its value
|
||||
// is stored in newly created 'grip' object.
|
||||
func newGrip(cph syscall.Handle, parent *watched, filter uint32) (*grip, error) {
|
||||
g := &grip{
|
||||
handle: syscall.InvalidHandle,
|
||||
filter: filter,
|
||||
recursive: parent.recursive,
|
||||
pathw: parent.pathw,
|
||||
parent: parent,
|
||||
ovlapped: &overlappedEx{},
|
||||
}
|
||||
if err := g.register(cph); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.ovlapped.parent = g
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// NOTE : Thread safe
|
||||
func (g *grip) register(cph syscall.Handle) (err error) {
|
||||
if g.handle, err = syscall.CreateFile(
|
||||
&g.pathw[0],
|
||||
syscall.FILE_LIST_DIRECTORY,
|
||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
||||
nil,
|
||||
syscall.OPEN_EXISTING,
|
||||
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED,
|
||||
0,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = syscall.CreateIoCompletionPort(g.handle, cph, 0, 0); err != nil {
|
||||
syscall.CloseHandle(g.handle)
|
||||
return
|
||||
}
|
||||
return g.readDirChanges()
|
||||
}
|
||||
|
||||
// readDirChanges tells the system to store file change information in grip's
|
||||
// buffer. Directory changes that occur between calls to this function are added
|
||||
// to the buffer and then, returned with the next call.
|
||||
func (g *grip) readDirChanges() error {
|
||||
return syscall.ReadDirectoryChanges(
|
||||
g.handle,
|
||||
&g.buffer[0],
|
||||
uint32(unsafe.Sizeof(g.buffer)),
|
||||
g.recursive,
|
||||
encode(g.filter),
|
||||
nil,
|
||||
(*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)),
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
// encode transforms a generic filter, which contains platform independent and
|
||||
// implementation specific bit fields, to value that can be used as NotifyFilter
|
||||
// parameter in ReadDirectoryChangesW function.
|
||||
func encode(filter uint32) uint32 {
|
||||
e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges))
|
||||
if e&dirmarker != 0 {
|
||||
return uint32(FileNotifyChangeDirName)
|
||||
}
|
||||
if e&Create != 0 {
|
||||
e = (e ^ Create) | FileNotifyChangeFileName
|
||||
}
|
||||
if e&Remove != 0 {
|
||||
e = (e ^ Remove) | FileNotifyChangeFileName
|
||||
}
|
||||
if e&Write != 0 {
|
||||
e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize |
|
||||
FileNotifyChangeCreation | FileNotifyChangeSecurity
|
||||
}
|
||||
if e&Rename != 0 {
|
||||
e = (e ^ Rename) | FileNotifyChangeFileName
|
||||
}
|
||||
return uint32(e)
|
||||
}
|
||||
|
||||
// watched is made in order to check whether an action comes from a directory or
|
||||
// file. This approach requires two file handlers per single monitored folder. The
|
||||
// second grip handles actions which include creating or deleting a directory. If
|
||||
// these processes are not monitored, only the first grip is created.
|
||||
type watched struct {
|
||||
filter uint32
|
||||
recursive bool
|
||||
count uint8
|
||||
pathw []uint16
|
||||
digrip [2]*grip
|
||||
}
|
||||
|
||||
// newWatched creates a new watched instance. It splits the filter variable into
|
||||
// two parts. The first part is responsible for watching all events which can be
|
||||
// created for a file in watched directory structure and the second one watches
|
||||
// only directory Create/Remove actions. If all operations succeed, the Create
|
||||
// message is sent to I/O completion port queue for further processing.
|
||||
func newWatched(cph syscall.Handle, filter uint32, recursive bool,
|
||||
path string) (wd *watched, err error) {
|
||||
wd = &watched{
|
||||
filter: filter,
|
||||
recursive: recursive,
|
||||
}
|
||||
if wd.pathw, err = syscall.UTF16FromString(path); err != nil {
|
||||
return
|
||||
}
|
||||
if err = wd.recreate(cph); err != nil {
|
||||
return
|
||||
}
|
||||
return wd, nil
|
||||
}
|
||||
|
||||
// TODO : doc
|
||||
func (wd *watched) recreate(cph syscall.Handle) (err error) {
|
||||
filefilter := wd.filter &^ uint32(FileNotifyChangeDirName)
|
||||
if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil {
|
||||
return
|
||||
}
|
||||
dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove)
|
||||
if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil {
|
||||
return
|
||||
}
|
||||
wd.filter &^= onlyMachineStates
|
||||
return
|
||||
}
|
||||
|
||||
// TODO : doc
|
||||
func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool,
|
||||
newflag uint32) (err error) {
|
||||
if reset {
|
||||
wd.digrip[idx] = nil
|
||||
} else {
|
||||
if wd.digrip[idx] == nil {
|
||||
if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil {
|
||||
wd.closeHandle()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
wd.digrip[idx].filter = newflag
|
||||
wd.digrip[idx].recursive = wd.recursive
|
||||
if err = wd.digrip[idx].register(cph); err != nil {
|
||||
wd.closeHandle()
|
||||
return
|
||||
}
|
||||
}
|
||||
wd.count++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// closeHandle closes handles that are stored in digrip array. Function always
|
||||
// tries to close all of the handlers before it exits, even when there are errors
|
||||
// returned from the operating system kernel.
|
||||
func (wd *watched) closeHandle() (err error) {
|
||||
for _, g := range wd.digrip {
|
||||
if g != nil && g.handle != syscall.InvalidHandle {
|
||||
switch suberr := syscall.CloseHandle(g.handle); {
|
||||
case suberr == nil:
|
||||
g.handle = syscall.InvalidHandle
|
||||
case err == nil:
|
||||
err = suberr
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// watcher implements Watcher interface. It stores a set of watched directories.
|
||||
// All operations which remove watched objects from map `m` must be performed in
|
||||
// loop goroutine since these structures are used internally by operating system.
|
||||
type readdcw struct {
|
||||
sync.Mutex
|
||||
m map[string]*watched
|
||||
cph syscall.Handle
|
||||
start bool
|
||||
wg sync.WaitGroup
|
||||
c chan<- EventInfo
|
||||
}
|
||||
|
||||
// NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW.
|
||||
func newWatcher(c chan<- EventInfo) watcher {
|
||||
r := &readdcw{
|
||||
m: make(map[string]*watched),
|
||||
cph: syscall.InvalidHandle,
|
||||
c: c,
|
||||
}
|
||||
runtime.SetFinalizer(r, func(r *readdcw) {
|
||||
if r.cph != syscall.InvalidHandle {
|
||||
syscall.CloseHandle(r.cph)
|
||||
}
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// Watch implements notify.Watcher interface.
|
||||
func (r *readdcw) Watch(path string, event Event) error {
|
||||
return r.watch(path, event, false)
|
||||
}
|
||||
|
||||
// RecursiveWatch implements notify.RecursiveWatcher interface.
|
||||
func (r *readdcw) RecursiveWatch(path string, event Event) error {
|
||||
return r.watch(path, event, true)
|
||||
}
|
||||
|
||||
// watch inserts a directory to the group of watched folders. If watched folder
|
||||
// already exists, function tries to rewatch it with new filters(NOT VALID). Moreover,
|
||||
// watch starts the main event loop goroutine when called for the first time.
|
||||
func (r *readdcw) watch(path string, event Event, recursive bool) (err error) {
|
||||
if event&^(All|fileNotifyChangeAll) != 0 {
|
||||
return errors.New("notify: unknown event")
|
||||
}
|
||||
r.Lock()
|
||||
wd, ok := r.m[path]
|
||||
r.Unlock()
|
||||
if !ok {
|
||||
if err = r.lazyinit(); err != nil {
|
||||
return
|
||||
}
|
||||
r.Lock()
|
||||
if wd, ok = r.m[path]; ok {
|
||||
r.Unlock()
|
||||
return
|
||||
}
|
||||
if wd, err = newWatched(r.cph, uint32(event), recursive, path); err != nil {
|
||||
r.Unlock()
|
||||
return
|
||||
}
|
||||
r.m[path] = wd
|
||||
r.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lazyinit creates an I/O completion port and starts the main event processing
|
||||
// loop. This method uses Double-Checked Locking optimization.
|
||||
func (r *readdcw) lazyinit() (err error) {
|
||||
invalid := uintptr(syscall.InvalidHandle)
|
||||
if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
|
||||
cph := syscall.InvalidHandle
|
||||
if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
r.cph, r.start = cph, true
|
||||
go r.loop()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(pknap) : doc
|
||||
func (r *readdcw) loop() {
|
||||
var n, key uint32
|
||||
var overlapped *syscall.Overlapped
|
||||
for {
|
||||
err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE)
|
||||
if key == stateCPClose {
|
||||
r.Lock()
|
||||
handle := r.cph
|
||||
r.cph = syscall.InvalidHandle
|
||||
r.Unlock()
|
||||
syscall.CloseHandle(handle)
|
||||
r.wg.Done()
|
||||
return
|
||||
}
|
||||
if overlapped == nil {
|
||||
// TODO: check key == rewatch delete or 0(panic)
|
||||
continue
|
||||
}
|
||||
overEx := (*overlappedEx)(unsafe.Pointer(overlapped))
|
||||
if n == 0 {
|
||||
r.loopstate(overEx)
|
||||
} else {
|
||||
r.loopevent(n, overEx)
|
||||
if err = overEx.parent.readDirChanges(); err != nil {
|
||||
// TODO: error handling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pknap) : doc
|
||||
func (r *readdcw) loopstate(overEx *overlappedEx) {
|
||||
filter := atomic.LoadUint32(&overEx.parent.parent.filter)
|
||||
if filter&onlyMachineStates == 0 {
|
||||
return
|
||||
}
|
||||
if overEx.parent.parent.count--; overEx.parent.parent.count == 0 {
|
||||
switch filter & onlyMachineStates {
|
||||
case stateRewatch:
|
||||
r.Lock()
|
||||
overEx.parent.parent.recreate(r.cph)
|
||||
r.Unlock()
|
||||
case stateUnwatch:
|
||||
r.Lock()
|
||||
delete(r.m, syscall.UTF16ToString(overEx.parent.pathw))
|
||||
r.Unlock()
|
||||
case stateCPClose:
|
||||
default:
|
||||
panic(`notify: windows loopstate logic error`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pknap) : doc
|
||||
func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) {
|
||||
events := []*event{}
|
||||
var currOffset uint32
|
||||
for {
|
||||
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset]))
|
||||
name := syscall.UTF16ToString((*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1])
|
||||
events = append(events, &event{
|
||||
pathw: overEx.parent.pathw,
|
||||
filter: overEx.parent.filter,
|
||||
action: raw.Action,
|
||||
name: name,
|
||||
})
|
||||
if raw.NextEntryOffset == 0 {
|
||||
break
|
||||
}
|
||||
if currOffset += raw.NextEntryOffset; currOffset >= n {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.send(events)
|
||||
}
|
||||
|
||||
// TODO(pknap) : doc
|
||||
func (r *readdcw) send(es []*event) {
|
||||
for _, e := range es {
|
||||
var syse Event
|
||||
if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case e.action == syscall.FILE_ACTION_MODIFIED:
|
||||
e.ftype = fTypeUnknown
|
||||
case e.filter&uint32(dirmarker) != 0:
|
||||
e.ftype = fTypeDirectory
|
||||
default:
|
||||
e.ftype = fTypeFile
|
||||
}
|
||||
switch {
|
||||
case e.e == 0:
|
||||
e.e = syse
|
||||
case syse != 0:
|
||||
r.c <- &event{
|
||||
pathw: e.pathw,
|
||||
name: e.name,
|
||||
ftype: e.ftype,
|
||||
action: e.action,
|
||||
filter: e.filter,
|
||||
e: syse,
|
||||
}
|
||||
}
|
||||
r.c <- e
|
||||
}
|
||||
}
|
||||
|
||||
// Rewatch implements notify.Rewatcher interface.
|
||||
func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error {
|
||||
return r.rewatch(path, uint32(oldevent), uint32(newevent), false)
|
||||
}
|
||||
|
||||
// RecursiveRewatch implements notify.RecursiveRewatcher interface.
|
||||
func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent,
|
||||
newevent Event) error {
|
||||
if oldpath != newpath {
|
||||
if err := r.unwatch(oldpath); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.watch(newpath, newevent, true)
|
||||
}
|
||||
return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true)
|
||||
}
|
||||
|
||||
// TODO : (pknap) doc.
|
||||
func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) {
|
||||
if Event(newevent)&^(All|fileNotifyChangeAll) != 0 {
|
||||
return errors.New("notify: unknown event")
|
||||
}
|
||||
var wd *watched
|
||||
r.Lock()
|
||||
if wd, err = r.nonStateWatched(path); err != nil {
|
||||
r.Unlock()
|
||||
return
|
||||
}
|
||||
if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent {
|
||||
panic(`notify: windows re-watcher logic error`)
|
||||
}
|
||||
wd.filter = stateRewatch | newevent
|
||||
wd.recursive, recursive = recursive, wd.recursive
|
||||
if err = wd.closeHandle(); err != nil {
|
||||
wd.filter = oldevent
|
||||
wd.recursive = recursive
|
||||
r.Unlock()
|
||||
return
|
||||
}
|
||||
r.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// TODO : pknap
|
||||
func (r *readdcw) nonStateWatched(path string) (wd *watched, err error) {
|
||||
wd, ok := r.m[path]
|
||||
if !ok || wd == nil {
|
||||
err = errors.New(`notify: ` + path + ` path is unwatched`)
|
||||
return
|
||||
}
|
||||
if filter := atomic.LoadUint32(&wd.filter); filter&onlyMachineStates != 0 {
|
||||
err = errors.New(`notify: another re/unwatching operation in progress`)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Unwatch implements notify.Watcher interface.
|
||||
func (r *readdcw) Unwatch(path string) error {
|
||||
return r.unwatch(path)
|
||||
}
|
||||
|
||||
// RecursiveUnwatch implements notify.RecursiveWatcher interface.
|
||||
func (r *readdcw) RecursiveUnwatch(path string) error {
|
||||
return r.unwatch(path)
|
||||
}
|
||||
|
||||
// TODO : pknap
|
||||
func (r *readdcw) unwatch(path string) (err error) {
|
||||
var wd *watched
|
||||
r.Lock()
|
||||
if wd, err = r.nonStateWatched(path); err != nil {
|
||||
r.Unlock()
|
||||
return
|
||||
}
|
||||
wd.filter |= stateUnwatch
|
||||
if err = wd.closeHandle(); err != nil {
|
||||
wd.filter &^= stateUnwatch
|
||||
r.Unlock()
|
||||
return
|
||||
}
|
||||
r.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Close resets the whole watcher object, closes all existing file descriptors,
|
||||
// and sends stateCPClose state as completion key to the main watcher's loop.
|
||||
func (r *readdcw) Close() (err error) {
|
||||
r.Lock()
|
||||
if !r.start {
|
||||
r.Unlock()
|
||||
return nil
|
||||
}
|
||||
for _, wd := range r.m {
|
||||
wd.filter &^= onlyMachineStates
|
||||
wd.filter |= stateCPClose
|
||||
if e := wd.closeHandle(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
r.start = false
|
||||
r.Unlock()
|
||||
r.wg.Add(1)
|
||||
if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil {
|
||||
return e
|
||||
}
|
||||
r.wg.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
// decode creates a notify event from both non-raw filter and action which was
|
||||
// returned from completion routine. Function may return Event(0) in case when
|
||||
// filter was replaced by a new value which does not contain fields that are
|
||||
// valid with passed action.
|
||||
func decode(filter, action uint32) (Event, Event) {
|
||||
switch action {
|
||||
case syscall.FILE_ACTION_ADDED:
|
||||
return gensys(filter, Create, FileActionAdded)
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
return gensys(filter, Remove, FileActionRemoved)
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
return gensys(filter, Write, FileActionModified)
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
return gensys(filter, Rename, FileActionRenamedOldName)
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
return gensys(filter, Rename, FileActionRenamedNewName)
|
||||
}
|
||||
panic(`notify: cannot decode internal mask`)
|
||||
}
|
||||
|
||||
// gensys decides whether the Windows action, system-independent event or both
|
||||
// of them should be returned. Since the grip's filter may be atomically changed
|
||||
// during watcher lifetime, it is possible that neither Windows nor notify masks
|
||||
// are watched by the user when this function is called.
|
||||
func gensys(filter uint32, ge, se Event) (gene, syse Event) {
|
||||
isdir := filter&uint32(dirmarker) != 0
|
||||
if isdir && filter&uint32(FileNotifyChangeDirName) != 0 ||
|
||||
!isdir && filter&uint32(FileNotifyChangeFileName) != 0 ||
|
||||
filter&uint32(fileNotifyChangeModified) != 0 {
|
||||
syse = se
|
||||
}
|
||||
if filter&uint32(ge) != 0 {
|
||||
gene = ge
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows
|
||||
// +build !kqueue,!solaris
|
||||
|
||||
package notify
|
||||
|
||||
import "errors"
|
||||
|
||||
type stub struct{ error }
|
||||
|
||||
// newWatcher stub.
|
||||
func newWatcher(chan<- EventInfo) watcher {
|
||||
return stub{errors.New("notify: not implemented")}
|
||||
}
|
||||
|
||||
// Following methods implement notify.watcher interface.
|
||||
func (s stub) Watch(string, Event) error { return s }
|
||||
func (s stub) Rewatch(string, Event, Event) error { return s }
|
||||
func (s stub) Unwatch(string) (err error) { return s }
|
||||
func (s stub) Close() error { return s }
|
|
@ -0,0 +1,438 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
|
||||
|
||||
// watcher_trigger is used for FEN and kqueue which behave similarly:
|
||||
// only files and dirs can be watched directly, but not files inside dirs.
|
||||
// As a result Create events have to be generated by implementation when
|
||||
// after Write event is returned for watched dir, it is rescanned and Create
|
||||
// event is returned for new files and these are automatically added
|
||||
// to watchlist. In case of removal of watched directory, native system returns
|
||||
// events for all files, but for Rename, they also need to be generated.
|
||||
// As a result native system works as something like trigger for rescan,
|
||||
// but contains additional data about dir in which changes occurred. For files
|
||||
// detailed data is returned.
|
||||
// Usage of watcher_trigger requires:
|
||||
// - trigger implementation,
|
||||
// - encode func,
|
||||
// - not2nat, nat2not maps.
|
||||
// Required manual operations on filesystem can lead to loss of precision.
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// trigger is to be implemented by platform implementation like FEN or kqueue.
|
||||
type trigger interface {
|
||||
// Close closes watcher's main native file descriptor.
|
||||
Close() error
|
||||
// Stop waiting for new events.
|
||||
Stop() error
|
||||
// Create new instance of watched.
|
||||
NewWatched(string, os.FileInfo) (*watched, error)
|
||||
// Record internally new *watched instance.
|
||||
Record(*watched)
|
||||
// Del removes internal copy of *watched instance.
|
||||
Del(*watched)
|
||||
// Watched returns *watched instance and native events for native type.
|
||||
Watched(interface{}) (*watched, int64, error)
|
||||
// Init initializes native watcher call.
|
||||
Init() error
|
||||
// Watch starts watching provided file/dir.
|
||||
Watch(os.FileInfo, *watched, int64) error
|
||||
// Unwatch stops watching provided file/dir.
|
||||
Unwatch(*watched) error
|
||||
// Wait for new events.
|
||||
Wait() (interface{}, error)
|
||||
// IsStop checks if Wait finished because of request watcher's stop.
|
||||
IsStop(n interface{}, err error) bool
|
||||
}
|
||||
|
||||
// encode Event to native representation. Implementation is to be provided by
|
||||
// platform specific implementation.
|
||||
var encode func(Event) int64
|
||||
|
||||
var (
|
||||
// nat2not matches native events to notify's ones. To be initialized by
|
||||
// platform dependent implementation.
|
||||
nat2not map[Event]Event
|
||||
// not2nat matches notify's events to native ones. To be initialized by
|
||||
// platform dependent implementation.
|
||||
not2nat map[Event]Event
|
||||
)
|
||||
|
||||
// trg is a main structure implementing watcher.
|
||||
type trg struct {
|
||||
sync.Mutex
|
||||
// s is a channel used to stop monitoring.
|
||||
s chan struct{}
|
||||
// c is a channel used to pass events further.
|
||||
c chan<- EventInfo
|
||||
// pthLkp is a data structure mapping file names with data about watching
|
||||
// represented by them files/directories.
|
||||
pthLkp map[string]*watched
|
||||
// t is a platform dependent implementation of trigger.
|
||||
t trigger
|
||||
}
|
||||
|
||||
// newWatcher returns new watcher's implementation.
|
||||
func newWatcher(c chan<- EventInfo) watcher {
|
||||
t := &trg{
|
||||
s: make(chan struct{}, 1),
|
||||
pthLkp: make(map[string]*watched, 0),
|
||||
c: c,
|
||||
}
|
||||
t.t = newTrigger(t.pthLkp)
|
||||
if err := t.t.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go t.monitor()
|
||||
return t
|
||||
}
|
||||
|
||||
// Close implements watcher.
|
||||
func (t *trg) Close() (err error) {
|
||||
t.Lock()
|
||||
if err = t.t.Stop(); err != nil {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
<-t.s
|
||||
var e error
|
||||
for _, w := range t.pthLkp {
|
||||
if e = t.unwatch(w.p, w.fi); e != nil {
|
||||
dbgprintf("trg: unwatch %q failed: %q\n", w.p, e)
|
||||
err = nonil(err, e)
|
||||
}
|
||||
}
|
||||
if e = t.t.Close(); e != nil {
|
||||
dbgprintf("trg: closing native watch failed: %q\n", e)
|
||||
err = nonil(err, e)
|
||||
}
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// send reported events one by one through chan.
|
||||
func (t *trg) send(evn []event) {
|
||||
for i := range evn {
|
||||
t.c <- &evn[i]
|
||||
}
|
||||
}
|
||||
|
||||
// singlewatch starts to watch given p file/directory.
|
||||
func (t *trg) singlewatch(p string, e Event, direct mode, fi os.FileInfo) (err error) {
|
||||
w, ok := t.pthLkp[p]
|
||||
if !ok {
|
||||
if w, err = t.t.NewWatched(p, fi); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
switch direct {
|
||||
case dir:
|
||||
w.eDir |= e
|
||||
case ndir:
|
||||
w.eNonDir |= e
|
||||
case both:
|
||||
w.eDir |= e
|
||||
w.eNonDir |= e
|
||||
}
|
||||
var ee int64
|
||||
// Native Write event is added to wait for Create events (Write event on
|
||||
// directory triggers it's rescan).
|
||||
if e&Create != 0 && fi.IsDir() {
|
||||
ee = int64(not2nat[Write])
|
||||
}
|
||||
if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir)|ee); err != nil {
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
t.t.Record(w)
|
||||
return nil
|
||||
}
|
||||
return errAlreadyWatched
|
||||
}
|
||||
|
||||
// decode converts event received from native to notify.Event
|
||||
// representation taking into account requested events (w).
|
||||
func decode(o int64, w Event) (e Event) {
|
||||
for f, n := range nat2not {
|
||||
if o&int64(f) != 0 {
|
||||
if w&f != 0 {
|
||||
e |= f
|
||||
}
|
||||
if w&n != 0 {
|
||||
e |= n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *trg) watch(p string, e Event, fi os.FileInfo) error {
|
||||
if err := t.singlewatch(p, e, dir, fi); err != nil {
|
||||
if err != errAlreadyWatched {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if fi.IsDir() {
|
||||
err := t.walk(p, func(fi os.FileInfo) (err error) {
|
||||
if err = t.singlewatch(filepath.Join(p, fi.Name()), e, ndir,
|
||||
fi); err != nil {
|
||||
if err != errAlreadyWatched {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// walk runs f func on each file/dir from p directory.
|
||||
func (t *trg) walk(p string, fn func(os.FileInfo) error) error {
|
||||
fp, err := os.Open(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ls, err := fp.Readdir(0)
|
||||
fp.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range ls {
|
||||
if err := fn(ls[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *trg) unwatch(p string, fi os.FileInfo) error {
|
||||
if fi.IsDir() {
|
||||
err := t.walk(p, func(fi os.FileInfo) error {
|
||||
err := t.singleunwatch(filepath.Join(p, fi.Name()), ndir)
|
||||
if err != errNotWatched {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return t.singleunwatch(p, dir)
|
||||
}
|
||||
|
||||
// Watch implements Watcher interface.
|
||||
func (t *trg) Watch(p string, e Event) error {
|
||||
fi, err := os.Stat(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Lock()
|
||||
err = t.watch(p, e, fi)
|
||||
t.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// Unwatch implements Watcher interface.
|
||||
func (t *trg) Unwatch(p string) error {
|
||||
fi, err := os.Stat(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Lock()
|
||||
err = t.unwatch(p, fi)
|
||||
t.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// Rewatch implements Watcher interface.
|
||||
//
|
||||
// TODO(rjeczalik): This is a naive hack. Rewrite might help.
|
||||
func (t *trg) Rewatch(p string, _, e Event) error {
|
||||
fi, err := os.Stat(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Lock()
|
||||
if err = t.unwatch(p, fi); err == nil {
|
||||
// TODO(rjeczalik): If watch fails then we leave trigger in inconsistent
|
||||
// state. Handle? Panic? Native version of rewatch?
|
||||
err = t.watch(p, e, fi)
|
||||
}
|
||||
t.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*trg) file(w *watched, n interface{}, e Event) (evn []event) {
|
||||
evn = append(evn, event{w.p, e, w.fi.IsDir(), n})
|
||||
return
|
||||
}
|
||||
|
||||
func (t *trg) dir(w *watched, n interface{}, e, ge Event) (evn []event) {
|
||||
// If it's dir and delete we have to send it and continue, because
|
||||
// other processing relies on opening (in this case not existing) dir.
|
||||
// Events for contents of this dir are reported by native impl.
|
||||
// However events for rename must be generated for all monitored files
|
||||
// inside of moved directory, because native impl does not report it independently
|
||||
// for each file descriptor being moved in result of move action on
|
||||
// parent dirLiczba dostępnych dni urlopowych: 0ectory.
|
||||
if (ge & (not2nat[Rename] | not2nat[Remove])) != 0 {
|
||||
// Write is reported also for Remove on directory. Because of that
|
||||
// we have to filter it out explicitly.
|
||||
evn = append(evn, event{w.p, e & ^Write & ^not2nat[Write], true, n})
|
||||
if ge¬2nat[Rename] != 0 {
|
||||
for p := range t.pthLkp {
|
||||
if strings.HasPrefix(p, w.p+string(os.PathSeparator)) {
|
||||
if err := t.singleunwatch(p, both); err != nil && err != errNotWatched &&
|
||||
!os.IsNotExist(err) {
|
||||
dbgprintf("trg: failed stop watching moved file (%q): %q\n",
|
||||
p, err)
|
||||
}
|
||||
if (w.eDir|w.eNonDir)&(not2nat[Rename]|Rename) != 0 {
|
||||
evn = append(evn, event{
|
||||
p, (w.eDir | w.eNonDir) & e &^ Write &^ not2nat[Write],
|
||||
w.fi.IsDir(), nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
t.t.Del(w)
|
||||
return
|
||||
}
|
||||
if (ge & not2nat[Write]) != 0 {
|
||||
switch err := t.walk(w.p, func(fi os.FileInfo) error {
|
||||
p := filepath.Join(w.p, fi.Name())
|
||||
switch err := t.singlewatch(p, w.eDir, ndir, fi); {
|
||||
case os.IsNotExist(err) && ((w.eDir & Remove) != 0):
|
||||
evn = append(evn, event{p, Remove, fi.IsDir(), n})
|
||||
case err == errAlreadyWatched:
|
||||
case err != nil:
|
||||
dbgprintf("trg: watching %q failed: %q", p, err)
|
||||
case (w.eDir & Create) != 0:
|
||||
evn = append(evn, event{p, Create, fi.IsDir(), n})
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}); {
|
||||
case os.IsNotExist(err):
|
||||
return
|
||||
case err != nil:
|
||||
dbgprintf("trg: dir processing failed: %q", err)
|
||||
default:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type mode uint
|
||||
|
||||
const (
|
||||
dir mode = iota
|
||||
ndir
|
||||
both
|
||||
)
|
||||
|
||||
// unwatch stops watching p file/directory.
|
||||
func (t *trg) singleunwatch(p string, direct mode) error {
|
||||
w, ok := t.pthLkp[p]
|
||||
if !ok {
|
||||
return errNotWatched
|
||||
}
|
||||
switch direct {
|
||||
case dir:
|
||||
w.eDir = 0
|
||||
case ndir:
|
||||
w.eNonDir = 0
|
||||
case both:
|
||||
w.eDir, w.eNonDir = 0, 0
|
||||
}
|
||||
if err := t.t.Unwatch(w); err != nil {
|
||||
return err
|
||||
}
|
||||
if w.eNonDir|w.eDir != 0 {
|
||||
mod := dir
|
||||
if w.eNonDir == 0 {
|
||||
mod = ndir
|
||||
}
|
||||
if err := t.singlewatch(p, w.eNonDir|w.eDir, mod,
|
||||
w.fi); err != nil && err != errAlreadyWatched {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
t.t.Del(w)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *trg) monitor() {
|
||||
var (
|
||||
n interface{}
|
||||
err error
|
||||
)
|
||||
for {
|
||||
switch n, err = t.t.Wait(); {
|
||||
case err == syscall.EINTR:
|
||||
case t.t.IsStop(n, err):
|
||||
t.s <- struct{}{}
|
||||
return
|
||||
case err != nil:
|
||||
dbgprintf("trg: failed to read events: %q\n", err)
|
||||
default:
|
||||
t.send(t.process(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process event returned by port_get call.
|
||||
func (t *trg) process(n interface{}) (evn []event) {
|
||||
t.Lock()
|
||||
w, ge, err := t.t.Watched(n)
|
||||
if err != nil {
|
||||
t.Unlock()
|
||||
dbgprintf("trg: %v event lookup failed: %q", Event(ge), err)
|
||||
return
|
||||
}
|
||||
|
||||
e := decode(ge, w.eDir|w.eNonDir)
|
||||
if ge&int64(not2nat[Remove]|not2nat[Rename]) == 0 {
|
||||
switch fi, err := os.Stat(w.p); {
|
||||
case err != nil:
|
||||
default:
|
||||
if err = t.t.Watch(fi, w, (encode(w.eDir | w.eNonDir))); err != nil {
|
||||
dbgprintf("trg: %q is no longer watched: %q", w.p, err)
|
||||
t.t.Del(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
if e == Event(0) {
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if w.fi.IsDir() {
|
||||
evn = append(evn, t.dir(w, n, e, Event(ge))...)
|
||||
} else {
|
||||
evn = append(evn, t.file(w, n, e)...)
|
||||
}
|
||||
if Event(ge)&(not2nat[Remove]|not2nat[Rename]) != 0 {
|
||||
t.t.Del(w)
|
||||
}
|
||||
t.Unlock()
|
||||
return
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package notify
|
||||
|
||||
// EventDiff describes a change to an event set - EventDiff[0] is an old state,
|
||||
// while EventDiff[1] is a new state. If event set has not changed (old == new),
|
||||
// functions typically return the None value.
|
||||
type eventDiff [2]Event
|
||||
|
||||
func (diff eventDiff) Event() Event {
|
||||
return diff[1] &^ diff[0]
|
||||
}
|
||||
|
||||
// Watchpoint
|
||||
//
|
||||
// The nil key holds total event set - logical sum for all registered events.
|
||||
// It speeds up computing EventDiff for Add method.
|
||||
//
|
||||
// The rec key holds an event set for a watchpoints created by RecursiveWatch
|
||||
// for a Watcher implementation which is not natively recursive.
|
||||
type watchpoint map[chan<- EventInfo]Event
|
||||
|
||||
// None is an empty event diff, think null object.
|
||||
var none eventDiff
|
||||
|
||||
// rec is just a placeholder
|
||||
var rec = func() (ch chan<- EventInfo) {
|
||||
ch = make(chan<- EventInfo)
|
||||
close(ch)
|
||||
return
|
||||
}()
|
||||
|
||||
func (wp watchpoint) dryAdd(ch chan<- EventInfo, e Event) eventDiff {
|
||||
if e &^= internal; wp[ch]&e == e {
|
||||
return none
|
||||
}
|
||||
total := wp[ch] &^ internal
|
||||
return eventDiff{total, total | e}
|
||||
}
|
||||
|
||||
// Add assumes neither c nor e are nil or zero values.
|
||||
func (wp watchpoint) Add(c chan<- EventInfo, e Event) (diff eventDiff) {
|
||||
wp[c] |= e
|
||||
diff[0] = wp[nil]
|
||||
diff[1] = diff[0] | e
|
||||
wp[nil] = diff[1] &^ omit
|
||||
// Strip diff from internal events.
|
||||
diff[0] &^= internal
|
||||
diff[1] &^= internal
|
||||
if diff[0] == diff[1] {
|
||||
return none
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (wp watchpoint) Del(c chan<- EventInfo, e Event) (diff eventDiff) {
|
||||
wp[c] &^= e
|
||||
if wp[c] == 0 {
|
||||
delete(wp, c)
|
||||
}
|
||||
diff[0] = wp[nil]
|
||||
delete(wp, nil)
|
||||
if len(wp) != 0 {
|
||||
// Recalculate total event set.
|
||||
for _, e := range wp {
|
||||
diff[1] |= e
|
||||
}
|
||||
wp[nil] = diff[1] &^ omit
|
||||
}
|
||||
// Strip diff from internal events.
|
||||
diff[0] &^= internal
|
||||
diff[1] &^= internal
|
||||
if diff[0] == diff[1] {
|
||||
return none
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (wp watchpoint) Dispatch(ei EventInfo, extra Event) {
|
||||
e := eventmask(ei, extra)
|
||||
if !matches(wp[nil], e) {
|
||||
return
|
||||
}
|
||||
for ch, eset := range wp {
|
||||
if ch != nil && matches(eset, e) {
|
||||
select {
|
||||
case ch <- ei:
|
||||
default: // Drop event if receiver is too slow
|
||||
dbgprintf("dropped %s on %q: receiver too slow", ei.Event(), ei.Path())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wp watchpoint) Total() Event {
|
||||
return wp[nil] &^ internal
|
||||
}
|
||||
|
||||
func (wp watchpoint) IsRecursive() bool {
|
||||
return wp[nil]&recursive != 0
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package notify
|
||||
|
||||
// eventmask uses ei to create a new event which contains internal flags used by
|
||||
// notify package logic.
|
||||
func eventmask(ei EventInfo, extra Event) Event {
|
||||
return ei.Event() | extra
|
||||
}
|
||||
|
||||
// matches reports a match only when:
|
||||
//
|
||||
// - for user events, when event is present in the given set
|
||||
// - for internal events, when additionaly both event and set have omit bit set
|
||||
//
|
||||
// Internal events must not be sent to user channels and vice versa.
|
||||
func matches(set, event Event) bool {
|
||||
return (set&omit)^(event&omit) == 0 && set&event == event
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package notify
|
||||
|
||||
// eventmask uses ei to create a new event which contains internal flags used by
|
||||
// notify package logic. If one of FileAction* masks is detected, this function
|
||||
// adds corresponding FileNotifyChange* values. This allows non registered
|
||||
// FileAction* events to be passed on.
|
||||
func eventmask(ei EventInfo, extra Event) (e Event) {
|
||||
if e = ei.Event() | extra; e&fileActionAll != 0 {
|
||||
if ev, ok := ei.(*event); ok {
|
||||
switch ev.ftype {
|
||||
case fTypeFile:
|
||||
e |= FileNotifyChangeFileName
|
||||
case fTypeDirectory:
|
||||
e |= FileNotifyChangeDirName
|
||||
case fTypeUnknown:
|
||||
e |= fileNotifyChangeModified
|
||||
}
|
||||
return e &^ fileActionAll
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// matches reports a match only when:
|
||||
//
|
||||
// - for user events, when event is present in the given set
|
||||
// - for internal events, when additionally both event and set have omit bit set
|
||||
//
|
||||
// Internal events must not be sent to user channels and vice versa.
|
||||
func matches(set, event Event) bool {
|
||||
return (set&omit)^(event&omit) == 0 && (set&event == event || set&fileNotifyChangeModified&event != 0)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue