mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
http-stream format (#1202)
* POC code for inotify UDS-io-socket * http-stream format introducing the `http-stream` format support in fn. there are many details for this, none of which can be linked from github :( -- docs are coming (I could even try to add some here?). this is kinda MVP-ish level, but does not implement the remaining spec, ie 'headers' fixing up / invoke fixing up. the thinking being we can land this to test fdks / cli with and start splitting work up on top of this. all other formats work the same as previous (no breakage, only new stuff) with the cli you can set `format: http-stream` and deploy, and then invoke a function via the `http-stream` format. this uses unix domain socket (uds) on the container instead of previous stdin/stdout, and fdks will have to support this in a new fashion (will see about getting docs on here). fdk-go works, which is here: https://github.com/fnproject/fdk-go/pull/30 . the output looks the same as an http format function when invoking a function. wahoo. there's some amount of stuff we can clean up here, enumerated: * the cleanup of the sock files is iffy, high pri here * permissions are a pain in the ass and i punted on dealing with them. you can run `sudo ./fnserver` if running locally, it may/may not work in dind(?) ootb * no pipe usage at all (yay), still could reduce buffer usage around the pipe behavior, we could clean this up potentially before removal (and tests) * my brain can’t figure out if dispatchOldFormats changes pipe behavior, but tests work * i marked XXX to do some clean up which will follow soon… need this to test fdk tho so meh, any thoughts on those marked would be appreciated however (1 less decision for me). mostly happy w/ general shape/plumbing tho * there are no tests atm, this is a tricky dance indeed. attempts were made. need to futz with the permission stuff before committing to adding any tests here, which I don't like either. also, need to get the fdk-go based test image updated according to the fdk-go, and there's a dance there too. rumba time.. * delaying the big big cleanup until we have good enough fdk support to kill all the other formats. open to ideas on how to maneuver landing stuff... * fix unmount * see if the tests work on ci... * add call id header * fix up makefile * add configurable iofs opts * add format file describing http-stream contract * rm some cruft * default iofs to /tmp, remove mounting out of the box fn we can't mount. /tmp will provide a memory backed fs for us on most systems, this will be fine for local developing and this can be configured to be wherever for anyone that wants to make things more difficult for themselves. also removes the mounting, this has to be done as root. we can't do this in the oss fn (short of requesting root, but no). in the future, we may want to have a knob here to have a function that can be configured in fn that allows further configuration here. since we don't know what we need in this dept really, not doing that yet (it may be the case that it could be done operationally outside of fn, eg, but not if each directory needs to be configured itself, which seems likely, anyway...) * add WIP note just in case...
This commit is contained in:
8
Gopkg.lock
generated
8
Gopkg.lock
generated
@@ -125,6 +125,12 @@
|
|||||||
]
|
]
|
||||||
revision = "1eb29530716f262bad5b83eb9a5b3f7483636949"
|
revision = "1eb29530716f262bad5b83eb9a5b3f7483636949"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/fsnotify/fsnotify"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||||
|
version = "v1.4.7"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/fsouza/go-dockerclient"
|
name = "github.com/fsouza/go-dockerclient"
|
||||||
packages = [
|
packages = [
|
||||||
@@ -502,6 +508,6 @@
|
|||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "3a999b52438a7f308dfa208212e3c3154f7a310f024d3f288b54334868ab8ef9"
|
inputs-digest = "2f46ba75652e354a0cffe42fa0b49096f42e21861118a7eff0f318c6d54b1507"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -8,9 +8,12 @@ dep-up:
|
|||||||
dep ensure
|
dep ensure
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: api/agent/grpc/runner.pb.go
|
build:
|
||||||
go build -o fnserver ./cmd/fnserver
|
go build -o fnserver ./cmd/fnserver
|
||||||
|
|
||||||
|
.PHONY: generate
|
||||||
|
generate: api/agent/grpc/runner.pb.go
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install:
|
install:
|
||||||
go build -o ${GOPATH}/bin/fnserver ./cmd/fnserver
|
go build -o ${GOPATH}/bin/fnserver ./cmd/fnserver
|
||||||
@@ -130,4 +133,4 @@ docker-test:
|
|||||||
-v $(shell docker run --rm -ti -v ${CURDIR}:/go/src/github.com/fnproject/fn -w /go/src/github.com/fnproject/fn -e GOPATH=/go golang:alpine sh -c 'go list ./... | grep -v vendor | grep -v examples | grep -v tool | grep -v fn')
|
-v $(shell docker run --rm -ti -v ${CURDIR}:/go/src/github.com/fnproject/fn -w /go/src/github.com/fnproject/fn -e GOPATH=/go golang:alpine sh -c 'go list ./... | grep -v vendor | grep -v examples | grep -v tool | grep -v fn')
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: dep build
|
all: dep generate build
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,6 +20,7 @@ import (
|
|||||||
"github.com/fnproject/fn/api/id"
|
"github.com/fnproject/fn/api/id"
|
||||||
"github.com/fnproject/fn/api/models"
|
"github.com/fnproject/fn/api/models"
|
||||||
"github.com/fnproject/fn/fnext"
|
"github.com/fnproject/fn/fnext"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"go.opencensus.io/stats"
|
"go.opencensus.io/stats"
|
||||||
"go.opencensus.io/trace"
|
"go.opencensus.io/trace"
|
||||||
@@ -646,6 +652,7 @@ type hotSlot struct {
|
|||||||
errC <-chan error // container error
|
errC <-chan error // container error
|
||||||
container *container // TODO mask this
|
container *container // TODO mask this
|
||||||
cfg *Config
|
cfg *Config
|
||||||
|
udsClient http.Client
|
||||||
fatalErr error
|
fatalErr error
|
||||||
containerSpan trace.SpanContext
|
containerSpan trace.SpanContext
|
||||||
}
|
}
|
||||||
@@ -681,26 +688,14 @@ func (s *hotSlot) exec(ctx context.Context, call *call) error {
|
|||||||
Type: trace.LinkTypeChild,
|
Type: trace.LinkTypeChild,
|
||||||
})
|
})
|
||||||
|
|
||||||
// swap in fresh pipes & stat accumulator to not interlace with other calls that used this slot [and timed out]
|
call.req = call.req.WithContext(ctx) // TODO this is funny biz reed is bad
|
||||||
stdinRead, stdinWrite := io.Pipe()
|
|
||||||
stdoutRead, stdoutWritePipe := io.Pipe()
|
|
||||||
defer stdinRead.Close()
|
|
||||||
defer stdoutWritePipe.Close()
|
|
||||||
|
|
||||||
// NOTE: stderr is limited separately (though line writer is vulnerable to attack?)
|
var errApp chan error
|
||||||
// limit the bytes allowed to be written to the stdout pipe, which handles any
|
if call.Format == models.FormatHTTPStream {
|
||||||
// buffering overflows (json to a string, http to a buffer, etc)
|
errApp = s.dispatch(ctx, call)
|
||||||
stdoutWrite := common.NewClampWriter(stdoutWritePipe, s.cfg.MaxResponseSize, models.ErrFunctionResponseTooBig)
|
} else { // TODO remove this block one glorious day
|
||||||
|
errApp = s.dispatchOldFormats(ctx, call)
|
||||||
proto := protocol.New(protocol.Protocol(call.Format), stdinWrite, stdoutRead)
|
}
|
||||||
swapBack := s.container.swap(stdinRead, stdoutWrite, call.stderr, &call.Stats)
|
|
||||||
defer swapBack() // NOTE: it's important this runs before the pipes are closed.
|
|
||||||
|
|
||||||
errApp := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
ci := protocol.NewCallInfo(call.IsCloudEvent, call.Call, call.req.WithContext(ctx))
|
|
||||||
errApp <- proto.Dispatch(ctx, ci, call.w)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err := <-s.errC: // error from container
|
case err := <-s.errC: // error from container
|
||||||
@@ -723,6 +718,93 @@ func (s *hotSlot) exec(ctx context.Context, call *call) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *hotSlot) dispatch(ctx context.Context, call *call) chan error {
|
||||||
|
ctx, span := trace.StartSpan(ctx, "agent_dispatch_httpstream")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
// TODO we can't trust that resp.Write doesn't timeout, even if the http
|
||||||
|
// client should respect the request context (right?) so we still need this (right?)
|
||||||
|
errApp := make(chan error, 1)
|
||||||
|
|
||||||
|
req := call.req
|
||||||
|
req.RequestURI = "" // we have to clear this before using it as a client request, see https://golang.org/pkg/net/http/#Request
|
||||||
|
|
||||||
|
//req.Header.Set("FN_DEADLINE", ci.Deadline().String())
|
||||||
|
req.Header.Set("FN_CALL_ID", call.ID)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
resp, err := s.udsClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
errApp <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case errApp <- writeResp(resp, call.w):
|
||||||
|
case <-ctx.Done():
|
||||||
|
errApp <- ctx.Err()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return errApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX(reed): dupe code in http proto (which will die...)
|
||||||
|
func writeResp(resp *http.Response, w io.Writer) error {
|
||||||
|
rw, ok := w.(http.ResponseWriter)
|
||||||
|
if !ok {
|
||||||
|
return resp.Write(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're writing directly to the response writer, we need to set headers
|
||||||
|
// and status code, and only copy the body. resp.Write would copy a full
|
||||||
|
// http request into the response body (not what we want).
|
||||||
|
|
||||||
|
for k, vs := range resp.Header {
|
||||||
|
for _, v := range vs {
|
||||||
|
rw.Header().Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if resp.StatusCode > 0 {
|
||||||
|
rw.WriteHeader(resp.StatusCode)
|
||||||
|
}
|
||||||
|
_, err := io.Copy(rw, resp.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove
|
||||||
|
func (s *hotSlot) dispatchOldFormats(ctx context.Context, call *call) chan error {
|
||||||
|
|
||||||
|
errApp := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
// XXX(reed): this may be liable to leave the pipes fucked up if dispatch times out, eg
|
||||||
|
// we may need ye ole close() func to put the Close()/swapBack() in from the caller
|
||||||
|
|
||||||
|
// swap in fresh pipes & stat accumulator to not interlace with other calls that used this slot [and timed out]
|
||||||
|
stdinRead, stdinWrite := io.Pipe()
|
||||||
|
stdoutRead, stdoutWritePipe := io.Pipe()
|
||||||
|
defer stdinRead.Close()
|
||||||
|
defer stdoutWritePipe.Close()
|
||||||
|
|
||||||
|
// NOTE: stderr is limited separately (though line writer is vulnerable to attack?)
|
||||||
|
// limit the bytes allowed to be written to the stdout pipe, which handles any
|
||||||
|
// buffering overflows (json to a string, http to a buffer, etc)
|
||||||
|
stdoutWrite := common.NewClampWriter(stdoutWritePipe, s.cfg.MaxResponseSize, models.ErrFunctionResponseTooBig)
|
||||||
|
|
||||||
|
swapBack := s.container.swap(stdinRead, stdoutWrite, call.stderr, &call.Stats)
|
||||||
|
defer swapBack() // NOTE: it's important this runs before the pipes are closed.
|
||||||
|
|
||||||
|
// TODO this should get killed completely
|
||||||
|
// TODO we could alternatively dial in and use the conn as stdin/stdout for an interim solution
|
||||||
|
// XXX(reed): ^^^ do we need that for the cloud event dance ????
|
||||||
|
proto := protocol.New(protocol.Protocol(call.Format), stdinWrite, stdoutRead)
|
||||||
|
ci := protocol.NewCallInfo(call.IsCloudEvent, call.Call, call.req)
|
||||||
|
errApp <- proto.Dispatch(ctx, ci, call.w)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return errApp
|
||||||
|
}
|
||||||
|
|
||||||
func (a *agent) prepCold(ctx context.Context, call *call, tok ResourceToken, ch chan Slot) {
|
func (a *agent) prepCold(ctx context.Context, call *call, tok ResourceToken, ch chan Slot) {
|
||||||
ctx, span := trace.StartSpan(ctx, "agent_prep_cold")
|
ctx, span := trace.StartSpan(ctx, "agent_prep_cold")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
@@ -792,8 +874,33 @@ func (a *agent) runHot(ctx context.Context, call *call, tok ResourceToken, state
|
|||||||
state.UpdateState(ctx, ContainerStateStart, call.slots)
|
state.UpdateState(ctx, ContainerStateStart, call.slots)
|
||||||
defer state.UpdateState(ctx, ContainerStateDone, call.slots)
|
defer state.UpdateState(ctx, ContainerStateDone, call.slots)
|
||||||
|
|
||||||
container, closer := newHotContainer(ctx, call, &a.cfg)
|
container, err := newHotContainer(ctx, call, &a.cfg)
|
||||||
defer closer()
|
if err != nil {
|
||||||
|
call.slots.queueSlot(&hotSlot{done: make(chan struct{}), fatalErr: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer container.Close()
|
||||||
|
|
||||||
|
// NOTE: soon this isn't assigned in a branch...
|
||||||
|
var udsClient http.Client
|
||||||
|
udsAwait := make(chan error)
|
||||||
|
if call.Format == models.FormatHTTPStream {
|
||||||
|
// start our listener before starting the container, so we don't miss the pretty things whispered in our ears
|
||||||
|
// XXX(reed): figure out cleaner way to carry around the directory and expose the lsnr.sock file
|
||||||
|
go inotifyUDS(ctx, container.UDSPath(), udsAwait)
|
||||||
|
|
||||||
|
udsClient = http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
// XXX(reed): other settings ?
|
||||||
|
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||||
|
var d net.Dialer
|
||||||
|
return d.DialContext(ctx, "unix", container.UDSPath()+"/lsnr.sock") // XXX(reed): hardcoded lsnr.sock
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
close(udsAwait) // XXX(reed): short case first / kill this
|
||||||
|
}
|
||||||
|
|
||||||
logger := logrus.WithFields(logrus.Fields{"id": container.id, "app_id": call.AppID, "route": call.Path, "image": call.Image, "memory": call.Memory, "cpus": call.CPUs, "format": call.Format, "idle_timeout": call.IdleTimeout})
|
logger := logrus.WithFields(logrus.Fields{"id": container.id, "app_id": call.AppID, "route": call.Path, "image": call.Image, "memory": call.Memory, "cpus": call.CPUs, "format": call.Format, "idle_timeout": call.IdleTimeout})
|
||||||
ctx = common.WithLogger(ctx, logger)
|
ctx = common.WithLogger(ctx, logger)
|
||||||
@@ -804,7 +911,7 @@ func (a *agent) runHot(ctx context.Context, call *call, tok ResourceToken, state
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer cookie.Close(ctx) // NOTE ensure this ctx doesn't time out
|
defer cookie.Close(ctx)
|
||||||
|
|
||||||
err = a.driver.PrepareCookie(ctx, cookie)
|
err = a.driver.PrepareCookie(ctx, cookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -818,6 +925,19 @@ func (a *agent) runHot(ctx context.Context, call *call, tok ResourceToken, state
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now we wait for the socket to be created before handing out any slots
|
||||||
|
select {
|
||||||
|
case err := <-udsAwait: // XXX(reed): need to leave a note about pairing ctx here?
|
||||||
|
// sends a nil error if all is good, we can proceed...
|
||||||
|
if err != nil {
|
||||||
|
call.slots.queueSlot(&hotSlot{done: make(chan struct{}), fatalErr: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
call.slots.queueSlot(&hotSlot{done: make(chan struct{}), fatalErr: ctx.Err()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// container is running
|
// container is running
|
||||||
state.UpdateState(ctx, ContainerStateIdle, call.slots)
|
state.UpdateState(ctx, ContainerStateIdle, call.slots)
|
||||||
|
|
||||||
@@ -843,6 +963,7 @@ func (a *agent) runHot(ctx context.Context, call *call, tok ResourceToken, state
|
|||||||
errC: errC,
|
errC: errC,
|
||||||
container: container,
|
container: container,
|
||||||
cfg: &a.cfg,
|
cfg: &a.cfg,
|
||||||
|
udsClient: udsClient,
|
||||||
containerSpan: trace.FromContext(ctx).SpanContext(),
|
containerSpan: trace.FromContext(ctx).SpanContext(),
|
||||||
}
|
}
|
||||||
if !a.runHotReq(ctx, call, state, logger, cookie, slot) {
|
if !a.runHotReq(ctx, call, state, logger, cookie, slot) {
|
||||||
@@ -867,6 +988,82 @@ func (a *agent) runHot(ctx context.Context, call *call, tok ResourceToken, state
|
|||||||
logger.WithError(res.Error()).Info("hot function terminated")
|
logger.WithError(res.Error()).Info("hot function terminated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createIOFS(cfg *Config) (string, error) {
|
||||||
|
// XXX(reed): need to ensure these are cleaned up if any of these ops in here fail...
|
||||||
|
|
||||||
|
dir := cfg.IOFSPath
|
||||||
|
if dir == "" {
|
||||||
|
// /tmp should be a memory backed filesystem, where we can get user perms
|
||||||
|
// on the socket file (fdks must give write permissions to users on sock).
|
||||||
|
// /var/run is root only, hence this...
|
||||||
|
dir = "/tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a tmpdir
|
||||||
|
iofsDir, err := ioutil.TempDir(dir, "iofs")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("cannot create tmpdir for iofs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := cfg.IOFSOpts
|
||||||
|
if opts == "" {
|
||||||
|
// opts = "size=1k,nr_inodes=8,mode=0777"
|
||||||
|
}
|
||||||
|
|
||||||
|
// under tmpdir, create tmpfs
|
||||||
|
// TODO uh, yea, idk
|
||||||
|
//if cfg.IOFSPath != "" {
|
||||||
|
//err = syscall.Mount("tmpfs", iofsDir, "tmpfs", uintptr( [>syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV<] 0), opts)
|
||||||
|
//if err != nil {
|
||||||
|
//return "", fmt.Errorf("cannot mount/create tmpfs=%s", iofsDir)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
return iofsDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func inotifyUDS(ctx context.Context, iofsDir string, awaitUDS chan<- error) {
|
||||||
|
// XXX(reed): I forgot how to plumb channels temporarily forgive me for this sin (inotify will timeout, this is just bad programming)
|
||||||
|
err := inotifyAwait(ctx, iofsDir)
|
||||||
|
select {
|
||||||
|
case awaitUDS <- err:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inotifyAwait(ctx context.Context, iofsDir string) error {
|
||||||
|
ctx, span := trace.StartSpan(ctx, "inotify_await")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
fsWatcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting fsnotify watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer fsWatcher.Close()
|
||||||
|
|
||||||
|
err = fsWatcher.Add(iofsDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error adding iofs dir to fswatcher: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// XXX(reed): damn it would sure be nice to tell users they didn't make a uds and that's why it timed out
|
||||||
|
return ctx.Err()
|
||||||
|
case err := <-fsWatcher.Errors:
|
||||||
|
return fmt.Errorf("error watching for iofs: %v", err)
|
||||||
|
case event := <-fsWatcher.Events:
|
||||||
|
common.Logger(ctx).WithField("event", event).Debug("fsnotify event")
|
||||||
|
if event.Op&fsnotify.Create == fsnotify.Create && event.Name == iofsDir+"/lsnr.sock" {
|
||||||
|
// XXX(reed): hardcoded /lsnr.sock path
|
||||||
|
// wait until the socket file is created by the container
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// runHotReq enqueues a free slot to slot queue manager and watches various timers and the consumer until
|
// runHotReq enqueues a free slot to slot queue manager and watches various timers and the consumer until
|
||||||
// the slot is consumed. A return value of false means, the container should shutdown and no subsequent
|
// the slot is consumed. A return value of false means, the container should shutdown and no subsequent
|
||||||
// calls should be made to this function.
|
// calls should be made to this function.
|
||||||
@@ -980,8 +1177,10 @@ type container struct {
|
|||||||
cpus uint64
|
cpus uint64
|
||||||
fsSize uint64
|
fsSize uint64
|
||||||
tmpFsSize uint64
|
tmpFsSize uint64
|
||||||
|
iofs string
|
||||||
timeout time.Duration // cold only (superfluous, but in case)
|
timeout time.Duration // cold only (superfluous, but in case)
|
||||||
logCfg drivers.LoggerConfig
|
logCfg drivers.LoggerConfig
|
||||||
|
close func()
|
||||||
|
|
||||||
stdin io.Reader
|
stdin io.Reader
|
||||||
stdout io.Writer
|
stdout io.Writer
|
||||||
@@ -993,7 +1192,7 @@ type container struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//newHotContainer creates a container that can be used for multiple sequential events
|
//newHotContainer creates a container that can be used for multiple sequential events
|
||||||
func newHotContainer(ctx context.Context, call *call, cfg *Config) (*container, func()) {
|
func newHotContainer(ctx context.Context, call *call, cfg *Config) (*container, error) {
|
||||||
// if freezer is enabled, be consistent with freezer behavior and
|
// if freezer is enabled, be consistent with freezer behavior and
|
||||||
// block stdout and stderr between calls.
|
// block stdout and stderr between calls.
|
||||||
isBlockIdleIO := MaxMsDisabled != cfg.FreezeIdle
|
isBlockIdleIO := MaxMsDisabled != cfg.FreezeIdle
|
||||||
@@ -1038,6 +1237,31 @@ func newHotContainer(ctx context.Context, call *call, cfg *Config) (*container,
|
|||||||
stderr.Swap(newLineWriterWithBuffer(buf2, sec))
|
stderr.Swap(newLineWriterWithBuffer(buf2, sec))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var iofs string
|
||||||
|
var err error
|
||||||
|
closer := func() {} // XXX(reed):
|
||||||
|
if call.Format == models.FormatHTTPStream {
|
||||||
|
// XXX(reed): we should also point stdout to stderr, and not have stdin
|
||||||
|
|
||||||
|
iofs, err = createIOFS(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX(reed): futz with this, we have to make sure shit gets cleaned up properly
|
||||||
|
closer = func() {
|
||||||
|
//err := syscall.Unmount(iofs, 0)
|
||||||
|
//if err != nil {
|
||||||
|
//common.Logger(ctx).WithError(err).Error("error unmounting iofs")
|
||||||
|
//}
|
||||||
|
|
||||||
|
err = os.RemoveAll(iofs)
|
||||||
|
if err != nil {
|
||||||
|
common.Logger(ctx).WithError(err).Error("error removing iofs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &container{
|
return &container{
|
||||||
id: id, // XXX we could just let docker generate ids...
|
id: id, // XXX we could just let docker generate ids...
|
||||||
image: call.Image,
|
image: call.Image,
|
||||||
@@ -1047,6 +1271,7 @@ func newHotContainer(ctx context.Context, call *call, cfg *Config) (*container,
|
|||||||
cpus: uint64(call.CPUs),
|
cpus: uint64(call.CPUs),
|
||||||
fsSize: cfg.MaxFsSize,
|
fsSize: cfg.MaxFsSize,
|
||||||
tmpFsSize: uint64(call.TmpFsSize),
|
tmpFsSize: uint64(call.TmpFsSize),
|
||||||
|
iofs: iofs,
|
||||||
logCfg: drivers.LoggerConfig{
|
logCfg: drivers.LoggerConfig{
|
||||||
URL: strings.TrimSpace(call.SyslogURL),
|
URL: strings.TrimSpace(call.SyslogURL),
|
||||||
Tags: []drivers.LoggerTag{
|
Tags: []drivers.LoggerTag{
|
||||||
@@ -1057,14 +1282,16 @@ func newHotContainer(ctx context.Context, call *call, cfg *Config) (*container,
|
|||||||
stdin: stdin,
|
stdin: stdin,
|
||||||
stdout: stdout,
|
stdout: stdout,
|
||||||
stderr: stderr,
|
stderr: stderr,
|
||||||
}, func() {
|
close: func() {
|
||||||
stdin.Close()
|
stdin.Close()
|
||||||
stderr.Close()
|
stderr.Close()
|
||||||
stdout.Close()
|
stdout.Close()
|
||||||
for _, b := range bufs {
|
for _, b := range bufs {
|
||||||
bufPool.Put(b)
|
bufPool.Put(b)
|
||||||
}
|
}
|
||||||
}
|
closer() // XXX(reed): clean up
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *container) swap(stdin io.Reader, stdout, stderr io.Writer, cs *drivers.Stats) func() {
|
func (c *container) swap(stdin io.Reader, stdout, stderr io.Writer, cs *drivers.Stats) func() {
|
||||||
@@ -1094,7 +1321,7 @@ func (c *container) Input() io.Reader { return c.stdin }
|
|||||||
func (c *container) Logger() (io.Writer, io.Writer) { return c.stdout, c.stderr }
|
func (c *container) Logger() (io.Writer, io.Writer) { return c.stdout, c.stderr }
|
||||||
func (c *container) Volumes() [][2]string { return nil }
|
func (c *container) Volumes() [][2]string { return nil }
|
||||||
func (c *container) WorkDir() string { return "" }
|
func (c *container) WorkDir() string { return "" }
|
||||||
func (c *container) Close() {}
|
func (c *container) Close() { c.close() }
|
||||||
func (c *container) Image() string { return c.image }
|
func (c *container) Image() string { return c.image }
|
||||||
func (c *container) Timeout() time.Duration { return c.timeout }
|
func (c *container) Timeout() time.Duration { return c.timeout }
|
||||||
func (c *container) EnvVars() map[string]string { return c.env }
|
func (c *container) EnvVars() map[string]string { return c.env }
|
||||||
@@ -1104,6 +1331,7 @@ func (c *container) FsSize() uint64 { return c.fsSize }
|
|||||||
func (c *container) TmpFsSize() uint64 { return c.tmpFsSize }
|
func (c *container) TmpFsSize() uint64 { return c.tmpFsSize }
|
||||||
func (c *container) Extensions() map[string]string { return c.extensions }
|
func (c *container) Extensions() map[string]string { return c.extensions }
|
||||||
func (c *container) LoggerConfig() drivers.LoggerConfig { return c.logCfg }
|
func (c *container) LoggerConfig() drivers.LoggerConfig { return c.logCfg }
|
||||||
|
func (c *container) UDSPath() string { return c.iofs }
|
||||||
|
|
||||||
// WriteStat publishes each metric in the specified Stats structure as a histogram metric
|
// WriteStat publishes each metric in the specified Stats structure as a histogram metric
|
||||||
func (c *container) WriteStat(ctx context.Context, stat drivers.Stat) {
|
func (c *container) WriteStat(ctx context.Context, stat drivers.Stat) {
|
||||||
|
|||||||
@@ -311,6 +311,9 @@ func buildConfigWithPath(app *models.App, fn *models.Fn, path string) models.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf["FN_FORMAT"] = fn.Format
|
conf["FN_FORMAT"] = fn.Format
|
||||||
|
if fn.Format == models.FormatHTTPStream { // TODO should be always soon...
|
||||||
|
conf["FN_LISTENER"] = "unix:/iofs/lsnr.sock" // XXX(reed): hardcoding this is ok right? it's a contract
|
||||||
|
}
|
||||||
conf["FN_APP_NAME"] = app.Name
|
conf["FN_APP_NAME"] = app.Name
|
||||||
conf["FN_PATH"] = path
|
conf["FN_PATH"] = path
|
||||||
// TODO: might be a good idea to pass in: "FN_BASE_PATH" = fmt.Sprintf("/r/%s", appName) || "/" if using DNS entries per app
|
// TODO: might be a good idea to pass in: "FN_BASE_PATH" = fmt.Sprintf("/r/%s", appName) || "/" if using DNS entries per app
|
||||||
@@ -433,12 +436,15 @@ func (a *agent) GetCall(opts ...CallOpt) (Call, error) {
|
|||||||
|
|
||||||
c.handler = a.da
|
c.handler = a.da
|
||||||
c.ct = a
|
c.ct = a
|
||||||
|
// TODO(reed): is line writer is vulnerable to attack?
|
||||||
c.stderr = setupLogger(c.req.Context(), a.cfg.MaxLogSize, !a.cfg.DisableDebugUserLogs, c.Call)
|
c.stderr = setupLogger(c.req.Context(), a.cfg.MaxLogSize, !a.cfg.DisableDebugUserLogs, c.Call)
|
||||||
if c.w == nil {
|
if c.w == nil {
|
||||||
// send STDOUT to logs if no writer given (async...)
|
// send STDOUT to logs if no writer given (async...)
|
||||||
// TODO we could/should probably make this explicit to GetCall, ala 'WithLogger', but it's dupe code (who cares?)
|
// TODO we could/should probably make this explicit to GetCall, ala 'WithLogger', but it's dupe code (who cares?)
|
||||||
c.w = c.stderr
|
c.w = c.stderr
|
||||||
}
|
}
|
||||||
|
// NOTE: we need to limit the output size(?) since users may not use fdk we can't limit it there
|
||||||
|
// c.w = common.NewClampWriter(c.w, a.cfg.MaxResponseSize, models.ErrFunctionResponseTooBig)
|
||||||
|
|
||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ type Config struct {
|
|||||||
DisableReadOnlyRootFs bool `json:"disable_readonly_rootfs"`
|
DisableReadOnlyRootFs bool `json:"disable_readonly_rootfs"`
|
||||||
DisableTini bool `json:"disable_tini"`
|
DisableTini bool `json:"disable_tini"`
|
||||||
DisableDebugUserLogs bool `json:"disable_debug_user_logs"`
|
DisableDebugUserLogs bool `json:"disable_debug_user_logs"`
|
||||||
|
IOFSPath string `json:"iofs_path"`
|
||||||
|
IOFSOpts string `json:"iofs_opts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -83,6 +85,10 @@ const (
|
|||||||
EnvDisableTini = "FN_DISABLE_TINI"
|
EnvDisableTini = "FN_DISABLE_TINI"
|
||||||
// EnvDisableDebugUserLogs disables user function logs being logged at level debug. wise to enable for production.
|
// EnvDisableDebugUserLogs disables user function logs being logged at level debug. wise to enable for production.
|
||||||
EnvDisableDebugUserLogs = "FN_DISABLE_DEBUG_USER_LOGS"
|
EnvDisableDebugUserLogs = "FN_DISABLE_DEBUG_USER_LOGS"
|
||||||
|
// EnvIOFSPath is the path of a directory to configure for unix socket files for each container
|
||||||
|
EnvIOFSPath = "FN_IOFS_PATH"
|
||||||
|
// EnvIOFSOpts are the options to set when mounting the iofs directory for unix socket files
|
||||||
|
EnvIOFSOpts = "FN_IOFS_OPTS"
|
||||||
|
|
||||||
// MaxMsDisabled is used to determine whether mr freeze is lying in wait. TODO remove this manuever
|
// MaxMsDisabled is used to determine whether mr freeze is lying in wait. TODO remove this manuever
|
||||||
MaxMsDisabled = time.Duration(math.MaxInt64)
|
MaxMsDisabled = time.Duration(math.MaxInt64)
|
||||||
@@ -123,6 +129,8 @@ func NewConfig() (*Config, error) {
|
|||||||
err = setEnvStr(err, EnvDockerNetworks, &cfg.DockerNetworks)
|
err = setEnvStr(err, EnvDockerNetworks, &cfg.DockerNetworks)
|
||||||
err = setEnvStr(err, EnvDockerLoadFile, &cfg.DockerLoadFile)
|
err = setEnvStr(err, EnvDockerLoadFile, &cfg.DockerLoadFile)
|
||||||
err = setEnvUint(err, EnvMaxTmpFsInodes, &cfg.MaxTmpFsInodes)
|
err = setEnvUint(err, EnvMaxTmpFsInodes, &cfg.MaxTmpFsInodes)
|
||||||
|
err = setEnvStr(err, EnvIOFSPath, &cfg.IOFSPath)
|
||||||
|
err = setEnvStr(err, EnvIOFSOpts, &cfg.IOFSOpts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type cookie struct {
|
|||||||
poolId string
|
poolId string
|
||||||
// network name from docker networks if applicable
|
// network name from docker networks if applicable
|
||||||
netId string
|
netId string
|
||||||
|
|
||||||
// docker container create options created by Driver.CreateCookie, required for Driver.Prepare()
|
// docker container create options created by Driver.CreateCookie, required for Driver.Prepare()
|
||||||
opts docker.CreateContainerOptions
|
opts docker.CreateContainerOptions
|
||||||
// task associated with this cookie
|
// task associated with this cookie
|
||||||
@@ -104,6 +105,17 @@ func (c *cookie) configureTmpFs(log logrus.FieldLogger) {
|
|||||||
c.opts.HostConfig.Tmpfs["/tmp"] = tmpFsOption
|
c.opts.HostConfig.Tmpfs["/tmp"] = tmpFsOption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cookie) configureIOFs(log logrus.FieldLogger) {
|
||||||
|
path := c.task.UDSPath()
|
||||||
|
if path == "" {
|
||||||
|
// TODO this should be required soon-ish
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bind := fmt.Sprintf("%s:/iofs", path)
|
||||||
|
c.opts.HostConfig.Binds = append(c.opts.HostConfig.Binds, bind)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *cookie) configureVolumes(log logrus.FieldLogger) {
|
func (c *cookie) configureVolumes(log logrus.FieldLogger) {
|
||||||
if len(c.task.Volumes()) == 0 {
|
if len(c.task.Volumes()) == 0 {
|
||||||
return
|
return
|
||||||
@@ -176,12 +188,13 @@ func (c *cookie) configureEnv(log logrus.FieldLogger) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
envvars := make([]string, 0, len(c.task.EnvVars()))
|
if c.opts.Config.Env == nil {
|
||||||
for name, val := range c.task.EnvVars() {
|
c.opts.Config.Env = make([]string, 0, len(c.task.EnvVars()))
|
||||||
envvars = append(envvars, name+"="+val)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.opts.Config.Env = envvars
|
for name, val := range c.task.EnvVars() {
|
||||||
|
c.opts.Config.Env = append(c.opts.Config.Env, name+"="+val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// implements Cookie
|
// implements Cookie
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ func (drv *DockerDriver) CreateCookie(ctx context.Context, task drivers.Containe
|
|||||||
cookie.configureTmpFs(log)
|
cookie.configureTmpFs(log)
|
||||||
cookie.configureVolumes(log)
|
cookie.configureVolumes(log)
|
||||||
cookie.configureWorkDir(log)
|
cookie.configureWorkDir(log)
|
||||||
|
cookie.configureIOFs(log)
|
||||||
|
|
||||||
// Order is important, if pool is enabled, it overrides pick network
|
// Order is important, if pool is enabled, it overrides pick network
|
||||||
drv.pickPool(ctx, cookie)
|
drv.pickPool(ctx, cookie)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ func (c *poolTask) TmpFsSize() uint64 { return 0
|
|||||||
func (c *poolTask) Extensions() map[string]string { return nil }
|
func (c *poolTask) Extensions() map[string]string { return nil }
|
||||||
func (c *poolTask) LoggerConfig() drivers.LoggerConfig { return drivers.LoggerConfig{} }
|
func (c *poolTask) LoggerConfig() drivers.LoggerConfig { return drivers.LoggerConfig{} }
|
||||||
func (c *poolTask) WriteStat(ctx context.Context, stat drivers.Stat) {}
|
func (c *poolTask) WriteStat(ctx context.Context, stat drivers.Stat) {}
|
||||||
|
func (c *poolTask) UDSPath() string { return "" }
|
||||||
|
|
||||||
type dockerPoolItem struct {
|
type dockerPoolItem struct {
|
||||||
id string
|
id string
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func (f *taskDockerTest) Close() {}
|
|||||||
func (f *taskDockerTest) Input() io.Reader { return f.input }
|
func (f *taskDockerTest) Input() io.Reader { return f.input }
|
||||||
func (f *taskDockerTest) Extensions() map[string]string { return nil }
|
func (f *taskDockerTest) Extensions() map[string]string { return nil }
|
||||||
func (f *taskDockerTest) LoggerConfig() drivers.LoggerConfig { return drivers.LoggerConfig{} }
|
func (f *taskDockerTest) LoggerConfig() drivers.LoggerConfig { return drivers.LoggerConfig{} }
|
||||||
|
func (f *taskDockerTest) UDSPath() string { return "" }
|
||||||
|
|
||||||
func TestRunnerDocker(t *testing.T) {
|
func TestRunnerDocker(t *testing.T) {
|
||||||
dkr := NewDocker(drivers.Config{})
|
dkr := NewDocker(drivers.Config{})
|
||||||
|
|||||||
@@ -158,8 +158,13 @@ type ContainerTask interface {
|
|||||||
// Close should be safe to call multiple times.
|
// Close should be safe to call multiple times.
|
||||||
Close()
|
Close()
|
||||||
|
|
||||||
// Extra Configuration Options
|
// Extensions are extra driver specific configuration options. They should be
|
||||||
|
// more specific but it's easier to be lazy.
|
||||||
Extensions() map[string]string
|
Extensions() map[string]string
|
||||||
|
|
||||||
|
// UDSPath to use to configure the unix domain socket. the drivers
|
||||||
|
// abstractions have leaked so bad at this point it's a monsoon.
|
||||||
|
UDSPath() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat is a bucket of stats from a driver at a point in time for a certain task.
|
// Stat is a bucket of stats from a driver at a point in time for a certain task.
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ type Protocol string
|
|||||||
const (
|
const (
|
||||||
Default Protocol = models.FormatDefault
|
Default Protocol = models.FormatDefault
|
||||||
HTTP Protocol = models.FormatHTTP
|
HTTP Protocol = models.FormatHTTP
|
||||||
|
HTTPStream Protocol = models.FormatHTTPStream
|
||||||
JSON Protocol = models.FormatJSON
|
JSON Protocol = models.FormatJSON
|
||||||
CloudEventP Protocol = models.FormatCloudEvent
|
CloudEventP Protocol = models.FormatCloudEvent
|
||||||
Empty Protocol = ""
|
Empty Protocol = ""
|
||||||
@@ -160,7 +161,7 @@ func (p Protocol) MarshalJSON() ([]byte, error) {
|
|||||||
// stdin/stdout.
|
// stdin/stdout.
|
||||||
func New(p Protocol, in io.Writer, out io.Reader) ContainerIO {
|
func New(p Protocol, in io.Writer, out io.Reader) ContainerIO {
|
||||||
switch p {
|
switch p {
|
||||||
case HTTP:
|
case HTTP, HTTPStream:
|
||||||
return &HTTPProtocol{in, out}
|
return &HTTPProtocol{in, out}
|
||||||
case JSON:
|
case JSON:
|
||||||
return &JSONProtocol{in, out}
|
return &JSONProtocol{in, out}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const (
|
|||||||
FormatDefault = "default"
|
FormatDefault = "default"
|
||||||
// FormatHTTP ...
|
// FormatHTTP ...
|
||||||
FormatHTTP = "http"
|
FormatHTTP = "http"
|
||||||
|
// FormatHTTPStream
|
||||||
|
FormatHTTPStream = "http-stream"
|
||||||
// FormatJSON ...
|
// FormatJSON ...
|
||||||
FormatJSON = "json"
|
FormatJSON = "json"
|
||||||
// FormatCloudEvent ...
|
// FormatCloudEvent ...
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ func (f *Fn) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch f.Format {
|
switch f.Format {
|
||||||
case FormatDefault, FormatHTTP, FormatJSON, FormatCloudEvent:
|
case FormatDefault, FormatHTTP, FormatHTTPStream, FormatJSON, FormatCloudEvent:
|
||||||
default:
|
default:
|
||||||
return ErrFnsInvalidFormat
|
return ErrFnsInvalidFormat
|
||||||
}
|
}
|
||||||
|
|||||||
100
docs/developers/fn-format.md
Normal file
100
docs/developers/fn-format.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Fn container contract
|
||||||
|
|
||||||
|
NOTE: THIS IS WORK IN PROGRESS AND ITS API IS SUBJECT TO CHANGE
|
||||||
|
|
||||||
|
This document will describe the details of how a function works, inputs/outputs, etc.
|
||||||
|
(It is meant to replace (./function-format.md) - if the time has come, remove this line,
|
||||||
|
for now it defines the 'http-stream' format option)
|
||||||
|
|
||||||
|
The basic idea is to handle http requests over a unix domain socket. Each
|
||||||
|
container will only receive one request at a time, in that until a response is
|
||||||
|
returned from a previous request, no new requests will be issued to a
|
||||||
|
container's http server.
|
||||||
|
|
||||||
|
### XXX(reed): motivation section for future us posterity?
|
||||||
|
|
||||||
|
### FDK Contract outline
|
||||||
|
|
||||||
|
Function Development Kits (FDKs) are libraries for various languages that implement Fn's container contract for function input, output and configuration. In order to be a fully 'FDK compliant', below are the rules:
|
||||||
|
|
||||||
|
If `FN_FORMAT` variable is http-stream, then FDKs __MUST__ parse `FN_LISTENER` environment variable.
|
||||||
|
|
||||||
|
`FN_LISTENER` must contain the listener address for FDKs to bind/listen to. Currently we only support unix-domain-socket (UDS) with `SOCK_STREAM` (TCP). This means `FN_LISTENER` prefix is always `unix:/`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
FN_LISTENER=unix:/var/run/fn/listener/listen.sock
|
||||||
|
FN_FORMAT=http-stream
|
||||||
|
```
|
||||||
|
|
||||||
|
If `FN_FORMAT` is `http-stream`, then absence of `FN_LISTENER` or "unix:" prefix in `FN_LISTENER` is an error and FDKs are REQUIRED to terminate/exit with error.
|
||||||
|
|
||||||
|
Before exiting, FDKs __SHOULD__ remove the UDS file (from `FN_LISTENER` path).
|
||||||
|
|
||||||
|
FDKs upon creation of UDS file on disk with bind system call __SHOULD__ be ready to receive and handle traffic. Upon bind call, the UDS file __MUST__ be writable by fn-agent.
|
||||||
|
|
||||||
|
Path in `FN_LISTENER` (after "unix:" prefix) cannot be larger than 107 bytes.
|
||||||
|
|
||||||
|
FDKs __MUST__ listen on the unix socket within 5 seconds of startup, Fn will enforce time limits and will terminate such FDK containers.
|
||||||
|
|
||||||
|
Once initialised the FDK should respond to HTTP requests by accepting connections on the created unix socket.
|
||||||
|
|
||||||
|
The FDK __SHOULD NOT__ enforce any read or write timeouts on incoming requests or responses
|
||||||
|
|
||||||
|
The FDK __SHOULD__ support HTTP/1.1 Keep alive behaviour
|
||||||
|
|
||||||
|
The agent __MUST__ maintain no more than one concurrent HTTP connection to the FDK HTTP servers
|
||||||
|
|
||||||
|
Containers __MUST__ implement HTTP/1.1
|
||||||
|
|
||||||
|
Any data sent to Stdout and Stderr will be logged by Fn and sent to any configured logging facility
|
||||||
|
|
||||||
|
Each function container is responsible for handling multiple function
|
||||||
|
invocations against it, one at a time, for as long as the container lives.
|
||||||
|
|
||||||
|
Fn will make HTTP requests to the container on the `/call` URL of the containers HTTP UDS port using.
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /call HTTP/1.1
|
||||||
|
Host: localhost:8080
|
||||||
|
Fn-Call-Id : <call_id>
|
||||||
|
Fn-Deadline: <date/time>
|
||||||
|
Content-type: application/cloudevent+json
|
||||||
|
|
||||||
|
<Body here>
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Fn-Http-Status: 204
|
||||||
|
Fn-Http-H-My-Header: foo
|
||||||
|
Content-type: text/plain
|
||||||
|
|
||||||
|
<Body here>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
The below are the environment variables that a function can expect to use.
|
||||||
|
FDKs __SHOULD__ provide a facility to easily access these without having to
|
||||||
|
use an OS library.
|
||||||
|
|
||||||
|
* `FN_ID` - fn id
|
||||||
|
* `FN_APP_ID` - app id of the fn
|
||||||
|
* `FN_NAME` - name of the fn
|
||||||
|
* `FN_APP_NAME` - the name of the application of the fn
|
||||||
|
* `FN_FORMAT` - `http-stream` (DEPRECATED)
|
||||||
|
* `FN_LISTENER` - the path where a unix socket file should accept
|
||||||
|
* `FN_MEMORY` - a number representing the amount of memory available to the call, in MB
|
||||||
|
* `FN_TMPSIZE` - a number representing the amount of writable disk space available under /tmp for the call, in MB
|
||||||
|
|
||||||
|
In addition to these, all config variables set on `app.config` or `fn.config` will be populated into the environment exactly as configured, for example if `app.config = { "HAMMER": "TIME" }` your environment will be populated with `HAMMER=TIME`.
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
|
||||||
|
* `Fn-Call-Id` - id for the call
|
||||||
|
* `Fn-Deadline` - RFC3339 timestamp indicating the deadline for a function call
|
||||||
|
* `Fn-*` - reserved for future usage
|
||||||
|
* `*` - any other headers, potentially rooted from an http trigger
|
||||||
|
|
||||||
5
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
5
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
11
vendor/github.com/fsnotify/fsnotify/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
11
vendor/github.com/fsnotify/fsnotify/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Before reporting an issue, please ensure you are using the latest release of fsnotify.
|
||||||
|
|
||||||
|
### Which operating system (GOOS) and version are you using?
|
||||||
|
|
||||||
|
Linux: lsb_release -a
|
||||||
|
macOS: sw_vers
|
||||||
|
Windows: systeminfo | findstr /B /C:OS
|
||||||
|
|
||||||
|
### Please describe the issue that occurred.
|
||||||
|
|
||||||
|
### Are you able to reproduce the issue? Please provide steps to reproduce and a code sample if possible.
|
||||||
8
vendor/github.com/fsnotify/fsnotify/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
8
vendor/github.com/fsnotify/fsnotify/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#### What does this pull request do?
|
||||||
|
|
||||||
|
|
||||||
|
#### Where should the reviewer start?
|
||||||
|
|
||||||
|
|
||||||
|
#### How should this be manually tested?
|
||||||
|
|
||||||
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Setup a Global .gitignore for OS and editor generated files:
|
||||||
|
# https://help.github.com/articles/ignoring-files
|
||||||
|
# git config --global core.excludesfile ~/.gitignore_global
|
||||||
|
|
||||||
|
.vagrant
|
||||||
|
*.sublime-project
|
||||||
30
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
Normal file
30
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
fast_finish: true
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- go get -u github.com/golang/lint/golint
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v --race ./...
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
||||||
|
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||||
|
- go vet ./...
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
52
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
52
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
# You can update this list using the following command:
|
||||||
|
#
|
||||||
|
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Aaron L <aaron@bettercoder.net>
|
||||||
|
Adrien Bustany <adrien@bustany.org>
|
||||||
|
Amit Krishnan <amit.krishnan@oracle.com>
|
||||||
|
Anmol Sethi <me@anmol.io>
|
||||||
|
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||||
|
Bruno Bigras <bigras.bruno@gmail.com>
|
||||||
|
Caleb Spare <cespare@gmail.com>
|
||||||
|
Case Nelson <case@teammating.com>
|
||||||
|
Chris Howey <chris@howey.me> <howeyc@gmail.com>
|
||||||
|
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||||
|
Daniel Wagner-Hall <dawagner@gmail.com>
|
||||||
|
Dave Cheney <dave@cheney.net>
|
||||||
|
Evan Phoenix <evan@fallingsnow.net>
|
||||||
|
Francisco Souza <f@souza.cc>
|
||||||
|
Hari haran <hariharan.uno@gmail.com>
|
||||||
|
John C Barstow
|
||||||
|
Kelvin Fo <vmirage@gmail.com>
|
||||||
|
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
||||||
|
Matt Layher <mdlayher@gmail.com>
|
||||||
|
Nathan Youngman <git@nathany.com>
|
||||||
|
Nickolai Zeldovich <nickolai@csail.mit.edu>
|
||||||
|
Patrick <patrick@dropbox.com>
|
||||||
|
Paul Hammond <paul@paulhammond.org>
|
||||||
|
Pawel Knap <pawelknap88@gmail.com>
|
||||||
|
Pieter Droogendijk <pieter@binky.org.uk>
|
||||||
|
Pursuit92 <JoshChase@techpursuit.net>
|
||||||
|
Riku Voipio <riku.voipio@linaro.org>
|
||||||
|
Rob Figueiredo <robfig@gmail.com>
|
||||||
|
Rodrigo Chiossi <rodrigochiossi@gmail.com>
|
||||||
|
Slawek Ligus <root@ooz.ie>
|
||||||
|
Soge Zhang <zhssoge@gmail.com>
|
||||||
|
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
||||||
|
Tilak Sharma <tilaks@google.com>
|
||||||
|
Tom Payne <twpayne@gmail.com>
|
||||||
|
Travis Cline <travis.cline@gmail.com>
|
||||||
|
Tudor Golubenco <tudor.g@gmail.com>
|
||||||
|
Vahe Khachikyan <vahe@live.ca>
|
||||||
|
Yukang <moorekang@gmail.com>
|
||||||
|
bronze1man <bronze1man@gmail.com>
|
||||||
|
debrando <denis.brandolini@gmail.com>
|
||||||
|
henrikedwards <henrik.edwards@gmail.com>
|
||||||
|
铁哥 <guotie.9@gmail.com>
|
||||||
317
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
317
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.4.7 / 2018-01-09
|
||||||
|
|
||||||
|
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||||
|
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||||
|
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||||
|
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||||
|
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||||
|
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||||
|
* Docs: replace references to OS X with macOS
|
||||||
|
|
||||||
|
## v1.4.2 / 2016-10-10
|
||||||
|
|
||||||
|
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## v1.4.1 / 2016-10-04
|
||||||
|
|
||||||
|
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## v1.4.0 / 2016-10-01
|
||||||
|
|
||||||
|
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||||
|
|
||||||
|
## v1.3.1 / 2016-06-28
|
||||||
|
|
||||||
|
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||||
|
|
||||||
|
## v1.3.0 / 2016-04-19
|
||||||
|
|
||||||
|
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||||
|
|
||||||
|
## v1.2.10 / 2016-03-02
|
||||||
|
|
||||||
|
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||||
|
|
||||||
|
## v1.2.9 / 2016-01-13
|
||||||
|
|
||||||
|
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||||
|
|
||||||
|
## v1.2.8 / 2015-12-17
|
||||||
|
|
||||||
|
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||||
|
* inotify: fix race in test
|
||||||
|
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||||
|
|
||||||
|
## v1.2.5 / 2015-10-17
|
||||||
|
|
||||||
|
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||||
|
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||||
|
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||||
|
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||||
|
|
||||||
|
## v1.2.1 / 2015-10-14
|
||||||
|
|
||||||
|
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||||
|
|
||||||
|
## v1.2.0 / 2015-02-08
|
||||||
|
|
||||||
|
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||||
|
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||||
|
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||||
|
|
||||||
|
## v1.1.1 / 2015-02-05
|
||||||
|
|
||||||
|
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||||
|
|
||||||
|
## v1.1.0 / 2014-12-12
|
||||||
|
|
||||||
|
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||||
|
* add low-level functions
|
||||||
|
* only need to store flags on directories
|
||||||
|
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||||
|
* done can be an unbuffered channel
|
||||||
|
* remove calls to os.NewSyscallError
|
||||||
|
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||||
|
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## v1.0.4 / 2014-09-07
|
||||||
|
|
||||||
|
* kqueue: add dragonfly to the build tags.
|
||||||
|
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||||
|
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||||
|
|
||||||
|
## v1.0.3 / 2014-08-19
|
||||||
|
|
||||||
|
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||||
|
|
||||||
|
## v1.0.2 / 2014-08-17
|
||||||
|
|
||||||
|
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||||
|
|
||||||
|
## v1.0.0 / 2014-08-15
|
||||||
|
|
||||||
|
* [API] Remove AddWatch on Windows, use Add.
|
||||||
|
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||||
|
* Minor updates based on feedback from golint.
|
||||||
|
|
||||||
|
## dev / 2014-07-09
|
||||||
|
|
||||||
|
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||||
|
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||||
|
|
||||||
|
## dev / 2014-07-04
|
||||||
|
|
||||||
|
* kqueue: fix incorrect mutex used in Close()
|
||||||
|
* Update example to demonstrate usage of Op.
|
||||||
|
|
||||||
|
## dev / 2014-06-28
|
||||||
|
|
||||||
|
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||||
|
* Fix for String() method on Event (thanks Alex Brainman)
|
||||||
|
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||||
|
|
||||||
|
## dev / 2014-06-21
|
||||||
|
|
||||||
|
* Events channel of type Event rather than *Event.
|
||||||
|
* [internal] use syscall constants directly for inotify and kqueue.
|
||||||
|
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||||
|
|
||||||
|
## dev / 2014-06-19
|
||||||
|
|
||||||
|
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||||
|
* [internal] remove cookie from Event struct (unused).
|
||||||
|
* [internal] Event struct has the same definition across every OS.
|
||||||
|
* [internal] remove internal watch and removeWatch methods.
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||||
|
* [API] Pluralized channel names: Events and Errors.
|
||||||
|
* [API] Renamed FileEvent struct to Event.
|
||||||
|
* [API] Op constants replace methods like IsCreate().
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## dev / 2014-05-23
|
||||||
|
|
||||||
|
* [API] Remove current implementation of WatchFlags.
|
||||||
|
* current implementation doesn't take advantage of OS for efficiency
|
||||||
|
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||||
|
* no tests for the current implementation
|
||||||
|
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||||
|
|
||||||
|
## v0.9.3 / 2014-12-31
|
||||||
|
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## v0.9.2 / 2014-08-17
|
||||||
|
|
||||||
|
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
||||||
|
## v0.9.1 / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## v0.9.0 / 2014-01-17
|
||||||
|
|
||||||
|
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||||
|
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||||
|
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||||
|
|
||||||
|
## v0.8.12 / 2013-11-13
|
||||||
|
|
||||||
|
* [API] Remove FD_SET and friends from Linux adapter
|
||||||
|
|
||||||
|
## v0.8.11 / 2013-11-02
|
||||||
|
|
||||||
|
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||||
|
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||||
|
|
||||||
|
## v0.8.10 / 2013-10-19
|
||||||
|
|
||||||
|
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||||
|
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||||
|
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||||
|
|
||||||
|
## v0.8.9 / 2013-09-08
|
||||||
|
|
||||||
|
* [Doc] Contributing (thanks @nathany)
|
||||||
|
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||||
|
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||||
|
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||||
|
|
||||||
|
## v0.8.8 / 2013-06-17
|
||||||
|
|
||||||
|
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||||
|
|
||||||
|
## v0.8.7 / 2013-06-03
|
||||||
|
|
||||||
|
* [API] Make syscall flags internal
|
||||||
|
* [Fix] inotify: ignore event changes
|
||||||
|
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||||
|
* [Fix] tests on Windows
|
||||||
|
* lower case error messages
|
||||||
|
|
||||||
|
## v0.8.6 / 2013-05-23
|
||||||
|
|
||||||
|
* kqueue: Use EVT_ONLY flag on Darwin
|
||||||
|
* [Doc] Update README with full example
|
||||||
|
|
||||||
|
## v0.8.5 / 2013-05-09
|
||||||
|
|
||||||
|
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||||
|
|
||||||
|
## v0.8.4 / 2013-04-07
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||||
|
|
||||||
|
## v0.8.3 / 2013-03-13
|
||||||
|
|
||||||
|
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||||
|
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||||
|
|
||||||
|
## v0.8.2 / 2013-02-07
|
||||||
|
|
||||||
|
* [Doc] add Authors
|
||||||
|
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||||
|
|
||||||
|
## v0.8.1 / 2013-01-09
|
||||||
|
|
||||||
|
* [Fix] Windows path separators
|
||||||
|
* [Doc] BSD License
|
||||||
|
|
||||||
|
## v0.8.0 / 2012-11-09
|
||||||
|
|
||||||
|
* kqueue: directory watching improvements (thanks @vmirage)
|
||||||
|
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||||
|
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||||
|
|
||||||
|
## v0.7.4 / 2012-10-09
|
||||||
|
|
||||||
|
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||||
|
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||||
|
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||||
|
* [Fix] kqueue: modify after recreation of file
|
||||||
|
|
||||||
|
## v0.7.3 / 2012-09-27
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||||
|
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||||
|
|
||||||
|
## v0.7.2 / 2012-09-01
|
||||||
|
|
||||||
|
* kqueue: events for created directories
|
||||||
|
|
||||||
|
## v0.7.1 / 2012-07-14
|
||||||
|
|
||||||
|
* [Fix] for renaming files
|
||||||
|
|
||||||
|
## v0.7.0 / 2012-07-02
|
||||||
|
|
||||||
|
* [Feature] FSNotify flags
|
||||||
|
* [Fix] inotify: Added file name back to event path
|
||||||
|
|
||||||
|
## v0.6.0 / 2012-06-06
|
||||||
|
|
||||||
|
* kqueue: watch files after directory created (thanks @tmc)
|
||||||
|
|
||||||
|
## v0.5.1 / 2012-05-22
|
||||||
|
|
||||||
|
* [Fix] inotify: remove all watches before Close()
|
||||||
|
|
||||||
|
## v0.5.0 / 2012-05-03
|
||||||
|
|
||||||
|
* [API] kqueue: return errors during watch instead of sending over channel
|
||||||
|
* kqueue: match symlink behavior on Linux
|
||||||
|
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||||
|
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||||
|
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||||
|
|
||||||
|
## v0.4.0 / 2012-03-30
|
||||||
|
|
||||||
|
* Go 1 released: build with go tool
|
||||||
|
* [Feature] Windows support using winfsnotify
|
||||||
|
* Windows does not have attribute change notifications
|
||||||
|
* Roll attribute notifications into IsModify
|
||||||
|
|
||||||
|
## v0.3.0 / 2012-02-19
|
||||||
|
|
||||||
|
* kqueue: add files when watch directory
|
||||||
|
|
||||||
|
## v0.2.0 / 2011-12-30
|
||||||
|
|
||||||
|
* update to latest Go weekly code
|
||||||
|
|
||||||
|
## v0.1.0 / 2011-10-19
|
||||||
|
|
||||||
|
* kqueue: add watch on file creation to match inotify
|
||||||
|
* kqueue: create file event
|
||||||
|
* inotify: ignore `IN_IGNORED` events
|
||||||
|
* event String()
|
||||||
|
* linux: common FileEvent functions
|
||||||
|
* initial commit
|
||||||
|
|
||||||
|
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||||
|
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||||
|
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||||
|
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||||
|
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||||
|
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||||
|
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||||
|
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||||
|
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||||
|
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||||
|
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||||
|
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||||
|
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||||
|
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||||
|
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||||
|
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
||||||
77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
|
||||||
|
* Please indicate the platform you are using fsnotify on.
|
||||||
|
* A code example to reproduce the problem is appreciated.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
### Contributor License Agreement
|
||||||
|
|
||||||
|
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
|
||||||
|
|
||||||
|
Please indicate that you have signed the CLA in your pull request.
|
||||||
|
|
||||||
|
### How fsnotify is Developed
|
||||||
|
|
||||||
|
* Development is done on feature branches.
|
||||||
|
* Tests are run on BSD, Linux, macOS and Windows.
|
||||||
|
* Pull requests are reviewed and [applied to master][am] using [hub][].
|
||||||
|
* Maintainers may modify or squash commits rather than asking contributors to.
|
||||||
|
* To issue a new release, the maintainers will:
|
||||||
|
* Update the CHANGELOG
|
||||||
|
* Tag a version, which will become available through gopkg.in.
|
||||||
|
|
||||||
|
### How to Fork
|
||||||
|
|
||||||
|
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
|
||||||
|
|
||||||
|
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Ensure everything works and the tests pass (see below)
|
||||||
|
4. Commit your changes (`git commit -am 'Add some feature'`)
|
||||||
|
|
||||||
|
Contribute upstream:
|
||||||
|
|
||||||
|
1. Fork fsnotify on GitHub
|
||||||
|
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
|
||||||
|
3. Push to the branch (`git push fork my-new-feature`)
|
||||||
|
4. Create a new Pull Request on GitHub
|
||||||
|
|
||||||
|
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
|
||||||
|
|
||||||
|
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
|
||||||
|
|
||||||
|
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
|
||||||
|
|
||||||
|
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
|
||||||
|
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
|
||||||
|
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
|
||||||
|
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
|
||||||
|
* When you're done, you will want to halt or destroy the Vagrant boxes.
|
||||||
|
|
||||||
|
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
|
||||||
|
|
||||||
|
Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
|
||||||
|
|
||||||
|
### Maintainers
|
||||||
|
|
||||||
|
Help maintaining fsnotify is welcome. To be a maintainer:
|
||||||
|
|
||||||
|
* Submit a pull request and sign the CLA as above.
|
||||||
|
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
|
||||||
|
|
||||||
|
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
|
||||||
|
|
||||||
|
All code changes should be internal pull requests.
|
||||||
|
|
||||||
|
Releases are tagged using [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
[hub]: https://github.com/github/hub
|
||||||
|
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
|
||||||
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2012 fsnotify Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
79
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
79
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# File system notifications for Go
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/fsnotify/fsnotify) [](https://goreportcard.com/report/github.com/fsnotify/fsnotify)
|
||||||
|
|
||||||
|
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
|
||||||
|
|
||||||
|
```console
|
||||||
|
go get -u golang.org/x/sys/...
|
||||||
|
```
|
||||||
|
|
||||||
|
Cross platform: Windows, Linux, BSD and macOS.
|
||||||
|
|
||||||
|
|Adapter |OS |Status |
|
||||||
|
|----------|----------|----------|
|
||||||
|
|inotify |Linux 2.6.27 or later, Android\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|
||||||
|
|kqueue |BSD, macOS, iOS\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|
||||||
|
|ReadDirectoryChangesW|Windows|Supported [](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|
||||||
|
|FSEvents |macOS |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
|
||||||
|
|FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
|
||||||
|
|fanotify |Linux 2.6.37+ | |
|
||||||
|
|USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
|
||||||
|
|Polling |*All* |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
|
||||||
|
|
||||||
|
\* Android and iOS are untested.
|
||||||
|
|
||||||
|
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
|
||||||
|
|
||||||
|
## API stability
|
||||||
|
|
||||||
|
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
|
||||||
|
|
||||||
|
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
|
||||||
|
|
||||||
|
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**When a file is moved to another directory is it still being watched?**
|
||||||
|
|
||||||
|
No (it shouldn't be, unless you are watching where it was moved to).
|
||||||
|
|
||||||
|
**When I watch a directory, are all subdirectories watched as well?**
|
||||||
|
|
||||||
|
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
|
||||||
|
|
||||||
|
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
||||||
|
|
||||||
|
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
|
||||||
|
|
||||||
|
**Why am I receiving multiple events for the same file on OS X?**
|
||||||
|
|
||||||
|
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
|
||||||
|
|
||||||
|
**How many files can be watched at once?**
|
||||||
|
|
||||||
|
There are OS-specific limits as to how many watches can be created:
|
||||||
|
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
|
||||||
|
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
||||||
|
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||||
|
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
||||||
|
|
||||||
|
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
* [notify](https://github.com/rjeczalik/notify)
|
||||||
|
* [fsevents](https://github.com/fsnotify/fsevents)
|
||||||
|
|
||||||
42
vendor/github.com/fsnotify/fsnotify/example_test.go
generated
vendored
Normal file
42
vendor/github.com/fsnotify/fsnotify/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !plan9
|
||||||
|
|
||||||
|
package fsnotify_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleNewWatcher() {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-watcher.Events:
|
||||||
|
log.Println("event:", event)
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
log.Println("modified file:", event.Name)
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
log.Println("error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = watcher.Add("/tmp/foo")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
37
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
37
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
66
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
66
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !plan9
|
||||||
|
|
||||||
|
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event represents a single file system notification.
|
||||||
|
type Event struct {
|
||||||
|
Name string // Relative path to the file or directory.
|
||||||
|
Op Op // File operation that triggered the event.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op describes a set of file operations.
|
||||||
|
type Op uint32
|
||||||
|
|
||||||
|
// These are the generalized file operations that can trigger a notification.
|
||||||
|
const (
|
||||||
|
Create Op = 1 << iota
|
||||||
|
Write
|
||||||
|
Remove
|
||||||
|
Rename
|
||||||
|
Chmod
|
||||||
|
)
|
||||||
|
|
||||||
|
func (op Op) String() string {
|
||||||
|
// Use a buffer for efficient string concatenation
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
if op&Create == Create {
|
||||||
|
buffer.WriteString("|CREATE")
|
||||||
|
}
|
||||||
|
if op&Remove == Remove {
|
||||||
|
buffer.WriteString("|REMOVE")
|
||||||
|
}
|
||||||
|
if op&Write == Write {
|
||||||
|
buffer.WriteString("|WRITE")
|
||||||
|
}
|
||||||
|
if op&Rename == Rename {
|
||||||
|
buffer.WriteString("|RENAME")
|
||||||
|
}
|
||||||
|
if op&Chmod == Chmod {
|
||||||
|
buffer.WriteString("|CHMOD")
|
||||||
|
}
|
||||||
|
if buffer.Len() == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return buffer.String()[1:] // Strip leading pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the event in the form
|
||||||
|
// "file: REMOVE|WRITE|..."
|
||||||
|
func (e Event) String() string {
|
||||||
|
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common errors that can be reported by a watcher
|
||||||
|
var ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||||
70
vendor/github.com/fsnotify/fsnotify/fsnotify_test.go
generated
vendored
Normal file
70
vendor/github.com/fsnotify/fsnotify/fsnotify_test.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !plan9
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEventStringWithValue(t *testing.T) {
|
||||||
|
for opMask, expectedString := range map[Op]string{
|
||||||
|
Chmod | Create: `"/usr/someFile": CREATE|CHMOD`,
|
||||||
|
Rename: `"/usr/someFile": RENAME`,
|
||||||
|
Remove: `"/usr/someFile": REMOVE`,
|
||||||
|
Write | Chmod: `"/usr/someFile": WRITE|CHMOD`,
|
||||||
|
} {
|
||||||
|
event := Event{Name: "/usr/someFile", Op: opMask}
|
||||||
|
if event.String() != expectedString {
|
||||||
|
t.Fatalf("Expected %s, got: %v", expectedString, event.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventOpStringWithValue(t *testing.T) {
|
||||||
|
expectedOpString := "WRITE|CHMOD"
|
||||||
|
event := Event{Name: "someFile", Op: Write | Chmod}
|
||||||
|
if event.Op.String() != expectedOpString {
|
||||||
|
t.Fatalf("Expected %s, got: %v", expectedOpString, event.Op.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventOpStringWithNoValue(t *testing.T) {
|
||||||
|
expectedOpString := ""
|
||||||
|
event := Event{Name: "testFile", Op: 0}
|
||||||
|
if event.Op.String() != expectedOpString {
|
||||||
|
t.Fatalf("Expected %s, got: %v", expectedOpString, event.Op.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWatcherClose tests that the goroutine started by creating the watcher can be
|
||||||
|
// signalled to return at any time, even if there is no goroutine listening on the events
|
||||||
|
// or errors channels.
|
||||||
|
func TestWatcherClose(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
name := tempMkFile(t, "")
|
||||||
|
w := newWatcher(t)
|
||||||
|
err := w.Add(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Allow the watcher to receive the event.
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
337
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
337
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
fd int
|
||||||
|
poller *fdPoller
|
||||||
|
watches map[string]*watch // Map of inotify watches (key: path)
|
||||||
|
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
// Create inotify fd
|
||||||
|
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
|
||||||
|
if fd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
// Create epoll
|
||||||
|
poller, err := newFdPoller(fd)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
fd: fd,
|
||||||
|
poller: poller,
|
||||||
|
watches: make(map[string]*watch),
|
||||||
|
paths: make(map[int]string),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
// Wake up goroutine
|
||||||
|
w.poller.wake()
|
||||||
|
|
||||||
|
// Wait for goroutine to close
|
||||||
|
<-w.doneResp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
if w.isClosed() {
|
||||||
|
return errors.New("inotify instance already closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||||
|
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||||
|
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||||
|
|
||||||
|
var flags uint32 = agnosticEvents
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watchEntry := w.watches[name]
|
||||||
|
if watchEntry != nil {
|
||||||
|
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||||
|
}
|
||||||
|
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||||
|
if wd == -1 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
if watchEntry == nil {
|
||||||
|
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||||
|
w.paths[wd] = name
|
||||||
|
} else {
|
||||||
|
watchEntry.wd = uint32(wd)
|
||||||
|
watchEntry.flags = flags
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
// Fetch the watch.
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watch, ok := w.watches[name]
|
||||||
|
|
||||||
|
// Remove it from inotify.
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||||
|
// error, we need to clean up our internal state to ensure it matches
|
||||||
|
// inotify's kernel state.
|
||||||
|
delete(w.paths, int(watch.wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
|
||||||
|
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||||
|
// the inotify will already have been removed.
|
||||||
|
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||||
|
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||||
|
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||||
|
// by another thread and we have not received IN_IGNORE event.
|
||||||
|
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||||
|
if success == -1 {
|
||||||
|
// TODO: Perhaps it's not helpful to return an error here in every case.
|
||||||
|
// the only two possible errors are:
|
||||||
|
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
||||||
|
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
||||||
|
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
||||||
|
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||||
|
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the inotify file descriptor, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
|
n int // Number of bytes read with read()
|
||||||
|
errno error // Syscall errno
|
||||||
|
ok bool // For poller.wait
|
||||||
|
)
|
||||||
|
|
||||||
|
defer close(w.doneResp)
|
||||||
|
defer close(w.Errors)
|
||||||
|
defer close(w.Events)
|
||||||
|
defer unix.Close(w.fd)
|
||||||
|
defer w.poller.close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// See if we have been closed.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, errno = w.poller.wait()
|
||||||
|
if errno != nil {
|
||||||
|
select {
|
||||||
|
case w.Errors <- errno:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n, errno = unix.Read(w.fd, buf[:])
|
||||||
|
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
||||||
|
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
||||||
|
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
||||||
|
if errno == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix.Read might have been woken up by Close. If so, we're done.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofInotifyEvent {
|
||||||
|
var err error
|
||||||
|
if n == 0 {
|
||||||
|
// If EOF is received. This should really never happen.
|
||||||
|
err = io.EOF
|
||||||
|
} else if n < 0 {
|
||||||
|
// If an error occurred while reading.
|
||||||
|
err = errno
|
||||||
|
} else {
|
||||||
|
// Read was too short.
|
||||||
|
err = errors.New("notify: short read in readEvents()")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
// We don't know how many events we just read into the buffer
|
||||||
|
// While the offset points to at least one whole event...
|
||||||
|
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||||
|
|
||||||
|
mask := uint32(raw.Mask)
|
||||||
|
nameLen := uint32(raw.Len)
|
||||||
|
|
||||||
|
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||||
|
select {
|
||||||
|
case w.Errors <- ErrEventOverflow:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event happened to the watched directory or the watched file, the kernel
|
||||||
|
// doesn't append the filename to the event, but we would like to always fill the
|
||||||
|
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||||
|
// the "paths" map.
|
||||||
|
w.mu.Lock()
|
||||||
|
name, ok := w.paths[int(raw.Wd)]
|
||||||
|
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||||
|
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||||
|
// with the inotify kernel state which has already deleted the watch
|
||||||
|
// automatically.
|
||||||
|
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||||
|
delete(w.paths, int(raw.Wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if nameLen > 0 {
|
||||||
|
// Point "bytes" at the first byte of the filename
|
||||||
|
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
|
||||||
|
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||||
|
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := newEvent(name, mask)
|
||||||
|
|
||||||
|
// Send the events that are not ignored on the events channel
|
||||||
|
if !event.ignoreLinux(mask) {
|
||||||
|
select {
|
||||||
|
case w.Events <- event:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
offset += unix.SizeofInotifyEvent + nameLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certain types of events can be "ignored" and not sent over the Events
|
||||||
|
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
||||||
|
// against files that do not exist.
|
||||||
|
func (e *Event) ignoreLinux(mask uint32) bool {
|
||||||
|
// Ignore anything the inotify API says to ignore
|
||||||
|
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event is not a DELETE or RENAME, the file must exist.
|
||||||
|
// Otherwise the event is ignored.
|
||||||
|
// *Note*: this was put in place because it was seen that a MODIFY
|
||||||
|
// event was sent after the DELETE. This ignores that MODIFY and
|
||||||
|
// assumes a DELETE will come or has come if the file doesn't exist.
|
||||||
|
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
|
||||||
|
_, statErr := os.Lstat(e.Name)
|
||||||
|
return os.IsNotExist(statErr)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fdPoller struct {
|
||||||
|
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||||
|
epfd int // Epoll file descriptor
|
||||||
|
pipe [2]int // Pipe for waking up
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyPoller(fd int) *fdPoller {
|
||||||
|
poller := new(fdPoller)
|
||||||
|
poller.fd = fd
|
||||||
|
poller.epfd = -1
|
||||||
|
poller.pipe[0] = -1
|
||||||
|
poller.pipe[1] = -1
|
||||||
|
return poller
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new inotify poller.
|
||||||
|
// This creates an inotify handler, and an epoll handler.
|
||||||
|
func newFdPoller(fd int) (*fdPoller, error) {
|
||||||
|
var errno error
|
||||||
|
poller := emptyPoller(fd)
|
||||||
|
defer func() {
|
||||||
|
if errno != nil {
|
||||||
|
poller.close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
poller.fd = fd
|
||||||
|
|
||||||
|
// Create epoll fd
|
||||||
|
poller.epfd, errno = unix.EpollCreate1(0)
|
||||||
|
if poller.epfd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||||
|
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register inotify fd with epoll
|
||||||
|
event := unix.EpollEvent{
|
||||||
|
Fd: int32(poller.fd),
|
||||||
|
Events: unix.EPOLLIN,
|
||||||
|
}
|
||||||
|
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register pipe fd with epoll
|
||||||
|
event = unix.EpollEvent{
|
||||||
|
Fd: int32(poller.pipe[0]),
|
||||||
|
Events: unix.EPOLLIN,
|
||||||
|
}
|
||||||
|
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return poller, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait using epoll.
|
||||||
|
// Returns true if something is ready to be read,
|
||||||
|
// false if there is not.
|
||||||
|
func (poller *fdPoller) wait() (bool, error) {
|
||||||
|
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
||||||
|
// I don't know whether epoll_wait returns the number of events returned,
|
||||||
|
// or the total number of events ready.
|
||||||
|
// I decided to catch both by making the buffer one larger than the maximum.
|
||||||
|
events := make([]unix.EpollEvent, 7)
|
||||||
|
for {
|
||||||
|
n, errno := unix.EpollWait(poller.epfd, events, -1)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false, errno
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
// If there are no events, try again.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n > 6 {
|
||||||
|
// This should never happen. More events were returned than should be possible.
|
||||||
|
return false, errors.New("epoll_wait returned more events than I know what to do with")
|
||||||
|
}
|
||||||
|
ready := events[:n]
|
||||||
|
epollhup := false
|
||||||
|
epollerr := false
|
||||||
|
epollin := false
|
||||||
|
for _, event := range ready {
|
||||||
|
if event.Fd == int32(poller.fd) {
|
||||||
|
if event.Events&unix.EPOLLHUP != 0 {
|
||||||
|
// This should not happen, but if it does, treat it as a wakeup.
|
||||||
|
epollhup = true
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLERR != 0 {
|
||||||
|
// If an error is waiting on the file descriptor, we should pretend
|
||||||
|
// something is ready to read, and let unix.Read pick up the error.
|
||||||
|
epollerr = true
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLIN != 0 {
|
||||||
|
// There is data to read.
|
||||||
|
epollin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if event.Fd == int32(poller.pipe[0]) {
|
||||||
|
if event.Events&unix.EPOLLHUP != 0 {
|
||||||
|
// Write pipe descriptor was closed, by us. This means we're closing down the
|
||||||
|
// watcher, and we should wake up.
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLERR != 0 {
|
||||||
|
// If an error is waiting on the pipe file descriptor.
|
||||||
|
// This is an absolute mystery, and should never ever happen.
|
||||||
|
return false, errors.New("Error on the pipe descriptor.")
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLIN != 0 {
|
||||||
|
// This is a regular wakeup, so we have to clear the buffer.
|
||||||
|
err := poller.clearWake()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if epollhup || epollerr || epollin {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the write end of the poller.
|
||||||
|
func (poller *fdPoller) wake() error {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
n, errno := unix.Write(poller.pipe[1], buf)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EAGAIN {
|
||||||
|
// Buffer is full, poller will wake.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (poller *fdPoller) clearWake() error {
|
||||||
|
// You have to be woken up a LOT in order to get to 100!
|
||||||
|
buf := make([]byte, 100)
|
||||||
|
n, errno := unix.Read(poller.pipe[0], buf)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EAGAIN {
|
||||||
|
// Buffer is empty, someone else cleared our wake.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all poller file descriptors, but not the one passed to it.
|
||||||
|
func (poller *fdPoller) close() {
|
||||||
|
if poller.pipe[1] != -1 {
|
||||||
|
unix.Close(poller.pipe[1])
|
||||||
|
}
|
||||||
|
if poller.pipe[0] != -1 {
|
||||||
|
unix.Close(poller.pipe[0])
|
||||||
|
}
|
||||||
|
if poller.epfd != -1 {
|
||||||
|
unix.Close(poller.epfd)
|
||||||
|
}
|
||||||
|
}
|
||||||
229
vendor/github.com/fsnotify/fsnotify/inotify_poller_test.go
generated
vendored
Normal file
229
vendor/github.com/fsnotify/fsnotify/inotify_poller_test.go
generated
vendored
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testFd [2]int
|
||||||
|
|
||||||
|
func makeTestFd(t *testing.T) testFd {
|
||||||
|
var tfd testFd
|
||||||
|
errno := unix.Pipe(tfd[:])
|
||||||
|
if errno != nil {
|
||||||
|
t.Fatalf("Failed to create pipe: %v", errno)
|
||||||
|
}
|
||||||
|
return tfd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) fd() int {
|
||||||
|
return tfd[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) closeWrite(t *testing.T) {
|
||||||
|
errno := unix.Close(tfd[1])
|
||||||
|
if errno != nil {
|
||||||
|
t.Fatalf("Failed to close write end of pipe: %v", errno)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) put(t *testing.T) {
|
||||||
|
buf := make([]byte, 10)
|
||||||
|
_, errno := unix.Write(tfd[1], buf)
|
||||||
|
if errno != nil {
|
||||||
|
t.Fatalf("Failed to write to pipe: %v", errno)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) get(t *testing.T) {
|
||||||
|
buf := make([]byte, 10)
|
||||||
|
_, errno := unix.Read(tfd[0], buf)
|
||||||
|
if errno != nil {
|
||||||
|
t.Fatalf("Failed to read from pipe: %v", errno)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) close() {
|
||||||
|
unix.Close(tfd[1])
|
||||||
|
unix.Close(tfd[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePoller(t *testing.T) (testFd, *fdPoller) {
|
||||||
|
tfd := makeTestFd(t)
|
||||||
|
poller, err := newFdPoller(tfd.fd())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create poller: %v", err)
|
||||||
|
}
|
||||||
|
return tfd, poller
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithBadFd(t *testing.T) {
|
||||||
|
_, err := newFdPoller(-1)
|
||||||
|
if err != unix.EBADF {
|
||||||
|
t.Fatalf("Expected EBADF, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithData(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
tfd.put(t)
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected poller to return true")
|
||||||
|
}
|
||||||
|
tfd.get(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithWakeup(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
err := poller.wake()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wake failed: %v", err)
|
||||||
|
}
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expected poller to return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithClose(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
tfd.closeWrite(t)
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected poller to return true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithWakeupAndData(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
tfd.put(t)
|
||||||
|
err := poller.wake()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wake failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// both data and wakeup
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected poller to return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// data is still in the buffer, wakeup is cleared
|
||||||
|
ok, err = poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected poller to return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
tfd.get(t)
|
||||||
|
// data is gone, only wakeup now
|
||||||
|
err = poller.wake()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wake failed: %v", err)
|
||||||
|
}
|
||||||
|
ok, err = poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expected poller to return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerConcurrent(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
oks := make(chan bool)
|
||||||
|
live := make(chan bool)
|
||||||
|
defer close(live)
|
||||||
|
go func() {
|
||||||
|
defer close(oks)
|
||||||
|
for {
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
oks <- ok
|
||||||
|
if !<-live {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Try a write
|
||||||
|
select {
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
case <-oks:
|
||||||
|
t.Fatalf("poller did not wait")
|
||||||
|
}
|
||||||
|
tfd.put(t)
|
||||||
|
if !<-oks {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
tfd.get(t)
|
||||||
|
live <- true
|
||||||
|
|
||||||
|
// Try a wakeup
|
||||||
|
select {
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
case <-oks:
|
||||||
|
t.Fatalf("poller did not wait")
|
||||||
|
}
|
||||||
|
err := poller.wake()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wake failed: %v", err)
|
||||||
|
}
|
||||||
|
if <-oks {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
live <- true
|
||||||
|
|
||||||
|
// Try a close
|
||||||
|
select {
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
case <-oks:
|
||||||
|
t.Fatalf("poller did not wait")
|
||||||
|
}
|
||||||
|
tfd.closeWrite(t)
|
||||||
|
if !<-oks {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
tfd.get(t)
|
||||||
|
}
|
||||||
449
vendor/github.com/fsnotify/fsnotify/inotify_test.go
generated
vendored
Normal file
449
vendor/github.com/fsnotify/fsnotify/inotify_test.go
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInotifyCloseRightAway(t *testing.T) {
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close immediately; it won't even reach the first unix.Read.
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Wait for the close to complete.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
isWatcherReallyClosed(t, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyCloseSlightlyLater(t *testing.T) {
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until readEvents has reached unix.Read, and Close.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Wait for the close to complete.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
isWatcherReallyClosed(t, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher")
|
||||||
|
}
|
||||||
|
w.Add(testDir)
|
||||||
|
|
||||||
|
// Wait until readEvents has reached unix.Read, and Close.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Wait for the close to complete.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
isWatcherReallyClosed(t, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyCloseAfterRead(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Add(testDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add .")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate an event.
|
||||||
|
os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING"))
|
||||||
|
|
||||||
|
// Wait for readEvents to read the event, then close the watcher.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Wait for the close to complete.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
isWatcherReallyClosed(t, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWatcherReallyClosed(t *testing.T, w *Watcher) {
|
||||||
|
select {
|
||||||
|
case err, ok := <-w.Errors:
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("w.Errors would have blocked; readEvents is still alive!")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-w.Events:
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("w.Events is not closed; readEvents is still alive after closing")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("w.Events would have blocked; readEvents is still alive!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyCloseCreate(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
err = w.Add(testDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add testDir: %v", err)
|
||||||
|
}
|
||||||
|
h, err := os.Create(filepath.Join(testDir, "testfile"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create file in testdir: %v", err)
|
||||||
|
}
|
||||||
|
h.Close()
|
||||||
|
select {
|
||||||
|
case _ = <-w.Events:
|
||||||
|
case err := <-w.Errors:
|
||||||
|
t.Fatalf("Error from watcher: %v", err)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
t.Fatalf("Took too long to wait for event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we've received one event, so the goroutine is ready.
|
||||||
|
// It's also blocking on unix.Read.
|
||||||
|
// Now we try to swap the file descriptor under its nose.
|
||||||
|
w.Close()
|
||||||
|
w, err = NewWatcher()
|
||||||
|
defer w.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create second watcher: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
err = w.Add(testDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error adding testDir again: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies the watcher can keep up with file creations/deletions
|
||||||
|
// when under load.
|
||||||
|
func TestInotifyStress(t *testing.T) {
|
||||||
|
maxNumToCreate := 1000
|
||||||
|
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
testFilePrefix := filepath.Join(testDir, "testfile")
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
err = w.Add(testDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add testDir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doneChan := make(chan struct{})
|
||||||
|
// The buffer ensures that the file generation goroutine is never blocked.
|
||||||
|
errChan := make(chan error, 2*maxNumToCreate)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < maxNumToCreate; i++ {
|
||||||
|
testFile := fmt.Sprintf("%s%d", testFilePrefix, i)
|
||||||
|
|
||||||
|
handle, err := os.Create(testFile)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("Create failed: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handle.Close()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("Close failed: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we delete a newly created file too quickly, inotify will skip the
|
||||||
|
// create event and only send the delete event.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
for i := 0; i < maxNumToCreate; i++ {
|
||||||
|
testFile := fmt.Sprintf("%s%d", testFilePrefix, i)
|
||||||
|
err = os.Remove(testFile)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("Remove failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(doneChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
creates := 0
|
||||||
|
removes := 0
|
||||||
|
|
||||||
|
finished := false
|
||||||
|
after := time.After(10 * time.Second)
|
||||||
|
for !finished {
|
||||||
|
select {
|
||||||
|
case <-after:
|
||||||
|
t.Fatalf("Not done")
|
||||||
|
case <-doneChan:
|
||||||
|
finished = true
|
||||||
|
case err := <-errChan:
|
||||||
|
t.Fatalf("Got an error from file creator goroutine: %v", err)
|
||||||
|
case err := <-w.Errors:
|
||||||
|
t.Fatalf("Got an error from watcher: %v", err)
|
||||||
|
case evt := <-w.Events:
|
||||||
|
if !strings.HasPrefix(evt.Name, testFilePrefix) {
|
||||||
|
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
|
||||||
|
}
|
||||||
|
if evt.Op == Create {
|
||||||
|
creates++
|
||||||
|
}
|
||||||
|
if evt.Op == Remove {
|
||||||
|
removes++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drain remaining events from channels
|
||||||
|
count := 0
|
||||||
|
for count < 10 {
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
t.Fatalf("Got an error from file creator goroutine: %v", err)
|
||||||
|
case err := <-w.Errors:
|
||||||
|
t.Fatalf("Got an error from watcher: %v", err)
|
||||||
|
case evt := <-w.Events:
|
||||||
|
if !strings.HasPrefix(evt.Name, testFilePrefix) {
|
||||||
|
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
|
||||||
|
}
|
||||||
|
if evt.Op == Create {
|
||||||
|
creates++
|
||||||
|
}
|
||||||
|
if evt.Op == Remove {
|
||||||
|
removes++
|
||||||
|
}
|
||||||
|
count = 0
|
||||||
|
default:
|
||||||
|
count++
|
||||||
|
// Give the watcher chances to fill the channels.
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if creates-removes > 1 || creates-removes < -1 {
|
||||||
|
t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes)
|
||||||
|
}
|
||||||
|
if creates < 50 {
|
||||||
|
t.Fatalf("Expected at least 50 creates, got %d", creates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyRemoveTwice(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
testFile := filepath.Join(testDir, "testfile")
|
||||||
|
|
||||||
|
handle, err := os.Create(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Create failed: %v", err)
|
||||||
|
}
|
||||||
|
handle.Close()
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
err = w.Add(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add testFile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Remove(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wanted successful remove but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Remove(testFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("no error on removing invalid file")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if len(w.watches) != 0 {
|
||||||
|
t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches)
|
||||||
|
}
|
||||||
|
if len(w.paths) != 0 {
|
||||||
|
t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyInnerMapLength(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
testFile := filepath.Join(testDir, "testfile")
|
||||||
|
|
||||||
|
handle, err := os.Create(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Create failed: %v", err)
|
||||||
|
}
|
||||||
|
handle.Close()
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
err = w.Add(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add testFile: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for err := range w.Errors {
|
||||||
|
t.Fatalf("error received: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = os.Remove(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to remove testFile: %v", err)
|
||||||
|
}
|
||||||
|
_ = <-w.Events // consume Remove event
|
||||||
|
<-time.After(50 * time.Millisecond) // wait IN_IGNORE propagated
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if len(w.watches) != 0 {
|
||||||
|
t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches)
|
||||||
|
}
|
||||||
|
if len(w.paths) != 0 {
|
||||||
|
t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyOverflow(t *testing.T) {
|
||||||
|
// We need to generate many more events than the
|
||||||
|
// fs.inotify.max_queued_events sysctl setting.
|
||||||
|
// We use multiple goroutines (one per directory)
|
||||||
|
// to speed up file creation.
|
||||||
|
numDirs := 128
|
||||||
|
numFiles := 1024
|
||||||
|
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
for dn := 0; dn < numDirs; dn++ {
|
||||||
|
testSubdir := fmt.Sprintf("%s/%d", testDir, dn)
|
||||||
|
|
||||||
|
err := os.Mkdir(testSubdir, 0777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot create subdir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Add(testSubdir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add subdir: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errChan := make(chan error, numDirs*numFiles)
|
||||||
|
|
||||||
|
for dn := 0; dn < numDirs; dn++ {
|
||||||
|
testSubdir := fmt.Sprintf("%s/%d", testDir, dn)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for fn := 0; fn < numFiles; fn++ {
|
||||||
|
testFile := fmt.Sprintf("%s/%d", testSubdir, fn)
|
||||||
|
|
||||||
|
handle, err := os.Create(testFile)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("Create failed: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handle.Close()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("Close failed: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
creates := 0
|
||||||
|
overflows := 0
|
||||||
|
|
||||||
|
after := time.After(10 * time.Second)
|
||||||
|
for overflows == 0 && creates < numDirs*numFiles {
|
||||||
|
select {
|
||||||
|
case <-after:
|
||||||
|
t.Fatalf("Not done")
|
||||||
|
case err := <-errChan:
|
||||||
|
t.Fatalf("Got an error from file creator goroutine: %v", err)
|
||||||
|
case err := <-w.Errors:
|
||||||
|
if err == ErrEventOverflow {
|
||||||
|
overflows++
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Got an error from watcher: %v", err)
|
||||||
|
}
|
||||||
|
case evt := <-w.Events:
|
||||||
|
if !strings.HasPrefix(evt.Name, testDir) {
|
||||||
|
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
|
||||||
|
}
|
||||||
|
if evt.Op == Create {
|
||||||
|
creates++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if creates == numDirs*numFiles {
|
||||||
|
t.Fatalf("Could not trigger overflow")
|
||||||
|
}
|
||||||
|
|
||||||
|
if overflows == 0 {
|
||||||
|
t.Fatalf("No overflow and not enough creates (expected %d, got %d)",
|
||||||
|
numDirs*numFiles, creates)
|
||||||
|
}
|
||||||
|
}
|
||||||
147
vendor/github.com/fsnotify/fsnotify/integration_darwin_test.go
generated
vendored
Normal file
147
vendor/github.com/fsnotify/fsnotify/integration_darwin_test.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testExchangedataForWatcher tests the watcher with the exchangedata operation on macOS.
|
||||||
|
//
|
||||||
|
// This is widely used for atomic saves on macOS, e.g. TextMate and in Apple's NSDocument.
|
||||||
|
//
|
||||||
|
// See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
|
||||||
|
// Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20
|
||||||
|
func testExchangedataForWatcher(t *testing.T, watchDir bool) {
|
||||||
|
// Create directory to watch
|
||||||
|
testDir1 := tempMkdir(t)
|
||||||
|
|
||||||
|
// For the intermediate file
|
||||||
|
testDir2 := tempMkdir(t)
|
||||||
|
|
||||||
|
defer os.RemoveAll(testDir1)
|
||||||
|
defer os.RemoveAll(testDir2)
|
||||||
|
|
||||||
|
resolvedFilename := "TestFsnotifyEvents.file"
|
||||||
|
|
||||||
|
// TextMate does:
|
||||||
|
//
|
||||||
|
// 1. exchangedata (intermediate, resolved)
|
||||||
|
// 2. unlink intermediate
|
||||||
|
//
|
||||||
|
// Let's try to simulate that:
|
||||||
|
resolved := filepath.Join(testDir1, resolvedFilename)
|
||||||
|
intermediate := filepath.Join(testDir2, resolvedFilename+"~")
|
||||||
|
|
||||||
|
// Make sure we create the file before we start watching
|
||||||
|
createAndSyncFile(t, resolved)
|
||||||
|
|
||||||
|
watcher := newWatcher(t)
|
||||||
|
|
||||||
|
// Test both variants in isolation
|
||||||
|
if watchDir {
|
||||||
|
addWatch(t, watcher, testDir1)
|
||||||
|
} else {
|
||||||
|
addWatch(t, watcher, resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive errors on the error channel on a separate goroutine
|
||||||
|
go func() {
|
||||||
|
for err := range watcher.Errors {
|
||||||
|
t.Fatalf("error received: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Receive events on the event channel on a separate goroutine
|
||||||
|
eventstream := watcher.Events
|
||||||
|
var removeReceived counter
|
||||||
|
var createReceived counter
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for event := range eventstream {
|
||||||
|
// Only count relevant events
|
||||||
|
if event.Name == filepath.Clean(resolved) {
|
||||||
|
if event.Op&Remove == Remove {
|
||||||
|
removeReceived.increment()
|
||||||
|
}
|
||||||
|
if event.Op&Create == Create {
|
||||||
|
createReceived.increment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("event received: %s", event)
|
||||||
|
}
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop.
|
||||||
|
for i := 1; i <= 3; i++ {
|
||||||
|
// The intermediate file is created in a folder outside the watcher
|
||||||
|
createAndSyncFile(t, intermediate)
|
||||||
|
|
||||||
|
// 1. Swap
|
||||||
|
if err := unix.Exchangedata(intermediate, resolved, 0); err != nil {
|
||||||
|
t.Fatalf("[%d] exchangedata failed: %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
// 2. Delete the intermediate file
|
||||||
|
err := os.Remove(intermediate)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two:
|
||||||
|
if removeReceived.value() < 3 {
|
||||||
|
t.Fatal("fsnotify remove events have not been received after 500 ms")
|
||||||
|
}
|
||||||
|
|
||||||
|
if createReceived.value() < 3 {
|
||||||
|
t.Fatal("fsnotify create events have not been received after 500 ms")
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher.Close()
|
||||||
|
t.Log("waiting for the event channel to become closed...")
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
t.Log("event channel closed")
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("event stream was not closed after 2 seconds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir.
|
||||||
|
func TestExchangedataInWatchedDir(t *testing.T) {
|
||||||
|
testExchangedataForWatcher(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExchangedataInWatchedDir test exchangedata operation on watched file.
|
||||||
|
func TestExchangedataInWatchedFile(t *testing.T) {
|
||||||
|
testExchangedataForWatcher(t, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAndSyncFile(t *testing.T, filepath string) {
|
||||||
|
f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("creating %s failed: %s", filepath, err)
|
||||||
|
}
|
||||||
|
f1.Sync()
|
||||||
|
f1.Close()
|
||||||
|
}
|
||||||
1237
vendor/github.com/fsnotify/fsnotify/integration_test.go
generated
vendored
Normal file
1237
vendor/github.com/fsnotify/fsnotify/integration_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
521
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
521
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build freebsd openbsd netbsd dragonfly darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
|
||||||
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
|
|
||||||
|
mu sync.Mutex // Protects access to watcher data
|
||||||
|
watches map[string]int // Map of watched file descriptors (key: path).
|
||||||
|
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||||
|
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||||
|
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
||||||
|
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathInfo struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
kq, err := kqueue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &Watcher{
|
||||||
|
kq: kq,
|
||||||
|
watches: make(map[string]int),
|
||||||
|
dirFlags: make(map[string]uint32),
|
||||||
|
paths: make(map[int]pathInfo),
|
||||||
|
fileExists: make(map[string]bool),
|
||||||
|
externalWatches: make(map[string]bool),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
|
||||||
|
// copy paths to remove while locked
|
||||||
|
var pathsToRemove = make([]string, 0, len(w.watches))
|
||||||
|
for name := range w.watches {
|
||||||
|
pathsToRemove = append(pathsToRemove, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
// unlock before calling Remove, which also locks
|
||||||
|
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a "quit" message to the reader goroutine
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.externalWatches[name] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
_, err := w.addWatch(name, noteAllEvents)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
w.mu.Lock()
|
||||||
|
watchfd, ok := w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerRemove = unix.EV_DELETE
|
||||||
|
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(watchfd)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
isDir := w.paths[watchfd].isDir
|
||||||
|
delete(w.watches, name)
|
||||||
|
delete(w.paths, watchfd)
|
||||||
|
delete(w.dirFlags, name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Find all watched paths that are in this directory that are not external.
|
||||||
|
if isDir {
|
||||||
|
var pathsToRemove []string
|
||||||
|
w.mu.Lock()
|
||||||
|
for _, path := range w.paths {
|
||||||
|
wdir, _ := filepath.Split(path.name)
|
||||||
|
if filepath.Clean(wdir) == name {
|
||||||
|
if !w.externalWatches[path.name] {
|
||||||
|
pathsToRemove = append(pathsToRemove, path.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
// Since these are internal, not much sense in propagating error
|
||||||
|
// to the user, as that will just confuse them with an error about
|
||||||
|
// a path they did not explicitly watch themselves.
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||||
|
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||||
|
|
||||||
|
// keventWaitTime to block on each read from kevent
|
||||||
|
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// addWatch adds name to the watched file set.
|
||||||
|
// The flags are interpreted as described in kevent(2).
|
||||||
|
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||||
|
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||||
|
var isDir bool
|
||||||
|
// Make ./name and name equivalent
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return "", errors.New("kevent instance already closed")
|
||||||
|
}
|
||||||
|
watchfd, alreadyWatching := w.watches[name]
|
||||||
|
// We already have a watch, but we can still override flags.
|
||||||
|
if alreadyWatching {
|
||||||
|
isDir = w.paths[watchfd].isDir
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch sockets.
|
||||||
|
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch named pipes.
|
||||||
|
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow Symlinks
|
||||||
|
// Unfortunately, Linux can add bogus symlinks to watch list without
|
||||||
|
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
||||||
|
// consistency, we will act like everything is fine. There will simply
|
||||||
|
// be no file events for broken symlinks.
|
||||||
|
// Hence the returns of nil on errors.
|
||||||
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
name, err = filepath.EvalSymlinks(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
_, alreadyWatching = w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if alreadyWatching {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchfd, err = unix.Open(name, openMode, 0700)
|
||||||
|
if watchfd == -1 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
isDir = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
|
||||||
|
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
||||||
|
unix.Close(watchfd)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches[name] = watchfd
|
||||||
|
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDir {
|
||||||
|
// Watch the directory if it has not been watched before,
|
||||||
|
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||||
|
w.mu.Lock()
|
||||||
|
|
||||||
|
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||||
|
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||||
|
// Store flags so this watch can be updated later
|
||||||
|
w.dirFlags[name] = flags
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watchDir {
|
||||||
|
if err := w.watchDirectoryFiles(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
|
// Event values that it sends down the Events channel.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
eventBuffer := make([]unix.Kevent_t, 10)
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
// See if there is a message on the "done" channel
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get new events
|
||||||
|
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
||||||
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != unix.EINTR {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the events we received to the Events channel
|
||||||
|
for len(kevents) > 0 {
|
||||||
|
kevent := &kevents[0]
|
||||||
|
watchfd := int(kevent.Ident)
|
||||||
|
mask := uint32(kevent.Fflags)
|
||||||
|
w.mu.Lock()
|
||||||
|
path := w.paths[watchfd]
|
||||||
|
w.mu.Unlock()
|
||||||
|
event := newEvent(path.name, mask)
|
||||||
|
|
||||||
|
if path.isDir && !(event.Op&Remove == Remove) {
|
||||||
|
// Double check to make sure the directory exists. This can happen when
|
||||||
|
// we do a rm -fr on a recursively watched folders and we receive a
|
||||||
|
// modification event first but the folder has been deleted and later
|
||||||
|
// receive the delete event
|
||||||
|
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||||
|
// mark is as delete event
|
||||||
|
event.Op |= Remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
||||||
|
w.Remove(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.fileExists, event.Name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||||
|
w.sendDirectoryChangeEvents(event.Name)
|
||||||
|
} else {
|
||||||
|
// Send the event on the Events channel.
|
||||||
|
select {
|
||||||
|
case w.Events <- event:
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&Remove == Remove {
|
||||||
|
// Look for a file that may have overwritten this.
|
||||||
|
// For example, mv f1 f2 will delete f2, then create f2.
|
||||||
|
if path.isDir {
|
||||||
|
fileDir := filepath.Clean(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
_, found := w.watches[fileDir]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if found {
|
||||||
|
// make sure the directory exists before we watch for changes. When we
|
||||||
|
// do a recursive watch and perform rm -fr, the parent directory might
|
||||||
|
// have gone missing, ignore the missing directory and let the
|
||||||
|
// upcoming delete event remove the watch from the parent directory.
|
||||||
|
if _, err := os.Lstat(fileDir); err == nil {
|
||||||
|
w.sendDirectoryChangeEvents(fileDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePath := filepath.Clean(event.Name)
|
||||||
|
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||||
|
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next event
|
||||||
|
kevents = kevents[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
err := unix.Close(w.kq)
|
||||||
|
if err != nil {
|
||||||
|
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateEvent(name string) Event {
|
||||||
|
return Event{Name: name, Op: Create}
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||||
|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendDirectoryEvents searches the directory for newly created files
|
||||||
|
// and sends them over the event channel. This functionality is to have
|
||||||
|
// the BSD version of fsnotify match Linux inotify which provides a
|
||||||
|
// create event for files created in a watched directory.
|
||||||
|
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for new files
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||||
|
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
_, doesExist := w.fileExists[filePath]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !doesExist {
|
||||||
|
// Send create event
|
||||||
|
select {
|
||||||
|
case w.Events <- newCreateEvent(filePath):
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
// mimic Linux providing delete events for subdirectories
|
||||||
|
// but preserve the flags used if currently watching subdirectory
|
||||||
|
w.mu.Lock()
|
||||||
|
flags := w.dirFlags[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||||
|
return w.addWatch(name, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch file to mimic Linux inotify
|
||||||
|
return w.addWatch(name, noteAllEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// kqueue creates a new kernel event queue and returns a descriptor.
|
||||||
|
func kqueue() (kq int, err error) {
|
||||||
|
kq, err = unix.Kqueue()
|
||||||
|
if kq == -1 {
|
||||||
|
return kq, err
|
||||||
|
}
|
||||||
|
return kq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// register events with the queue
|
||||||
|
func register(kq int, fds []int, flags int, fflags uint32) error {
|
||||||
|
changes := make([]unix.Kevent_t, len(fds))
|
||||||
|
|
||||||
|
for i, fd := range fds {
|
||||||
|
// SetKevent converts int to the platform-specific types:
|
||||||
|
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||||
|
changes[i].Fflags = fflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the events
|
||||||
|
success, err := unix.Kevent(kq, changes, nil, nil)
|
||||||
|
if success == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read retrieves pending events, or waits until an event occurs.
|
||||||
|
// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
||||||
|
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
|
||||||
|
n, err := unix.Kevent(kq, nil, events, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return events[0:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationToTimespec prepares a timeout value
|
||||||
|
func durationToTimespec(d time.Duration) unix.Timespec {
|
||||||
|
return unix.NsecToTimespec(d.Nanoseconds())
|
||||||
|
}
|
||||||
11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build freebsd openbsd netbsd dragonfly
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const openMode = unix.O_NONBLOCK | unix.O_RDONLY
|
||||||
12
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
12
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// note: this constant is not defined on BSD
|
||||||
|
const openMode = unix.O_EVTONLY
|
||||||
561
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
561
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
port syscall.Handle // Handle to completion port
|
||||||
|
watches watchMap // Map of watches (key: i-number)
|
||||||
|
input chan *input // Inputs to the reader are sent on this channel
|
||||||
|
quit chan chan<- error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
||||||
|
if e != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
port: port,
|
||||||
|
watches: make(watchMap),
|
||||||
|
input: make(chan *input, 1),
|
||||||
|
Events: make(chan Event, 50),
|
||||||
|
Errors: make(chan error),
|
||||||
|
quit: make(chan chan<- error, 1),
|
||||||
|
}
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
if w.isClosed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
|
||||||
|
// Send "quit" message to the reader goroutine
|
||||||
|
ch := make(chan error)
|
||||||
|
w.quit <- ch
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
if w.isClosed {
|
||||||
|
return errors.New("watcher already closed")
|
||||||
|
}
|
||||||
|
in := &input{
|
||||||
|
op: opAddWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
flags: sysFSALLEVENTS,
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
in := &input{
|
||||||
|
op: opRemoveWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Options for AddWatch
|
||||||
|
sysFSONESHOT = 0x80000000
|
||||||
|
sysFSONLYDIR = 0x1000000
|
||||||
|
|
||||||
|
// Events
|
||||||
|
sysFSACCESS = 0x1
|
||||||
|
sysFSALLEVENTS = 0xfff
|
||||||
|
sysFSATTRIB = 0x4
|
||||||
|
sysFSCLOSE = 0x18
|
||||||
|
sysFSCREATE = 0x100
|
||||||
|
sysFSDELETE = 0x200
|
||||||
|
sysFSDELETESELF = 0x400
|
||||||
|
sysFSMODIFY = 0x2
|
||||||
|
sysFSMOVE = 0xc0
|
||||||
|
sysFSMOVEDFROM = 0x40
|
||||||
|
sysFSMOVEDTO = 0x80
|
||||||
|
sysFSMOVESELF = 0x800
|
||||||
|
|
||||||
|
// Special events
|
||||||
|
sysFSIGNORED = 0x8000
|
||||||
|
sysFSQOVERFLOW = 0x4000
|
||||||
|
)
|
||||||
|
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddWatch = iota
|
||||||
|
opRemoveWatch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
provisional uint64 = 1 << (32 + iota)
|
||||||
|
)
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
op int
|
||||||
|
path string
|
||||||
|
flags uint32
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type inode struct {
|
||||||
|
handle syscall.Handle
|
||||||
|
volume uint32
|
||||||
|
index uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
ov syscall.Overlapped
|
||||||
|
ino *inode // i-number
|
||||||
|
path string // Directory path
|
||||||
|
mask uint64 // Directory itself is being watched with these notify flags
|
||||||
|
names map[string]uint64 // Map of names being watched and their notify flags
|
||||||
|
rename string // Remembers the old name while renaming a file
|
||||||
|
buf [4096]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexMap map[uint64]*watch
|
||||||
|
type watchMap map[uint32]indexMap
|
||||||
|
|
||||||
|
func (w *Watcher) wakeupReader() error {
|
||||||
|
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||||
|
if e != nil {
|
||||||
|
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDir(pathname string) (dir string, err error) {
|
||||||
|
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
||||||
|
if e != nil {
|
||||||
|
return "", os.NewSyscallError("GetFileAttributes", e)
|
||||||
|
}
|
||||||
|
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
dir = pathname
|
||||||
|
} else {
|
||||||
|
dir, _ = filepath.Split(pathname)
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIno(path string) (ino *inode, err error) {
|
||||||
|
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
||||||
|
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)
|
||||||
|
if e != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateFile", e)
|
||||||
|
}
|
||||||
|
var fi syscall.ByHandleFileInformation
|
||||||
|
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
||||||
|
syscall.CloseHandle(h)
|
||||||
|
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
||||||
|
}
|
||||||
|
ino = &inode{
|
||||||
|
handle: h,
|
||||||
|
volume: fi.VolumeSerialNumber,
|
||||||
|
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||||
|
}
|
||||||
|
return ino, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) get(ino *inode) *watch {
|
||||||
|
if i := m[ino.volume]; i != nil {
|
||||||
|
return i[ino.index]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) set(ino *inode, watch *watch) {
|
||||||
|
i := m[ino.volume]
|
||||||
|
if i == nil {
|
||||||
|
i = make(indexMap)
|
||||||
|
m[ino.volume] = i
|
||||||
|
}
|
||||||
|
i[ino.index] = watch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||||
|
dir, err := getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flags&sysFSONLYDIR != 0 && pathname != dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ino, err := getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watchEntry == nil {
|
||||||
|
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
||||||
|
syscall.CloseHandle(ino.handle)
|
||||||
|
return os.NewSyscallError("CreateIoCompletionPort", e)
|
||||||
|
}
|
||||||
|
watchEntry = &watch{
|
||||||
|
ino: ino,
|
||||||
|
path: dir,
|
||||||
|
names: make(map[string]uint64),
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches.set(ino, watchEntry)
|
||||||
|
w.mu.Unlock()
|
||||||
|
flags |= provisional
|
||||||
|
} else {
|
||||||
|
syscall.CloseHandle(ino.handle)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask |= flags
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||||
|
}
|
||||||
|
if err = w.startRead(watchEntry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask &= ^provisional
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) remWatch(pathname string) error {
|
||||||
|
dir, err := getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ino, err := getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watch := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watch == nil {
|
||||||
|
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
watch.mask = 0
|
||||||
|
} else {
|
||||||
|
name := filepath.Base(pathname)
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
return w.startRead(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) deleteWatch(watch *watch) {
|
||||||
|
for name, mask := range watch.names {
|
||||||
|
if mask&provisional == 0 {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if watch.mask != 0 {
|
||||||
|
if watch.mask&provisional == 0 {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) startRead(watch *watch) error {
|
||||||
|
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
||||||
|
w.Errors <- os.NewSyscallError("CancelIo", e)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
}
|
||||||
|
mask := toWindowsFlags(watch.mask)
|
||||||
|
for _, m := range watch.names {
|
||||||
|
mask |= toWindowsFlags(m)
|
||||||
|
}
|
||||||
|
if mask == 0 {
|
||||||
|
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
||||||
|
w.Errors <- os.NewSyscallError("CloseHandle", e)
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||||
|
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||||
|
if e != nil {
|
||||||
|
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
||||||
|
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||||
|
// Watched directory was probably removed
|
||||||
|
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
|
||||||
|
if watch.mask&sysFSONESHOT != 0 {
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the I/O completion port, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel.
|
||||||
|
// Entry point to the I/O thread.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
n, key uint32
|
||||||
|
ov *syscall.Overlapped
|
||||||
|
)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
for {
|
||||||
|
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
||||||
|
watch := (*watch)(unsafe.Pointer(ov))
|
||||||
|
|
||||||
|
if watch == nil {
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.mu.Lock()
|
||||||
|
var indexes []indexMap
|
||||||
|
for _, index := range w.watches {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, index := range indexes {
|
||||||
|
for _, watch := range index {
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if e := syscall.CloseHandle(w.port); e != nil {
|
||||||
|
err = os.NewSyscallError("CloseHandle", e)
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
case in := <-w.input:
|
||||||
|
switch in.op {
|
||||||
|
case opAddWatch:
|
||||||
|
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||||
|
case opRemoveWatch:
|
||||||
|
in.reply <- w.remWatch(in.path)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e {
|
||||||
|
case syscall.ERROR_MORE_DATA:
|
||||||
|
if watch == nil {
|
||||||
|
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
||||||
|
} else {
|
||||||
|
// The i/o succeeded but the buffer is full.
|
||||||
|
// In theory we should be building up a full packet.
|
||||||
|
// In practice we can get away with just carrying on.
|
||||||
|
n = uint32(unsafe.Sizeof(watch.buf))
|
||||||
|
}
|
||||||
|
case syscall.ERROR_ACCESS_DENIED:
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
continue
|
||||||
|
case syscall.ERROR_OPERATION_ABORTED:
|
||||||
|
// CancelIo was called on this handle
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
||||||
|
continue
|
||||||
|
case nil:
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
for {
|
||||||
|
if n == 0 {
|
||||||
|
w.Events <- newEvent("", sysFSQOVERFLOW)
|
||||||
|
w.Errors <- errors.New("short read in readEvents()")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||||
|
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
|
||||||
|
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
|
||||||
|
fullname := filepath.Join(watch.path, name)
|
||||||
|
|
||||||
|
var mask uint64
|
||||||
|
switch raw.Action {
|
||||||
|
case syscall.FILE_ACTION_REMOVED:
|
||||||
|
mask = sysFSDELETESELF
|
||||||
|
case syscall.FILE_ACTION_MODIFIED:
|
||||||
|
mask = sysFSMODIFY
|
||||||
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
watch.rename = name
|
||||||
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
if watch.names[watch.rename] != 0 {
|
||||||
|
watch.names[name] |= watch.names[watch.rename]
|
||||||
|
delete(watch.names, watch.rename)
|
||||||
|
mask = sysFSMOVESELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNameEvent := func() {
|
||||||
|
if w.sendEvent(fullname, watch.names[name]&mask) {
|
||||||
|
if watch.names[name]&sysFSONESHOT != 0 {
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
||||||
|
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
||||||
|
if watch.mask&sysFSONESHOT != 0 {
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
fullname = filepath.Join(watch.path, watch.rename)
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
if raw.NextEntryOffset == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += raw.NextEntryOffset
|
||||||
|
|
||||||
|
// Error!
|
||||||
|
if offset >= n {
|
||||||
|
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.startRead(watch); err != nil {
|
||||||
|
w.Errors <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||||
|
if mask == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
event := newEvent(name, uint32(mask))
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.quit <- ch
|
||||||
|
case w.Events <- event:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func toWindowsFlags(mask uint64) uint32 {
|
||||||
|
var m uint32
|
||||||
|
if mask&sysFSACCESS != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||||
|
}
|
||||||
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFSnotifyFlags(action uint32) uint64 {
|
||||||
|
switch action {
|
||||||
|
case syscall.FILE_ACTION_ADDED:
|
||||||
|
return sysFSCREATE
|
||||||
|
case syscall.FILE_ACTION_REMOVED:
|
||||||
|
return sysFSDELETE
|
||||||
|
case syscall.FILE_ACTION_MODIFIED:
|
||||||
|
return sysFSMODIFY
|
||||||
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
return sysFSMOVEDFROM
|
||||||
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
return sysFSMOVEDTO
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user