mirror of
				https://github.com/minio/minio.git
				synced 2025-10-29 15:55:00 -04:00 
			
		
		
		
	Implement auto cert reloading (#5963)
This commit is contained in:
		
							parent
							
								
									487ecedc51
								
							
						
					
					
						commit
						74328c3061
					
				
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							| @ -58,7 +58,7 @@ spelling: | ||||
| check: test | ||||
| test: verifiers build | ||||
| 	@echo "Running unit tests" | ||||
| 	@go test $(GOFLAGS) ./... | ||||
| 	@go test $(GOFLAGS) -tags kqueue ./... | ||||
| 	@echo "Verifying build" | ||||
| 	@(env bash $(PWD)/buildscripts/verify-build.sh) | ||||
| 
 | ||||
| @ -69,7 +69,7 @@ coverage: build | ||||
| # Builds minio locally.
 | ||||
| build: checks | ||||
| 	@echo "Building minio binary to './minio'" | ||||
| 	@CGO_ENABLED=0 go build --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio | ||||
| 	@CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio | ||||
| 
 | ||||
| pkg-add: | ||||
| 	@echo "Adding new package $(PKG)" | ||||
|  | ||||
							
								
								
									
										31
									
								
								cmd/certs.go
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								cmd/certs.go
									
									
									
									
									
								
							| @ -25,6 +25,8 @@ import ( | ||||
| 	"encoding/pem" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/minio/minio/pkg/certs" | ||||
| ) | ||||
| 
 | ||||
| // TLSPrivateKeyPassword is the environment variable which contains the password used | ||||
| @ -135,10 +137,19 @@ func loadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) { | ||||
| 	if err != nil { | ||||
| 		return tls.Certificate{}, uiErrSSLUnexpectedData(nil).Msg(err.Error()) | ||||
| 	} | ||||
| 	// Ensure that the private key is not a P-384 or P-521 EC key. | ||||
| 	// The Go TLS stack does not provide constant-time implementations of P-384 and P-521. | ||||
| 	if priv, ok := cert.PrivateKey.(crypto.Signer); ok { | ||||
| 		if pub, ok := priv.Public().(*ecdsa.PublicKey); ok { | ||||
| 			if name := pub.Params().Name; name == "P-384" || name == "P-521" { // unfortunately there is no cleaner way to check | ||||
| 				return tls.Certificate{}, uiErrSSLUnexpectedData(nil).Msg("tls: the ECDSA curve '%s' is not supported", name) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return cert, nil | ||||
| } | ||||
| 
 | ||||
| func getSSLConfig() (x509Certs []*x509.Certificate, rootCAs *x509.CertPool, tlsCert *tls.Certificate, secureConn bool, err error) { | ||||
| func getSSLConfig() (x509Certs []*x509.Certificate, rootCAs *x509.CertPool, c *certs.Certs, secureConn bool, err error) { | ||||
| 	if !(isFile(getPublicCertFile()) && isFile(getPrivateKeyFile())) { | ||||
| 		return nil, nil, nil, false, nil | ||||
| 	} | ||||
| @ -147,27 +158,15 @@ func getSSLConfig() (x509Certs []*x509.Certificate, rootCAs *x509.CertPool, tlsC | ||||
| 		return nil, nil, nil, false, err | ||||
| 	} | ||||
| 
 | ||||
| 	var cert tls.Certificate | ||||
| 	if cert, err = loadX509KeyPair(getPublicCertFile(), getPrivateKeyFile()); err != nil { | ||||
| 	c, err = certs.New(getPublicCertFile(), getPrivateKeyFile(), loadX509KeyPair) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, nil, false, err | ||||
| 	} | ||||
| 	// Ensure that the private key is not a P-384 or P-521 EC key. | ||||
| 	// The Go TLS stack does not provide constant-time implementations of P-384 and P-521. | ||||
| 	if priv, ok := cert.PrivateKey.(crypto.Signer); ok { | ||||
| 		if pub, ok := priv.Public().(*ecdsa.PublicKey); ok { | ||||
| 			if name := pub.Params().Name; name == "P-384" || name == "P-521" { // unfortunately there is no cleaner way to check | ||||
| 				return nil, nil, nil, false, uiErrSSLUnexpectedData(nil).Msg("tls: the ECDSA curve '%s' is not supported", name) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	tlsCert = &cert | ||||
| 
 | ||||
| 	if rootCAs, err = getRootCAs(getCADir()); err != nil { | ||||
| 		return nil, nil, nil, false, err | ||||
| 	} | ||||
| 
 | ||||
| 	secureConn = true | ||||
| 	return x509Certs, rootCAs, tlsCert, secureConn, nil | ||||
| 	return x509Certs, rootCAs, c, secureConn, nil | ||||
| } | ||||
|  | ||||
| @ -31,6 +31,7 @@ import ( | ||||
| 	"github.com/minio/cli" | ||||
| 	xhttp "github.com/minio/minio/cmd/http" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/certs" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| @ -164,7 +165,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) { | ||||
| 
 | ||||
| 	// Check and load SSL certificates. | ||||
| 	var err error | ||||
| 	globalPublicCerts, globalRootCAs, globalTLSCertificate, globalIsSSL, err = getSSLConfig() | ||||
| 	globalPublicCerts, globalRootCAs, globalTLSCerts, globalIsSSL, err = getSSLConfig() | ||||
| 	logger.FatalIf(err, "Invalid SSL certificate file") | ||||
| 
 | ||||
| 	// Set system resources to maximum. | ||||
| @ -179,9 +180,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) { | ||||
| 	// Create new policy system. | ||||
| 	globalPolicySys = NewPolicySys() | ||||
| 
 | ||||
| 	newObject, err := gw.NewGatewayLayer(globalServerConfig.GetCredential()) | ||||
| 	logger.FatalIf(err, "Unable to initialize gateway layer") | ||||
| 
 | ||||
| 	router := mux.NewRouter().SkipClean(true) | ||||
| 
 | ||||
| 	// Add healthcheck router | ||||
| @ -198,17 +196,31 @@ func StartGateway(ctx *cli.Context, gw Gateway) { | ||||
| 	// Add API router. | ||||
| 	registerAPIRouter(router) | ||||
| 
 | ||||
| 	globalHTTPServer = xhttp.NewServer([]string{gatewayAddr}, registerHandlers(router, globalHandlers...), globalTLSCertificate) | ||||
| 	var getCert certs.GetCertificateFunc | ||||
| 	if globalTLSCerts != nil { | ||||
| 		getCert = globalTLSCerts.GetCertificate | ||||
| 	} | ||||
| 
 | ||||
| 	globalHTTPServer = xhttp.NewServer([]string{gatewayAddr}, registerHandlers(router, globalHandlers...), getCert) | ||||
| 	globalHTTPServer.ReadTimeout = globalConnReadTimeout | ||||
| 	globalHTTPServer.WriteTimeout = globalConnWriteTimeout | ||||
| 	globalHTTPServer.UpdateBytesReadFunc = globalConnStats.incInputBytes | ||||
| 	globalHTTPServer.UpdateBytesWrittenFunc = globalConnStats.incOutputBytes | ||||
| 
 | ||||
| 	// Start server, automatically configures TLS if certs are available. | ||||
| 	go func() { | ||||
| 		globalHTTPServerErrorCh <- globalHTTPServer.Start() | ||||
| 	}() | ||||
| 
 | ||||
| 	signal.Notify(globalOSSignalCh, os.Interrupt, syscall.SIGTERM) | ||||
| 
 | ||||
| 	newObject, err := gw.NewGatewayLayer(globalServerConfig.GetCredential()) | ||||
| 	if err != nil { | ||||
| 		// Stop watching for any certificate changes. | ||||
| 		globalTLSCerts.Stop() | ||||
| 
 | ||||
| 		globalHTTPServer.Shutdown() | ||||
| 		logger.FatalIf(err, "Unable to initialize gateway backend") | ||||
| 	} | ||||
| 
 | ||||
| 	// Once endpoints are finalized, initialize the new object api. | ||||
| 	globalObjLayerMutex.Lock() | ||||
| 	globalObjectAPI = newObject | ||||
|  | ||||
| @ -17,7 +17,6 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| @ -27,6 +26,7 @@ import ( | ||||
| 	"github.com/fatih/color" | ||||
| 	xhttp "github.com/minio/minio/cmd/http" | ||||
| 	"github.com/minio/minio/pkg/auth" | ||||
| 	"github.com/minio/minio/pkg/certs" | ||||
| ) | ||||
| 
 | ||||
| // minio configuration related constants. | ||||
| @ -131,7 +131,7 @@ var ( | ||||
| 	// IsSSL indicates if the server is configured with SSL. | ||||
| 	globalIsSSL bool | ||||
| 
 | ||||
| 	globalTLSCertificate *tls.Certificate | ||||
| 	globalTLSCerts *certs.Certs | ||||
| 
 | ||||
| 	globalHTTPServer        *xhttp.Server | ||||
| 	globalHTTPServerErrorCh = make(chan error) | ||||
|  | ||||
| @ -49,6 +49,14 @@ func getNextPort() string { | ||||
| 	return strconv.Itoa(int(atomic.AddUint32(&serverPort, 1))) | ||||
| } | ||||
| 
 | ||||
| var getCert = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { | ||||
| 	certificate, err := getTLSCert() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &certificate, nil | ||||
| } | ||||
| 
 | ||||
| func getTLSCert() (tls.Certificate, error) { | ||||
| 	keyPEMBlock := []byte(`-----BEGIN RSA PRIVATE KEY----- | ||||
| MIIEpAIBAAKCAQEApEkbPrT6wzcWK1W5atQiGptvuBsRdf8MCg4u6SN10QbslA5k | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2017 Minio, Inc. | ||||
|  * Minio Cloud Storage, (C) 2017, 2018 Minio, Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
| @ -26,6 +26,7 @@ import ( | ||||
| 
 | ||||
| 	humanize "github.com/dustin/go-humanize" | ||||
| 	"github.com/minio/minio-go/pkg/set" | ||||
| 	"github.com/minio/minio/pkg/certs" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| @ -176,17 +177,18 @@ var defaultCipherSuites = []uint16{ | ||||
| var secureCurves = []tls.CurveID{tls.X25519, tls.CurveP256} | ||||
| 
 | ||||
| // NewServer - creates new HTTP server using given arguments. | ||||
| func NewServer(addrs []string, handler http.Handler, certificate *tls.Certificate) *Server { | ||||
| func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificateFunc) *Server { | ||||
| 	var tlsConfig *tls.Config | ||||
| 	if certificate != nil { | ||||
| 	if getCert != nil { | ||||
| 		tlsConfig = &tls.Config{ | ||||
| 			// TLS hardening | ||||
| 			PreferServerCipherSuites: true, | ||||
| 			CipherSuites:             defaultCipherSuites, | ||||
| 			CurvePreferences:         secureCurves, | ||||
| 			MinVersion:               tls.VersionTLS12, | ||||
| 			NextProtos:               []string{"http/1.1", "h2"}, | ||||
| 		} | ||||
| 		tlsConfig.Certificates = append(tlsConfig.Certificates, *certificate) | ||||
| 		tlsConfig.GetCertificate = getCert | ||||
| 	} | ||||
| 
 | ||||
| 	httpServer := &Server{ | ||||
|  | ||||
| @ -23,14 +23,12 @@ import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/minio/minio/pkg/certs" | ||||
| ) | ||||
| 
 | ||||
| func TestNewServer(t *testing.T) { | ||||
| 	nonLoopBackIP := getNonLoopBackIP(t) | ||||
| 	certificate, err := getTLSCert() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to parse private/certificate data. %v\n", err) | ||||
| 	} | ||||
| 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		fmt.Fprintf(w, "Hello, world") | ||||
| 	}) | ||||
| @ -38,18 +36,18 @@ func TestNewServer(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		addrs   []string | ||||
| 		handler http.Handler | ||||
| 		certificate *tls.Certificate | ||||
| 		certFn  certs.GetCertificateFunc | ||||
| 	}{ | ||||
| 		{[]string{"127.0.0.1:9000"}, handler, nil}, | ||||
| 		{[]string{nonLoopBackIP + ":9000"}, handler, nil}, | ||||
| 		{[]string{"127.0.0.1:9000", nonLoopBackIP + ":9000"}, handler, nil}, | ||||
| 		{[]string{"127.0.0.1:9000"}, handler, &certificate}, | ||||
| 		{[]string{nonLoopBackIP + ":9000"}, handler, &certificate}, | ||||
| 		{[]string{"127.0.0.1:9000", nonLoopBackIP + ":9000"}, handler, &certificate}, | ||||
| 		{[]string{"127.0.0.1:9000"}, handler, getCert}, | ||||
| 		{[]string{nonLoopBackIP + ":9000"}, handler, getCert}, | ||||
| 		{[]string{"127.0.0.1:9000", nonLoopBackIP + ":9000"}, handler, getCert}, | ||||
| 	} | ||||
| 
 | ||||
| 	for i, testCase := range testCases { | ||||
| 		server := NewServer(testCase.addrs, testCase.handler, testCase.certificate) | ||||
| 		server := NewServer(testCase.addrs, testCase.handler, testCase.certFn) | ||||
| 		if server == nil { | ||||
| 			t.Fatalf("Case %v: server: expected: <non-nil>, got: <nil>", (i + 1)) | ||||
| 		} | ||||
| @ -63,7 +61,7 @@ func TestNewServer(t *testing.T) { | ||||
| 		// 	t.Fatalf("Case %v: server.Handler: expected: %v, got: %v", (i + 1), testCase.handler, server.Handler) | ||||
| 		// } | ||||
| 
 | ||||
| 		if testCase.certificate == nil { | ||||
| 		if testCase.certFn == nil { | ||||
| 			if server.TLSConfig != nil { | ||||
| 				t.Fatalf("Case %v: server.TLSConfig: expected: <nil>, got: %v", (i + 1), server.TLSConfig) | ||||
| 			} | ||||
| @ -122,11 +120,6 @@ func TestServerTLSCiphers(t *testing.T) { | ||||
| 		tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // Disabled because of RSA-PKCS1-v1.5 - AES-GCM is considered secure. | ||||
| 	} | ||||
| 
 | ||||
| 	certificate, err := getTLSCert() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to parse private/certificate data. %v\n", err) | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		ciphers            []uint16 | ||||
| 		resetServerCiphers bool | ||||
| @ -145,8 +138,7 @@ func TestServerTLSCiphers(t *testing.T) { | ||||
| 			server := NewServer([]string{addr}, | ||||
| 				http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 					fmt.Fprintf(w, "Hello, world") | ||||
| 				}), | ||||
| 				&certificate) | ||||
| 				}), getCert) | ||||
| 			if testCase.resetServerCiphers { | ||||
| 				// Use Go default ciphers. | ||||
| 				server.TLSConfig.CipherSuites = nil | ||||
|  | ||||
| @ -29,6 +29,7 @@ import ( | ||||
| 	"github.com/minio/dsync" | ||||
| 	xhttp "github.com/minio/minio/cmd/http" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/pkg/certs" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| @ -215,7 +216,7 @@ func serverMain(ctx *cli.Context) { | ||||
| 
 | ||||
| 	// Check and load SSL certificates. | ||||
| 	var err error | ||||
| 	globalPublicCerts, globalRootCAs, globalTLSCertificate, globalIsSSL, err = getSSLConfig() | ||||
| 	globalPublicCerts, globalRootCAs, globalTLSCerts, globalIsSSL, err = getSSLConfig() | ||||
| 	logger.FatalIf(err, "Unable to load the TLS configuration") | ||||
| 
 | ||||
| 	// Is distributed setup, error out if no certificates are found for HTTPS endpoints. | ||||
| @ -275,7 +276,12 @@ func serverMain(ctx *cli.Context) { | ||||
| 	// Initialize Admin Peers inter-node communication only in distributed setup. | ||||
| 	initGlobalAdminPeers(globalEndpoints) | ||||
| 
 | ||||
| 	globalHTTPServer = xhttp.NewServer([]string{globalMinioAddr}, handler, globalTLSCertificate) | ||||
| 	var getCert certs.GetCertificateFunc | ||||
| 	if globalTLSCerts != nil { | ||||
| 		getCert = globalTLSCerts.GetCertificate | ||||
| 	} | ||||
| 
 | ||||
| 	globalHTTPServer = xhttp.NewServer([]string{globalMinioAddr}, handler, getCert) | ||||
| 	globalHTTPServer.ReadTimeout = globalConnReadTimeout | ||||
| 	globalHTTPServer.WriteTimeout = globalConnWriteTimeout | ||||
| 	globalHTTPServer.UpdateBytesReadFunc = globalConnStats.incInputBytes | ||||
| @ -288,6 +294,9 @@ func serverMain(ctx *cli.Context) { | ||||
| 
 | ||||
| 	newObject, err := newObjectLayer(globalEndpoints) | ||||
| 	if err != nil { | ||||
| 		// Stop watching for any certificate changes. | ||||
| 		globalTLSCerts.Stop() | ||||
| 
 | ||||
| 		globalHTTPServer.Shutdown() | ||||
| 		logger.FatalIf(err, "Unable to initialize backend") | ||||
| 	} | ||||
|  | ||||
| @ -45,6 +45,9 @@ func handleSignals() { | ||||
| 			globalNotificationSys.RemoveAllRemoteTargets() | ||||
| 		} | ||||
| 
 | ||||
| 		// Stop watching for any certificate changes. | ||||
| 		globalTLSCerts.Stop() | ||||
| 
 | ||||
| 		err = globalHTTPServer.Shutdown() | ||||
| 		logger.LogIf(context.Background(), err) | ||||
| 
 | ||||
| @ -76,13 +79,11 @@ func handleSignals() { | ||||
| 				// Ignore this at the moment. | ||||
| 			case serviceRestart: | ||||
| 				logger.Info("Restarting on service signal") | ||||
| 				err := globalHTTPServer.Shutdown() | ||||
| 				logger.LogIf(context.Background(), err) | ||||
| 				stopHTTPTrace() | ||||
| 				stop := stopProcess() | ||||
| 				rerr := restartProcess() | ||||
| 				logger.LogIf(context.Background(), rerr) | ||||
| 
 | ||||
| 				exit(err == nil && rerr == nil) | ||||
| 				exit(stop && rerr == nil) | ||||
| 			case serviceStop: | ||||
| 				logger.Info("Stopping on service signal") | ||||
| 				stopHTTPTrace() | ||||
|  | ||||
							
								
								
									
										127
									
								
								pkg/certs/certs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								pkg/certs/certs.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 Minio, Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package certs | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/rjeczalik/notify" | ||||
| ) | ||||
| 
 | ||||
| // A Certs represents a certificate manager able to watch certificate | ||||
| // and key pairs for changes. | ||||
| type Certs struct { | ||||
| 	sync.RWMutex | ||||
| 	// user input params. | ||||
| 	certFile string | ||||
| 	keyFile  string | ||||
| 	loadCert LoadX509KeyPairFunc | ||||
| 
 | ||||
| 	// points to the latest certificate. | ||||
| 	cert tls.Certificate | ||||
| 
 | ||||
| 	// internal param to track for events, also | ||||
| 	// used to close the watcher. | ||||
| 	e chan notify.EventInfo | ||||
| } | ||||
| 
 | ||||
| // LoadX509KeyPairFunc - provides a type for custom cert loader function. | ||||
| type LoadX509KeyPairFunc func(certFile, keyFile string) (tls.Certificate, error) | ||||
| 
 | ||||
| // New initializes a new certs monitor. | ||||
| func New(certFile, keyFile string, loadCert LoadX509KeyPairFunc) (*Certs, error) { | ||||
| 	c := &Certs{ | ||||
| 		certFile: certFile, | ||||
| 		keyFile:  keyFile, | ||||
| 		loadCert: loadCert, | ||||
| 		// Make the channel buffered to ensure no event is dropped. Notify will drop | ||||
| 		// an event if the receiver is not able to keep up the sending pace. | ||||
| 		e: make(chan notify.EventInfo, 1), | ||||
| 	} | ||||
| 
 | ||||
| 	if err := c.watch(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return c, nil | ||||
| } | ||||
| 
 | ||||
| // watch starts watching for changes to the certificate | ||||
| // and key files. On any change the certificate and key | ||||
| // are reloaded. If there is an issue the loading will fail | ||||
| // and the old (if any) certificates and keys will continue | ||||
| // to be used. | ||||
| func (c *Certs) watch() (err error) { | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			// Stop any watches previously setup after an error. | ||||
| 			notify.Stop(c.e) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	if err = notify.Watch(c.certFile, c.e, eventWrite...); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if err = notify.Watch(c.keyFile, c.e, eventWrite...); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	c.Lock() | ||||
| 	c.cert, err = c.loadCert(c.certFile, c.keyFile) | ||||
| 	c.Unlock() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	go c.run() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *Certs) run() { | ||||
| 	for event := range c.e { | ||||
| 		if isWriteEvent(event.Event()) { | ||||
| 			cert, err := c.loadCert(c.certFile, c.keyFile) | ||||
| 			if err != nil { | ||||
| 				// ignore the error continue to use | ||||
| 				// old certificates. | ||||
| 				continue | ||||
| 			} | ||||
| 			c.Lock() | ||||
| 			c.cert = cert | ||||
| 			c.Unlock() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // GetCertificateFunc provides a GetCertificate type for custom client implementations. | ||||
| type GetCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) | ||||
| 
 | ||||
| // GetCertificate returns the loaded certificate for use by | ||||
| // the TLSConfig fields GetCertificate field in a http.Server. | ||||
| func (c *Certs) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
| 	return &c.cert, nil | ||||
| } | ||||
| 
 | ||||
| // Stop tells loader to stop watching for changes to the | ||||
| // certificate and key files. | ||||
| func (c *Certs) Stop() { | ||||
| 	if c != nil { | ||||
| 		notify.Stop(c.e) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										124
									
								
								pkg/certs/certs_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								pkg/certs/certs_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 Minio, Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package certs_test | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/minio/minio/pkg/certs" | ||||
| ) | ||||
| 
 | ||||
| func updateCerts(crt, key string) { | ||||
| 	// ignore error handling | ||||
| 	crtSource, _ := os.Open(crt) | ||||
| 	defer crtSource.Close() | ||||
| 	crtDest, _ := os.Create("server.crt") | ||||
| 	defer crtDest.Close() | ||||
| 	io.Copy(crtDest, crtSource) | ||||
| 
 | ||||
| 	keySource, _ := os.Open(key) | ||||
| 	defer keySource.Close() | ||||
| 	keyDest, _ := os.Create("server.key") | ||||
| 	defer keyDest.Close() | ||||
| 	io.Copy(keyDest, keySource) | ||||
| } | ||||
| 
 | ||||
| func TestCertNew(t *testing.T) { | ||||
| 	c, err := certs.New("server.crt", "server.key", tls.LoadX509KeyPair) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer c.Stop() | ||||
| 	hello := &tls.ClientHelloInfo{} | ||||
| 	gcert, err := c.GetCertificate(hello) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	expectedCert, err := tls.LoadX509KeyPair("server.crt", "server.key") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) { | ||||
| 		t.Error("certificate doesn't match expected certificate") | ||||
| 	} | ||||
| 	c, err = certs.New("server.crt", "server2.key", tls.LoadX509KeyPair) | ||||
| 	if err == nil { | ||||
| 		t.Fatal("Expected to fail but got success") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestValidPairAfterWrite(t *testing.T) { | ||||
| 	expectedCert, err := tls.LoadX509KeyPair("server2.crt", "server2.key") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	c, err := certs.New("server.crt", "server.key", tls.LoadX509KeyPair) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer c.Stop() | ||||
| 
 | ||||
| 	updateCerts("server2.crt", "server2.key") | ||||
| 	defer updateCerts("server1.crt", "server1.key") | ||||
| 
 | ||||
| 	// Wait for the write event.. | ||||
| 	time.Sleep(200 * time.Millisecond) | ||||
| 
 | ||||
| 	hello := &tls.ClientHelloInfo{} | ||||
| 	gcert, err := c.GetCertificate(hello) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) { | ||||
| 		t.Error("certificate doesn't match expected certificate") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestStop(t *testing.T) { | ||||
| 	expectedCert, err := tls.LoadX509KeyPair("server2.crt", "server2.key") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	c, err := certs.New("server.crt", "server.key", tls.LoadX509KeyPair) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	c.Stop() | ||||
| 
 | ||||
| 	// No one is listening on the event, will be ignored and | ||||
| 	// certificate will not be reloaded. | ||||
| 	updateCerts("server2.crt", "server2.key") | ||||
| 	defer updateCerts("server1.crt", "server1.key") | ||||
| 
 | ||||
| 	hello := &tls.ClientHelloInfo{} | ||||
| 	gcert, err := c.GetCertificate(hello) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) { | ||||
| 		t.Error("certificate shouldn't match, but matched") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										31
									
								
								pkg/certs/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								pkg/certs/event.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 Minio, Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package certs | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/rjeczalik/notify" | ||||
| ) | ||||
| 
 | ||||
| // isWriteEvent checks if the event returned is a write event | ||||
| func isWriteEvent(event notify.Event) bool { | ||||
| 	for _, ev := range eventWrite { | ||||
| 		if event&ev != 0 { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										26
									
								
								pkg/certs/event_linux.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								pkg/certs/event_linux.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| // +build linux | ||||
| 
 | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 Minio, Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package certs | ||||
| 
 | ||||
| import "github.com/rjeczalik/notify" | ||||
| 
 | ||||
| var ( | ||||
| 	// eventWrite contains the notify events that will cause a write | ||||
| 	eventWrite = []notify.Event{notify.InCloseWrite} | ||||
| ) | ||||
							
								
								
									
										26
									
								
								pkg/certs/event_others.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								pkg/certs/event_others.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| // +build !linux | ||||
| 
 | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 Minio, Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package certs | ||||
| 
 | ||||
| import "github.com/rjeczalik/notify" | ||||
| 
 | ||||
| var ( | ||||
| 	// eventWrite contains the notify events that will cause a write | ||||
| 	eventWrite = []notify.Event{notify.Create, notify.Write} | ||||
| ) | ||||
							
								
								
									
										22
									
								
								pkg/certs/server.crt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								pkg/certs/server.crt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIDqjCCApKgAwIBAgIJAOcv4FsrflS4MA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV | ||||
| BAYTAlVTMQswCQYDVQQIDAJDQTEVMBMGA1UEBwwMUmVkd29vZCBDaXR5MQ4wDAYD | ||||
| VQQKDAVNaW5pbzEUMBIGA1UECwwLRW5naW5lZXJpbmcxETAPBgNVBAMMCG1pbmlv | ||||
| LmlvMB4XDTE4MDUyMDA4NDc0MFoXDTE5MDUyMDA4NDc0MFowajELMAkGA1UEBhMC | ||||
| VVMxCzAJBgNVBAgMAkNBMRUwEwYDVQQHDAxSZWR3b29kIENpdHkxDjAMBgNVBAoM | ||||
| BU1pbmlvMRQwEgYDVQQLDAtFbmdpbmVlcmluZzERMA8GA1UEAwwIbWluaW8uaW8w | ||||
| ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPszxaYwn+mIz6IGuUlmvW | ||||
| wUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G+Q1IezxX | ||||
| +VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8fcQyT0TV | ||||
| apCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwKnoYnpda2 | ||||
| d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+fQG8QdDrz | ||||
| WQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5erEz776WCF | ||||
| AgMBAAGjUzBRMB0GA1UdDgQWBBRzC09a+3AlbFDg6BsvELolmO8jYjAfBgNVHSME | ||||
| GDAWgBRzC09a+3AlbFDg6BsvELolmO8jYjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG | ||||
| SIb3DQEBCwUAA4IBAQBl0cx7qbidKjhoZ1Iv4pCD8xHZgtuWEDApPoGuMtVS66jJ | ||||
| +oj0ncD5xCtv9XqXtshE65FIsEWnDOIwa+kyjMnxHbFwxveWBT4W0twtqwbVs7NE | ||||
| I0So6cEmSx4+rB0XorY6mIbD3O9YAStelNhB1jVfQfIMSByYkcGq2Fh+B1LHlOrz | ||||
| 06LJdwYMiILzK0c5fvjZvsDq/9EK+Xo66hphKjs5cl1t9WK7wKOCoZDt2lOTZqEq | ||||
| UWYGPWlTAxSWQxO4WnvSKqFdsRi8fOO3KlDq1eNqeDSGGCI0DTGgJxidHIpfOPEF | ||||
| s/zojgc5npE32/1n8og6gLcv7LIKelBfMhUrFTp7 | ||||
| -----END CERTIFICATE----- | ||||
							
								
								
									
										28
									
								
								pkg/certs/server.key
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								pkg/certs/server.key
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| -----BEGIN PRIVATE KEY----- | ||||
| MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPszxaYwn+mIz6 | ||||
| IGuUlmvWwUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G | ||||
| +Q1IezxX+VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8 | ||||
| fcQyT0TVapCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwK | ||||
| noYnpda2d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+f | ||||
| QG8QdDrzWQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5er | ||||
| Ez776WCFAgMBAAECggEBAJcHRyCWmcLm3MRY5MF0K9BKV9R3NnBdTuQ8OPdE2Ui3 | ||||
| w6gcRuBi+eK/TrU3CAIqUXsEW5Hq1mQuXfwAh5cn/XYfG/QXx91eKBCdOTIgqY/6 | ||||
| pODsmVkRhg0c2rl6eWYd4m6BNHsjhm8WWx9C+HJ4z528UpV1n2dUElkvbMHD+aKp | ||||
| Ndwd0W+0PCn/BjMn/sdyy01f8sfaK2Zoy7HBw/fGeBDNLFFj3Iz7BqXYeS+OyfLN | ||||
| B4xD5I5fFqt1iJeyqVPzGkOAYSqisijbM1GtZJCeVp37/+IDylCKTO3l8Xd8x73U | ||||
| qTYcYT3heSHyUC2xCM6Va2YkSrOHeqbq91QgHh9LVrUCgYEA9t/wE2S8TE2l1IG9 | ||||
| 68SXdhyaXTnB2qSL7ggY0uazPzBNLQpNMOxicZ6/4QGEi3hSuCqGxxGo9UEoTsVd | ||||
| pk8oIeDULdPVi4NQxSmkxUyArs/dzOMygUPyosOiEc8z6jWFFKDcQ7mnZnay8dZ4 | ||||
| e4j+/hZDONtDrJ+zH2xu98ZrJPcCgYEA12CbSRbCkTiRj/dq8Qvgp6+ceTVcAbnk | ||||
| MWpAhZQaXHrG3XP0L7QTIHG/7a09Mln92zjuAFXDp/Vc5NdxeXcnj9j6oUAxq+0I | ||||
| dq+vibzjROemmvnmQvXGY9tc0ns6u7GjM0+Sicmas+IH4vuum/aRasABfVe2XBwe | ||||
| 4fVs0n7yU2MCgYA7KevFGg0uVCV7yiQTzqdlvPEZim/00B5gyzv3vyYR7KdyNdfN | ||||
| 87ib9imR6OU0738Td82ZA5h0PktEpXQOGUZK6DCxUuUIbE39Ej/UsMLeIh7LrV87 | ||||
| L2eErlG25utQI8di7DIdYO7HVYcJAhcZs/k4N2mgxJtxUUyCKWBmrPycfQKBgAo7 | ||||
| 0uUUKcaQs4ntra0qbVBKbdrsiCSk2ozmiY5PTTlbtBtNqSqjGc2O2hnHA4Ni90b1 | ||||
| W4m0iYlvhSxyeDfXS4/wNWh4DmQm7SIGkwaubPYXM7llamWAHB8eiziNFmtYs3J6 | ||||
| s3HMnIczlEBayR8sBhjWaruz8TxLMcR2zubplUYVAoGBAItxeC9IT8BGJoZB++qM | ||||
| f2LXCqJ383x0sDHhwPMFPtwUTzAwc5BJgQe9zFktW5CBxsER+MnUZjlrarT1HQfH | ||||
| 1Y1mJQXtwuBKG4pPPZphH0yoVlYcWkBTMw/KmlVlwRclEzRQwV3TPD+i6ieKeZhz | ||||
| 9eZwhS3H+Zb/693WbBDyH8L+ | ||||
| -----END PRIVATE KEY----- | ||||
							
								
								
									
										22
									
								
								pkg/certs/server1.crt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								pkg/certs/server1.crt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIDqjCCApKgAwIBAgIJAOcv4FsrflS4MA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV | ||||
| BAYTAlVTMQswCQYDVQQIDAJDQTEVMBMGA1UEBwwMUmVkd29vZCBDaXR5MQ4wDAYD | ||||
| VQQKDAVNaW5pbzEUMBIGA1UECwwLRW5naW5lZXJpbmcxETAPBgNVBAMMCG1pbmlv | ||||
| LmlvMB4XDTE4MDUyMDA4NDc0MFoXDTE5MDUyMDA4NDc0MFowajELMAkGA1UEBhMC | ||||
| VVMxCzAJBgNVBAgMAkNBMRUwEwYDVQQHDAxSZWR3b29kIENpdHkxDjAMBgNVBAoM | ||||
| BU1pbmlvMRQwEgYDVQQLDAtFbmdpbmVlcmluZzERMA8GA1UEAwwIbWluaW8uaW8w | ||||
| ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPszxaYwn+mIz6IGuUlmvW | ||||
| wUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G+Q1IezxX | ||||
| +VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8fcQyT0TV | ||||
| apCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwKnoYnpda2 | ||||
| d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+fQG8QdDrz | ||||
| WQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5erEz776WCF | ||||
| AgMBAAGjUzBRMB0GA1UdDgQWBBRzC09a+3AlbFDg6BsvELolmO8jYjAfBgNVHSME | ||||
| GDAWgBRzC09a+3AlbFDg6BsvELolmO8jYjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG | ||||
| SIb3DQEBCwUAA4IBAQBl0cx7qbidKjhoZ1Iv4pCD8xHZgtuWEDApPoGuMtVS66jJ | ||||
| +oj0ncD5xCtv9XqXtshE65FIsEWnDOIwa+kyjMnxHbFwxveWBT4W0twtqwbVs7NE | ||||
| I0So6cEmSx4+rB0XorY6mIbD3O9YAStelNhB1jVfQfIMSByYkcGq2Fh+B1LHlOrz | ||||
| 06LJdwYMiILzK0c5fvjZvsDq/9EK+Xo66hphKjs5cl1t9WK7wKOCoZDt2lOTZqEq | ||||
| UWYGPWlTAxSWQxO4WnvSKqFdsRi8fOO3KlDq1eNqeDSGGCI0DTGgJxidHIpfOPEF | ||||
| s/zojgc5npE32/1n8og6gLcv7LIKelBfMhUrFTp7 | ||||
| -----END CERTIFICATE----- | ||||
							
								
								
									
										28
									
								
								pkg/certs/server1.key
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								pkg/certs/server1.key
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| -----BEGIN PRIVATE KEY----- | ||||
| MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPszxaYwn+mIz6 | ||||
| IGuUlmvWwUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G | ||||
| +Q1IezxX+VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8 | ||||
| fcQyT0TVapCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwK | ||||
| noYnpda2d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+f | ||||
| QG8QdDrzWQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5er | ||||
| Ez776WCFAgMBAAECggEBAJcHRyCWmcLm3MRY5MF0K9BKV9R3NnBdTuQ8OPdE2Ui3 | ||||
| w6gcRuBi+eK/TrU3CAIqUXsEW5Hq1mQuXfwAh5cn/XYfG/QXx91eKBCdOTIgqY/6 | ||||
| pODsmVkRhg0c2rl6eWYd4m6BNHsjhm8WWx9C+HJ4z528UpV1n2dUElkvbMHD+aKp | ||||
| Ndwd0W+0PCn/BjMn/sdyy01f8sfaK2Zoy7HBw/fGeBDNLFFj3Iz7BqXYeS+OyfLN | ||||
| B4xD5I5fFqt1iJeyqVPzGkOAYSqisijbM1GtZJCeVp37/+IDylCKTO3l8Xd8x73U | ||||
| qTYcYT3heSHyUC2xCM6Va2YkSrOHeqbq91QgHh9LVrUCgYEA9t/wE2S8TE2l1IG9 | ||||
| 68SXdhyaXTnB2qSL7ggY0uazPzBNLQpNMOxicZ6/4QGEi3hSuCqGxxGo9UEoTsVd | ||||
| pk8oIeDULdPVi4NQxSmkxUyArs/dzOMygUPyosOiEc8z6jWFFKDcQ7mnZnay8dZ4 | ||||
| e4j+/hZDONtDrJ+zH2xu98ZrJPcCgYEA12CbSRbCkTiRj/dq8Qvgp6+ceTVcAbnk | ||||
| MWpAhZQaXHrG3XP0L7QTIHG/7a09Mln92zjuAFXDp/Vc5NdxeXcnj9j6oUAxq+0I | ||||
| dq+vibzjROemmvnmQvXGY9tc0ns6u7GjM0+Sicmas+IH4vuum/aRasABfVe2XBwe | ||||
| 4fVs0n7yU2MCgYA7KevFGg0uVCV7yiQTzqdlvPEZim/00B5gyzv3vyYR7KdyNdfN | ||||
| 87ib9imR6OU0738Td82ZA5h0PktEpXQOGUZK6DCxUuUIbE39Ej/UsMLeIh7LrV87 | ||||
| L2eErlG25utQI8di7DIdYO7HVYcJAhcZs/k4N2mgxJtxUUyCKWBmrPycfQKBgAo7 | ||||
| 0uUUKcaQs4ntra0qbVBKbdrsiCSk2ozmiY5PTTlbtBtNqSqjGc2O2hnHA4Ni90b1 | ||||
| W4m0iYlvhSxyeDfXS4/wNWh4DmQm7SIGkwaubPYXM7llamWAHB8eiziNFmtYs3J6 | ||||
| s3HMnIczlEBayR8sBhjWaruz8TxLMcR2zubplUYVAoGBAItxeC9IT8BGJoZB++qM | ||||
| f2LXCqJ383x0sDHhwPMFPtwUTzAwc5BJgQe9zFktW5CBxsER+MnUZjlrarT1HQfH | ||||
| 1Y1mJQXtwuBKG4pPPZphH0yoVlYcWkBTMw/KmlVlwRclEzRQwV3TPD+i6ieKeZhz | ||||
| 9eZwhS3H+Zb/693WbBDyH8L+ | ||||
| -----END PRIVATE KEY----- | ||||
							
								
								
									
										21
									
								
								pkg/certs/server2.crt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								pkg/certs/server2.crt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIDYDCCAkigAwIBAgIJALIHkFXjtZ2yMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV | ||||
| BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX | ||||
| aWRnaXRzIFB0eSBMdGQwHhcNMTgwNTIwMDg1MzI3WhcNMTkwNTIwMDg1MzI3WjBF | ||||
| MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 | ||||
| ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB | ||||
| CgKCAQEA+LZ8+eqDHyoCt7HGQIhZK+ZagDxXzJ67a2V88s/rHB3zhi1d6ha6q5sc | ||||
| ljmCNqj250fjSWpDQ4hssfqyNDmY/IUaphnT9eMBPZX6RXZVFXGtpUUFvGik5hed | ||||
| 2g7j5Jhy+luz5QHn9zR6E7rkqTPl3WJZ2fe4LEfij6/bzZ2CMUFrKyt/uqn4laTl | ||||
| m4DO+wjoOUGAHmaHbkpkhYTb/qbWzV0qMh0Zy4gQuFYcBVbATcdAjV4bRNkHd0CL | ||||
| Ekd3A9ae5ZaeOrg2HkPVcinxg1ln5jBe2LBqDFqKkWudzm6jeNw+oE4lKKxDfHH8 | ||||
| AD08N8qFbfs1YxZAjL3wKpcYVw2pzQIDAQABo1MwUTAdBgNVHQ4EFgQU2Yywgv8p | ||||
| WfyZxYVx+MnH+VQ5TTUwHwYDVR0jBBgwFoAU2Yywgv8pWfyZxYVx+MnH+VQ5TTUw | ||||
| DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA2maF7DQ7CMpCho9B | ||||
| 9gjGxvt8HqY1pCyuQwcSPb4PTyoKUZ/ZuIDhVOaBX+ox1RzlfGtYs2BUM63/QUDs | ||||
| dP0GO7/IL/XEqJi1flrFvM7LNSs89qAbPJ440m6jJDzsuL2VeyUX/M72IEsBK2uS | ||||
| ajtS1+HFQjPMvt7wR6fDPCP7wHPOrkTN4hcHlgzVJShKUnFaHtb2lOnWaoM/Sk91 | ||||
| IsiyAhKRuCM9et7/bnOj7G8448QDVtQNniT8V/HpqQ7ltSuIGvs3QYTLDTege/74 | ||||
| Q8Ph1oH7shyRE/PqPfyIuLq3p0N9Sah3oRMHLohYjJL0zAGt0jxSsnhrBSNUUD/v | ||||
| bAd5VQ== | ||||
| -----END CERTIFICATE----- | ||||
							
								
								
									
										28
									
								
								pkg/certs/server2.key
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								pkg/certs/server2.key
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| -----BEGIN PRIVATE KEY----- | ||||
| MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4tnz56oMfKgK3 | ||||
| scZAiFkr5lqAPFfMnrtrZXzyz+scHfOGLV3qFrqrmxyWOYI2qPbnR+NJakNDiGyx | ||||
| +rI0OZj8hRqmGdP14wE9lfpFdlUVca2lRQW8aKTmF53aDuPkmHL6W7PlAef3NHoT | ||||
| uuSpM+XdYlnZ97gsR+KPr9vNnYIxQWsrK3+6qfiVpOWbgM77COg5QYAeZoduSmSF | ||||
| hNv+ptbNXSoyHRnLiBC4VhwFVsBNx0CNXhtE2Qd3QIsSR3cD1p7llp46uDYeQ9Vy | ||||
| KfGDWWfmMF7YsGoMWoqRa53ObqN43D6gTiUorEN8cfwAPTw3yoVt+zVjFkCMvfAq | ||||
| lxhXDanNAgMBAAECggEBAIGAI5rNbPCxIzEas6uuUx/0lXLn+J9mlxfYhDK56CV/ | ||||
| wuk+fgQBSblIzp252/8yAz1xxPrZBaUIR/B0JI3k36+8bp/GGwOQ63hxuxqn/q1n | ||||
| v46qXc44foQAEAUWc7r3Vgbd8NFxKKMjA916Fs2zZCDdsQM5ZQBJfcJrQvvQ45VY | ||||
| //UtXdNeIBQOb5Wg4o9fHJolKzCHWRaD2ExoIHZ5Fa6JpBmk9JBHcUbrHrlbOeep | ||||
| /SkbSa0ma9j3k3jqV970XRoQUCJf+K1Li49jmaYPPGXBUAp6AfU+yiAJ1aups38m | ||||
| BClLAV9g6vgE3xK2xozGPI1+j9lkruYbvGbPNkXexdECgYEA/47XnKITSnxtV+NK | ||||
| nDbWNOgpeaRbxAdjp1P0b4VI0S0SuRvKUOCp1UlPg5BjGL0JLPQpGlPzEfLlGWAa | ||||
| 68vhyj0V6HL2+PAJNib1eu6yyRBsSbPdrAD5nydHpbxRcdShhVwb2MHMyBeYH5Al | ||||
| kL+ed5wCF32kXOOGzhoGzJEKNEcCgYEA+SSdcdbuVpQFkAecIoABwdx/qeOAeS19 | ||||
| FsvVSTmWlhal8m2Mn8RWZ0IKXT9AoZJ0KQBIKHViPtyV7UQey05uRgLRHZapHpe8 | ||||
| dhm6SsGYtU3BhLdHJBP0kI79qm2kzqsHp6ghSzaxT9CkRfMniN+TD+w8p7lrOaxv | ||||
| vV46UHoGX0sCgYB4LlCvVHkF+mXhgv4/YHpz/woiLm0JTwBKXG0DVQbdd/jqHGuU | ||||
| hVLY/tTp5ij0JVH/VgNOYlRZCIU83blLUmIonXmECyyh/SAX21JuMXram2KRdoi0 | ||||
| rvC1K9/BzUHv6jLbaGmgEeOf5Zign0VLQRHg5fkF2wxEsqtemVbBNSQ7WQKBgBFk | ||||
| Y/VRervig2zlixnBc93zpZnXft12tnfD7PS6p298z0LYMOvqSdnVe2G9C6b70U4X | ||||
| bfIdF6mpvnGcwsWQiRQsGCsHnHC9SPO5og6b6ywk7HB2VuoG1pjM0pp2Iv4mZFdo | ||||
| 3kIg5EndF8qmSck9SkffRvCyefDBv98pV8rMaet3AoGBALjlN2hLoNE5Cs5vTYH8 | ||||
| W0AN4lEOaTlBRKG8a1h7Fm2vPgzGGkiwU6bVzsh0oTfytc8v8MW9lNQZpE3dBKne | ||||
| ms3FrNsnBbTczX+xJmndRnVRocdyON6u476VxAuz/dHSFFnZGXX+2lJse9xnWHUz | ||||
| OpSHUPq3TrUzhgZClE2ZKpNm | ||||
| -----END PRIVATE KEY----- | ||||
							
								
								
									
										10
									
								
								vendor/github.com/rjeczalik/notify/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/rjeczalik/notify/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| # List of individuals who contributed to the Notify package. | ||||
| # | ||||
| # The up-to-date list of the authors one may obtain with: | ||||
| # | ||||
| #   ~ $ git shortlog -es | cut -f2 | rev | uniq -f1 | rev | ||||
| # | ||||
| 
 | ||||
| Pawel Blaszczyk <blaszczykpb@gmail.com> | ||||
| Pawel Knap      <pawelknap88@gmail.com> | ||||
| Rafal Jeczalik  <rjeczalik@gmail.com> | ||||
							
								
								
									
										21
									
								
								vendor/github.com/rjeczalik/notify/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/rjeczalik/notify/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| The MIT License (MIT) | ||||
| 
 | ||||
| Copyright (c) 2014-2015 The Notify Authors | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										22
									
								
								vendor/github.com/rjeczalik/notify/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/rjeczalik/notify/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| notify [](https://godoc.org/github.com/rjeczalik/notify) [](https://travis-ci.org/rjeczalik/notify "inotify + FSEvents + kqueue") [](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [](https://coveralls.io/r/rjeczalik/notify?branch=master) | ||||
| ====== | ||||
| 
 | ||||
| Filesystem event notification library on steroids. (under active development) | ||||
| 
 | ||||
| *Documentation* | ||||
| 
 | ||||
| [godoc.org/github.com/rjeczalik/notify](https://godoc.org/github.com/rjeczalik/notify) | ||||
| 
 | ||||
| *Installation* | ||||
| 
 | ||||
| ``` | ||||
| ~ $ go get -u github.com/rjeczalik/notify | ||||
| ``` | ||||
| 
 | ||||
| *Projects using notify* | ||||
| 
 | ||||
| - [github.com/rjeczalik/cmd/notify](https://godoc.org/github.com/rjeczalik/cmd/notify) | ||||
| - [github.com/cortesi/devd](https://github.com/cortesi/devd) | ||||
| - [github.com/cortesi/modd](https://github.com/cortesi/modd) | ||||
| - [github.com/syncthing/syncthing-inotify](https://github.com/syncthing/syncthing-inotify) | ||||
| - [github.com/OrlovEvgeny/TinyJPG](https://github.com/OrlovEvgeny/TinyJPG) | ||||
							
								
								
									
										23
									
								
								vendor/github.com/rjeczalik/notify/appveyor.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/rjeczalik/notify/appveyor.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| version: "{build}" | ||||
| 
 | ||||
| os: Windows Server 2012 R2 | ||||
| 
 | ||||
| clone_folder: c:\projects\src\github.com\rjeczalik\notify | ||||
| 
 | ||||
| environment: | ||||
|  PATH: c:\projects\bin;%PATH% | ||||
|  GOPATH: c:\projects | ||||
|  NOTIFY_TIMEOUT: 5s | ||||
| 
 | ||||
| install: | ||||
|  - go version | ||||
|  - go get -v -t ./... | ||||
| 
 | ||||
| build_script: | ||||
|  - go tool vet -all . | ||||
|  - go build ./... | ||||
|  - go test -v -timeout 60s -race ./... | ||||
| 
 | ||||
| test: off | ||||
| 
 | ||||
| deploy: off | ||||
							
								
								
									
										53
									
								
								vendor/github.com/rjeczalik/notify/debug.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								vendor/github.com/rjeczalik/notify/debug.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| var dbgprint func(...interface{}) | ||||
| 
 | ||||
| var dbgprintf func(string, ...interface{}) | ||||
| 
 | ||||
| var dbgcallstack func(max int) []string | ||||
| 
 | ||||
| func init() { | ||||
| 	if _, ok := os.LookupEnv("NOTIFY_DEBUG"); ok || debugTag { | ||||
| 		log.SetOutput(os.Stdout) | ||||
| 		log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) | ||||
| 		dbgprint = func(v ...interface{}) { | ||||
| 			v = append([]interface{}{"[D] "}, v...) | ||||
| 			log.Println(v...) | ||||
| 		} | ||||
| 		dbgprintf = func(format string, v ...interface{}) { | ||||
| 			format = "[D] " + format | ||||
| 			log.Printf(format, v...) | ||||
| 		} | ||||
| 		dbgcallstack = func(max int) []string { | ||||
| 			pc, stack := make([]uintptr, max), make([]string, 0, max) | ||||
| 			runtime.Callers(2, pc) | ||||
| 			for _, pc := range pc { | ||||
| 				if f := runtime.FuncForPC(pc); f != nil { | ||||
| 					fname := f.Name() | ||||
| 					idx := strings.LastIndex(fname, string(os.PathSeparator)) | ||||
| 					if idx != -1 { | ||||
| 						stack = append(stack, fname[idx+1:]) | ||||
| 					} else { | ||||
| 						stack = append(stack, fname) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			return stack | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	dbgprint = func(v ...interface{}) {} | ||||
| 	dbgprintf = func(format string, v ...interface{}) {} | ||||
| 	dbgcallstack = func(max int) []string { return nil } | ||||
| } | ||||
							
								
								
									
										9
									
								
								vendor/github.com/rjeczalik/notify/debug_debug.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/rjeczalik/notify/debug_debug.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build debug | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| var debugTag bool = true | ||||
							
								
								
									
										9
									
								
								vendor/github.com/rjeczalik/notify/debug_nodebug.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/rjeczalik/notify/debug_nodebug.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build !debug | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| var debugTag bool = false | ||||
							
								
								
									
										43
									
								
								vendor/github.com/rjeczalik/notify/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/rjeczalik/notify/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // Package notify implements access to filesystem events. | ||||
| // | ||||
| // Notify is a high-level abstraction over filesystem watchers like inotify, | ||||
| // kqueue, FSEvents, FEN or ReadDirectoryChangesW. Watcher implementations are | ||||
| // split into two groups: ones that natively support recursive notifications | ||||
| // (FSEvents and ReadDirectoryChangesW) and ones that do not (inotify, kqueue, FEN). | ||||
| // For more details see watcher and recursiveWatcher interfaces in watcher.go | ||||
| // source file. | ||||
| // | ||||
| // On top of filesystem watchers notify maintains a watchpoint tree, which provides | ||||
| // a strategy for creating and closing filesystem watches and dispatching filesystem | ||||
| // events to user channels. | ||||
| // | ||||
| // An event set is just an event list joint using bitwise OR operator | ||||
| // into a single event value. | ||||
| // Both the platform-independent (see Constants) and specific events can be used. | ||||
| // Refer to the event_*.go source files for information about the available | ||||
| // events. | ||||
| // | ||||
| // A filesystem watch or just a watch is platform-specific entity which represents | ||||
| // a single path registered for notifications for specific event set. Setting a watch | ||||
| // means using platform-specific API calls for creating / initializing said watch. | ||||
| // For each watcher the API call is: | ||||
| // | ||||
| //   - FSEvents: FSEventStreamCreate | ||||
| //   - inotify:  notify_add_watch | ||||
| //   - kqueue:   kevent | ||||
| //   - ReadDirectoryChangesW: CreateFile+ReadDirectoryChangesW | ||||
| //   - FEN:      port_get | ||||
| // | ||||
| // To rewatch means to either shrink or expand an event set that was previously | ||||
| // registered during watch operation for particular filesystem watch. | ||||
| // | ||||
| // A watchpoint is a list of user channel and event set pairs for particular | ||||
| // path (watchpoint tree's node). A single watchpoint can contain multiple | ||||
| // different user channels registered to listen for one or more events. A single | ||||
| // user channel can be registered in one or more watchpoints, recursive and | ||||
| // non-recursive ones as well. | ||||
| package notify | ||||
							
								
								
									
										143
									
								
								vendor/github.com/rjeczalik/notify/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								vendor/github.com/rjeczalik/notify/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,143 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // Event represents the type of filesystem action. | ||||
| // | ||||
| // Number of available event values is dependent on the target system or the | ||||
| // watcher implmenetation used (e.g. it's possible to use either kqueue or | ||||
| // FSEvents on Darwin). | ||||
| // | ||||
| // Please consult documentation for your target platform to see list of all | ||||
| // available events. | ||||
| type Event uint32 | ||||
| 
 | ||||
| // Create, Remove, Write and Rename are the only event values guaranteed to be | ||||
| // present on all platforms. | ||||
| const ( | ||||
| 	Create = osSpecificCreate | ||||
| 	Remove = osSpecificRemove | ||||
| 	Write  = osSpecificWrite | ||||
| 	Rename = osSpecificRename | ||||
| 
 | ||||
| 	// All is handful alias for all platform-independent event values. | ||||
| 	All = Create | Remove | Write | Rename | ||||
| ) | ||||
| 
 | ||||
| const internal = recursive | omit | ||||
| 
 | ||||
| // String implements fmt.Stringer interface. | ||||
| func (e Event) String() string { | ||||
| 	var s []string | ||||
| 	for _, strmap := range []map[Event]string{estr, osestr} { | ||||
| 		for ev, str := range strmap { | ||||
| 			if e&ev == ev { | ||||
| 				s = append(s, str) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return strings.Join(s, "|") | ||||
| } | ||||
| 
 | ||||
| // EventInfo describes an event reported by the underlying filesystem notification | ||||
| // subsystem. | ||||
| // | ||||
| // It always describes single event, even if the OS reported a coalesced action. | ||||
| // Reported path is absolute and clean. | ||||
| // | ||||
| // For non-recursive watchpoints its base is always equal to the path passed | ||||
| // to corresponding Watch call. | ||||
| // | ||||
| // The value of Sys if system-dependent and can be nil. | ||||
| // | ||||
| // Sys | ||||
| // | ||||
| // Under Darwin (FSEvents) Sys() always returns a non-nil *notify.FSEvent value, | ||||
| // which is defined as: | ||||
| // | ||||
| //   type FSEvent struct { | ||||
| //       Path  string // real path of the file or directory | ||||
| //       ID    uint64 // ID of the event (FSEventStreamEventId) | ||||
| //       Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags) | ||||
| //   } | ||||
| // | ||||
| // For possible values of Flags see Darwin godoc for notify or FSEvents | ||||
| // documentation for FSEventStreamEventFlags constants: | ||||
| // | ||||
| //    https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags | ||||
| // | ||||
| // Under Linux (inotify) Sys() always returns a non-nil *unix.InotifyEvent | ||||
| // value, defined as: | ||||
| // | ||||
| //   type InotifyEvent struct { | ||||
| //       Wd     int32    // Watch descriptor | ||||
| //       Mask   uint32   // Mask describing event | ||||
| //       Cookie uint32   // Unique cookie associating related events (for rename(2)) | ||||
| //       Len    uint32   // Size of name field | ||||
| //       Name   [0]uint8 // Optional null-terminated name | ||||
| //   } | ||||
| // | ||||
| // More information about inotify masks and the usage of inotify_event structure | ||||
| // can be found at: | ||||
| // | ||||
| //    http://man7.org/linux/man-pages/man7/inotify.7.html | ||||
| // | ||||
| // Under Darwin, DragonFlyBSD, FreeBSD, NetBSD, OpenBSD (kqueue) Sys() always | ||||
| // returns a non-nil *notify.Kevent value, which is defined as: | ||||
| // | ||||
| //   type Kevent struct { | ||||
| //       Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure | ||||
| //       FI     os.FileInfo       // FI describes file/dir | ||||
| //   } | ||||
| // | ||||
| // More information about syscall.Kevent_t can be found at: | ||||
| // | ||||
| //    https://www.freebsd.org/cgi/man.cgi?query=kqueue | ||||
| // | ||||
| // Under Windows (ReadDirectoryChangesW) Sys() always returns nil. The documentation | ||||
| // of watcher's WinAPI function can be found at: | ||||
| // | ||||
| //    https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx | ||||
| type EventInfo interface { | ||||
| 	Event() Event     // event value for the filesystem action | ||||
| 	Path() string     // real path of the file or directory | ||||
| 	Sys() interface{} // underlying data source (can return nil) | ||||
| } | ||||
| 
 | ||||
| type isDirer interface { | ||||
| 	isDir() (bool, error) | ||||
| } | ||||
| 
 | ||||
| var _ fmt.Stringer = (*event)(nil) | ||||
| var _ isDirer = (*event)(nil) | ||||
| 
 | ||||
| // String implements fmt.Stringer interface. | ||||
| func (e *event) String() string { | ||||
| 	return e.Event().String() + `: "` + e.Path() + `"` | ||||
| } | ||||
| 
 | ||||
| var estr = map[Event]string{ | ||||
| 	Create: "notify.Create", | ||||
| 	Remove: "notify.Remove", | ||||
| 	Write:  "notify.Write", | ||||
| 	Rename: "notify.Rename", | ||||
| 	// Display name for recursive event is added only for debugging | ||||
| 	// purposes. It's an internal event after all and won't be exposed to the | ||||
| 	// user. Having Recursive event printable is helpful, e.g. for reading | ||||
| 	// testing failure messages: | ||||
| 	// | ||||
| 	//    --- FAIL: TestWatchpoint (0.00 seconds) | ||||
| 	//    watchpoint_test.go:64: want diff=[notify.Remove notify.Create|notify.Remove]; | ||||
| 	//    got [notify.Remove notify.Remove|notify.Create] (i=1) | ||||
| 	// | ||||
| 	// Yup, here the diff have Recursive event inside. Go figure. | ||||
| 	recursive: "recursive", | ||||
| 	omit:      "omit", | ||||
| } | ||||
							
								
								
									
										57
									
								
								vendor/github.com/rjeczalik/notify/event_fen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								vendor/github.com/rjeczalik/notify/event_fen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build solaris | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| const ( | ||||
| 	osSpecificCreate Event = 0x00000100 << iota | ||||
| 	osSpecificRemove | ||||
| 	osSpecificWrite | ||||
| 	osSpecificRename | ||||
| 	// internal | ||||
| 	// recursive is used to distinguish recursive eventsets from non-recursive ones | ||||
| 	recursive | ||||
| 	// omit is used for dispatching internal events; only those events are sent | ||||
| 	// for which both the event and the watchpoint has omit in theirs event sets. | ||||
| 	omit | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// FileAccess is an event reported when monitored file/directory was accessed. | ||||
| 	FileAccess = fileAccess | ||||
| 	// FileModified is an event reported when monitored file/directory was modified. | ||||
| 	FileModified = fileModified | ||||
| 	// FileAttrib is an event reported when monitored file/directory's ATTRIB | ||||
| 	// was changed. | ||||
| 	FileAttrib = fileAttrib | ||||
| 	// FileDelete is an event reported when monitored file/directory was deleted. | ||||
| 	FileDelete = fileDelete | ||||
| 	// FileRenameTo to is an event reported when monitored file/directory was renamed. | ||||
| 	FileRenameTo = fileRenameTo | ||||
| 	// FileRenameFrom is an event reported when monitored file/directory was renamed. | ||||
| 	FileRenameFrom = fileRenameFrom | ||||
| 	// FileTrunc is an event reported when monitored file/directory was truncated. | ||||
| 	FileTrunc = fileTrunc | ||||
| 	// FileNoFollow is an flag to indicate not to follow symbolic links. | ||||
| 	FileNoFollow = fileNoFollow | ||||
| 	// Unmounted is an event reported when monitored filesystem was unmounted. | ||||
| 	Unmounted = unmounted | ||||
| 	// MountedOver is an event reported when monitored file/directory was mounted on. | ||||
| 	MountedOver = mountedOver | ||||
| ) | ||||
| 
 | ||||
| var osestr = map[Event]string{ | ||||
| 	FileAccess:     "notify.FileAccess", | ||||
| 	FileModified:   "notify.FileModified", | ||||
| 	FileAttrib:     "notify.FileAttrib", | ||||
| 	FileDelete:     "notify.FileDelete", | ||||
| 	FileRenameTo:   "notify.FileRenameTo", | ||||
| 	FileRenameFrom: "notify.FileRenameFrom", | ||||
| 	FileTrunc:      "notify.FileTrunc", | ||||
| 	FileNoFollow:   "notify.FileNoFollow", | ||||
| 	Unmounted:      "notify.Unmounted", | ||||
| 	MountedOver:    "notify.MountedOver", | ||||
| } | ||||
							
								
								
									
										71
									
								
								vendor/github.com/rjeczalik/notify/event_fsevents.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								vendor/github.com/rjeczalik/notify/event_fsevents.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build darwin,!kqueue | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| const ( | ||||
| 	osSpecificCreate = Event(FSEventsCreated) | ||||
| 	osSpecificRemove = Event(FSEventsRemoved) | ||||
| 	osSpecificWrite  = Event(FSEventsModified) | ||||
| 	osSpecificRename = Event(FSEventsRenamed) | ||||
| 	// internal = Event(0x100000) | ||||
| 	// recursive is used to distinguish recursive eventsets from non-recursive ones | ||||
| 	recursive = Event(0x200000) | ||||
| 	// omit is used for dispatching internal events; only those events are sent | ||||
| 	// for which both the event and the watchpoint has omit in theirs event sets. | ||||
| 	omit = Event(0x400000) | ||||
| ) | ||||
| 
 | ||||
| // FSEvents specific event values. | ||||
| const ( | ||||
| 	FSEventsMustScanSubDirs Event = 0x00001 | ||||
| 	FSEventsUserDropped           = 0x00002 | ||||
| 	FSEventsKernelDropped         = 0x00004 | ||||
| 	FSEventsEventIdsWrapped       = 0x00008 | ||||
| 	FSEventsHistoryDone           = 0x00010 | ||||
| 	FSEventsRootChanged           = 0x00020 | ||||
| 	FSEventsMount                 = 0x00040 | ||||
| 	FSEventsUnmount               = 0x00080 | ||||
| 	FSEventsCreated               = 0x00100 | ||||
| 	FSEventsRemoved               = 0x00200 | ||||
| 	FSEventsInodeMetaMod          = 0x00400 | ||||
| 	FSEventsRenamed               = 0x00800 | ||||
| 	FSEventsModified              = 0x01000 | ||||
| 	FSEventsFinderInfoMod         = 0x02000 | ||||
| 	FSEventsChangeOwner           = 0x04000 | ||||
| 	FSEventsXattrMod              = 0x08000 | ||||
| 	FSEventsIsFile                = 0x10000 | ||||
| 	FSEventsIsDir                 = 0x20000 | ||||
| 	FSEventsIsSymlink             = 0x40000 | ||||
| ) | ||||
| 
 | ||||
| var osestr = map[Event]string{ | ||||
| 	FSEventsMustScanSubDirs: "notify.FSEventsMustScanSubDirs", | ||||
| 	FSEventsUserDropped:     "notify.FSEventsUserDropped", | ||||
| 	FSEventsKernelDropped:   "notify.FSEventsKernelDropped", | ||||
| 	FSEventsEventIdsWrapped: "notify.FSEventsEventIdsWrapped", | ||||
| 	FSEventsHistoryDone:     "notify.FSEventsHistoryDone", | ||||
| 	FSEventsRootChanged:     "notify.FSEventsRootChanged", | ||||
| 	FSEventsMount:           "notify.FSEventsMount", | ||||
| 	FSEventsUnmount:         "notify.FSEventsUnmount", | ||||
| 	FSEventsInodeMetaMod:    "notify.FSEventsInodeMetaMod", | ||||
| 	FSEventsFinderInfoMod:   "notify.FSEventsFinderInfoMod", | ||||
| 	FSEventsChangeOwner:     "notify.FSEventsChangeOwner", | ||||
| 	FSEventsXattrMod:        "notify.FSEventsXattrMod", | ||||
| 	FSEventsIsFile:          "notify.FSEventsIsFile", | ||||
| 	FSEventsIsDir:           "notify.FSEventsIsDir", | ||||
| 	FSEventsIsSymlink:       "notify.FSEventsIsSymlink", | ||||
| } | ||||
| 
 | ||||
| type event struct { | ||||
| 	fse   FSEvent | ||||
| 	event Event | ||||
| } | ||||
| 
 | ||||
| func (ei *event) Event() Event         { return ei.event } | ||||
| func (ei *event) Path() string         { return ei.fse.Path } | ||||
| func (ei *event) Sys() interface{}     { return &ei.fse } | ||||
| func (ei *event) isDir() (bool, error) { return ei.fse.Flags&FSEventsIsDir != 0, nil } | ||||
							
								
								
									
										75
									
								
								vendor/github.com/rjeczalik/notify/event_inotify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								vendor/github.com/rjeczalik/notify/event_inotify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build linux | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import "golang.org/x/sys/unix" | ||||
| 
 | ||||
| // Platform independent event values. | ||||
| const ( | ||||
| 	osSpecificCreate Event = 0x100000 << iota | ||||
| 	osSpecificRemove | ||||
| 	osSpecificWrite | ||||
| 	osSpecificRename | ||||
| 	// internal | ||||
| 	// recursive is used to distinguish recursive eventsets from non-recursive ones | ||||
| 	recursive | ||||
| 	// omit is used for dispatching internal events; only those events are sent | ||||
| 	// for which both the event and the watchpoint has omit in theirs event sets. | ||||
| 	omit | ||||
| ) | ||||
| 
 | ||||
| // Inotify specific masks are legal, implemented events that are guaranteed to | ||||
| // work with notify package on linux-based systems. | ||||
| const ( | ||||
| 	InAccess       = Event(unix.IN_ACCESS)        // File was accessed | ||||
| 	InModify       = Event(unix.IN_MODIFY)        // File was modified | ||||
| 	InAttrib       = Event(unix.IN_ATTRIB)        // Metadata changed | ||||
| 	InCloseWrite   = Event(unix.IN_CLOSE_WRITE)   // Writtable file was closed | ||||
| 	InCloseNowrite = Event(unix.IN_CLOSE_NOWRITE) // Unwrittable file closed | ||||
| 	InOpen         = Event(unix.IN_OPEN)          // File was opened | ||||
| 	InMovedFrom    = Event(unix.IN_MOVED_FROM)    // File was moved from X | ||||
| 	InMovedTo      = Event(unix.IN_MOVED_TO)      // File was moved to Y | ||||
| 	InCreate       = Event(unix.IN_CREATE)        // Subfile was created | ||||
| 	InDelete       = Event(unix.IN_DELETE)        // Subfile was deleted | ||||
| 	InDeleteSelf   = Event(unix.IN_DELETE_SELF)   // Self was deleted | ||||
| 	InMoveSelf     = Event(unix.IN_MOVE_SELF)     // Self was moved | ||||
| ) | ||||
| 
 | ||||
| var osestr = map[Event]string{ | ||||
| 	InAccess:       "notify.InAccess", | ||||
| 	InModify:       "notify.InModify", | ||||
| 	InAttrib:       "notify.InAttrib", | ||||
| 	InCloseWrite:   "notify.InCloseWrite", | ||||
| 	InCloseNowrite: "notify.InCloseNowrite", | ||||
| 	InOpen:         "notify.InOpen", | ||||
| 	InMovedFrom:    "notify.InMovedFrom", | ||||
| 	InMovedTo:      "notify.InMovedTo", | ||||
| 	InCreate:       "notify.InCreate", | ||||
| 	InDelete:       "notify.InDelete", | ||||
| 	InDeleteSelf:   "notify.InDeleteSelf", | ||||
| 	InMoveSelf:     "notify.InMoveSelf", | ||||
| } | ||||
| 
 | ||||
| // Inotify behavior events are not **currently** supported by notify package. | ||||
| const ( | ||||
| 	inDontFollow = Event(unix.IN_DONT_FOLLOW) | ||||
| 	inExclUnlink = Event(unix.IN_EXCL_UNLINK) | ||||
| 	inMaskAdd    = Event(unix.IN_MASK_ADD) | ||||
| 	inOneshot    = Event(unix.IN_ONESHOT) | ||||
| 	inOnlydir    = Event(unix.IN_ONLYDIR) | ||||
| ) | ||||
| 
 | ||||
| type event struct { | ||||
| 	sys   unix.InotifyEvent | ||||
| 	path  string | ||||
| 	event Event | ||||
| } | ||||
| 
 | ||||
| func (e *event) Event() Event         { return e.event } | ||||
| func (e *event) Path() string         { return e.path } | ||||
| func (e *event) Sys() interface{}     { return &e.sys } | ||||
| func (e *event) isDir() (bool, error) { return e.sys.Mask&unix.IN_ISDIR != 0, nil } | ||||
							
								
								
									
										59
									
								
								vendor/github.com/rjeczalik/notify/event_kqueue.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								vendor/github.com/rjeczalik/notify/event_kqueue.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build darwin,kqueue dragonfly freebsd netbsd openbsd | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import "syscall" | ||||
| 
 | ||||
| // TODO(pblaszczyk): ensure in runtime notify built-in event values do not | ||||
| // overlap with platform-defined ones. | ||||
| 
 | ||||
| // Platform independent event values. | ||||
| const ( | ||||
| 	osSpecificCreate Event = 0x0100 << iota | ||||
| 	osSpecificRemove | ||||
| 	osSpecificWrite | ||||
| 	osSpecificRename | ||||
| 	// internal | ||||
| 	// recursive is used to distinguish recursive eventsets from non-recursive ones | ||||
| 	recursive | ||||
| 	// omit is used for dispatching internal events; only those events are sent | ||||
| 	// for which both the event and the watchpoint has omit in theirs event sets. | ||||
| 	omit | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// NoteDelete is an event reported when the unlink() system call was called | ||||
| 	// on the file referenced by the descriptor. | ||||
| 	NoteDelete = Event(syscall.NOTE_DELETE) | ||||
| 	// NoteWrite is an event reported when a write occurred on the file | ||||
| 	// referenced by the descriptor. | ||||
| 	NoteWrite = Event(syscall.NOTE_WRITE) | ||||
| 	// NoteExtend is an event reported when the file referenced by the | ||||
| 	// descriptor was extended. | ||||
| 	NoteExtend = Event(syscall.NOTE_EXTEND) | ||||
| 	// NoteAttrib is an event reported when the file referenced | ||||
| 	// by the descriptor had its attributes changed. | ||||
| 	NoteAttrib = Event(syscall.NOTE_ATTRIB) | ||||
| 	// NoteLink is an event reported when the link count on the file changed. | ||||
| 	NoteLink = Event(syscall.NOTE_LINK) | ||||
| 	// NoteRename is an event reported when the file referenced | ||||
| 	// by the descriptor was renamed. | ||||
| 	NoteRename = Event(syscall.NOTE_RENAME) | ||||
| 	// NoteRevoke is an event reported when access to the file was revoked via | ||||
| 	// revoke(2) or	the underlying file system was unmounted. | ||||
| 	NoteRevoke = Event(syscall.NOTE_REVOKE) | ||||
| ) | ||||
| 
 | ||||
| var osestr = map[Event]string{ | ||||
| 	NoteDelete: "notify.NoteDelete", | ||||
| 	NoteWrite:  "notify.NoteWrite", | ||||
| 	NoteExtend: "notify.NoteExtend", | ||||
| 	NoteAttrib: "notify.NoteAttrib", | ||||
| 	NoteLink:   "notify.NoteLink", | ||||
| 	NoteRename: "notify.NoteRename", | ||||
| 	NoteRevoke: "notify.NoteRevoke", | ||||
| } | ||||
							
								
								
									
										118
									
								
								vendor/github.com/rjeczalik/notify/event_readdcw.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								vendor/github.com/rjeczalik/notify/event_readdcw.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build windows | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"syscall" | ||||
| ) | ||||
| 
 | ||||
| // Platform independent event values. | ||||
| const ( | ||||
| 	osSpecificCreate Event = 1 << (20 + iota) | ||||
| 	osSpecificRemove | ||||
| 	osSpecificWrite | ||||
| 	osSpecificRename | ||||
| 	// recursive is used to distinguish recursive eventsets from non-recursive ones | ||||
| 	recursive | ||||
| 	// omit is used for dispatching internal events; only those events are sent | ||||
| 	// for which both the event and the watchpoint has omit in theirs event sets. | ||||
| 	omit | ||||
| 	// dirmarker TODO(pknap) | ||||
| 	dirmarker | ||||
| ) | ||||
| 
 | ||||
| // ReadDirectoryChangesW filters | ||||
| // On Windows the following events can be passed to Watch. A different set of | ||||
| // events (see actions below) are received on the channel passed to Watch. | ||||
| // For more information refer to | ||||
| // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx | ||||
| const ( | ||||
| 	FileNotifyChangeFileName   = Event(syscall.FILE_NOTIFY_CHANGE_FILE_NAME) | ||||
| 	FileNotifyChangeDirName    = Event(syscall.FILE_NOTIFY_CHANGE_DIR_NAME) | ||||
| 	FileNotifyChangeAttributes = Event(syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES) | ||||
| 	FileNotifyChangeSize       = Event(syscall.FILE_NOTIFY_CHANGE_SIZE) | ||||
| 	FileNotifyChangeLastWrite  = Event(syscall.FILE_NOTIFY_CHANGE_LAST_WRITE) | ||||
| 	FileNotifyChangeLastAccess = Event(syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS) | ||||
| 	FileNotifyChangeCreation   = Event(syscall.FILE_NOTIFY_CHANGE_CREATION) | ||||
| 	FileNotifyChangeSecurity   = Event(syscallFileNotifyChangeSecurity) | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	fileNotifyChangeAll      = 0x17f // logical sum of all FileNotifyChange* events. | ||||
| 	fileNotifyChangeModified = fileNotifyChangeAll &^ (FileNotifyChangeFileName | FileNotifyChangeDirName) | ||||
| ) | ||||
| 
 | ||||
| // according to: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx | ||||
| // this flag should be declared in: http://golang.org/src/pkg/syscall/ztypes_windows.go | ||||
| const syscallFileNotifyChangeSecurity = 0x00000100 | ||||
| 
 | ||||
| // ReadDirectoryChangesW actions | ||||
| // The following events are returned on the channel passed to Watch, but cannot | ||||
| // be passed to Watch itself (see filters above). You can find a table showing | ||||
| // the relation between actions and filteres at | ||||
| // https://github.com/rjeczalik/notify/issues/10#issuecomment-66179535 | ||||
| // The msdn documentation on actions is part of | ||||
| // https://msdn.microsoft.com/en-us/library/windows/desktop/aa364391(v=vs.85).aspx | ||||
| const ( | ||||
| 	FileActionAdded          = Event(syscall.FILE_ACTION_ADDED) << 12 | ||||
| 	FileActionRemoved        = Event(syscall.FILE_ACTION_REMOVED) << 12 | ||||
| 	FileActionModified       = Event(syscall.FILE_ACTION_MODIFIED) << 14 | ||||
| 	FileActionRenamedOldName = Event(syscall.FILE_ACTION_RENAMED_OLD_NAME) << 15 | ||||
| 	FileActionRenamedNewName = Event(syscall.FILE_ACTION_RENAMED_NEW_NAME) << 16 | ||||
| ) | ||||
| 
 | ||||
| const fileActionAll = 0x7f000 // logical sum of all FileAction* events. | ||||
| 
 | ||||
| var osestr = map[Event]string{ | ||||
| 	FileNotifyChangeFileName:   "notify.FileNotifyChangeFileName", | ||||
| 	FileNotifyChangeDirName:    "notify.FileNotifyChangeDirName", | ||||
| 	FileNotifyChangeAttributes: "notify.FileNotifyChangeAttributes", | ||||
| 	FileNotifyChangeSize:       "notify.FileNotifyChangeSize", | ||||
| 	FileNotifyChangeLastWrite:  "notify.FileNotifyChangeLastWrite", | ||||
| 	FileNotifyChangeLastAccess: "notify.FileNotifyChangeLastAccess", | ||||
| 	FileNotifyChangeCreation:   "notify.FileNotifyChangeCreation", | ||||
| 	FileNotifyChangeSecurity:   "notify.FileNotifyChangeSecurity", | ||||
| 
 | ||||
| 	FileActionAdded:          "notify.FileActionAdded", | ||||
| 	FileActionRemoved:        "notify.FileActionRemoved", | ||||
| 	FileActionModified:       "notify.FileActionModified", | ||||
| 	FileActionRenamedOldName: "notify.FileActionRenamedOldName", | ||||
| 	FileActionRenamedNewName: "notify.FileActionRenamedNewName", | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	fTypeUnknown uint8 = iota | ||||
| 	fTypeFile | ||||
| 	fTypeDirectory | ||||
| ) | ||||
| 
 | ||||
| // TODO(ppknap) : doc. | ||||
| type event struct { | ||||
| 	pathw  []uint16 | ||||
| 	name   string | ||||
| 	ftype  uint8 | ||||
| 	action uint32 | ||||
| 	filter uint32 | ||||
| 	e      Event | ||||
| } | ||||
| 
 | ||||
| func (e *event) Event() Event     { return e.e } | ||||
| func (e *event) Path() string     { return filepath.Join(syscall.UTF16ToString(e.pathw), e.name) } | ||||
| func (e *event) Sys() interface{} { return e.ftype } | ||||
| 
 | ||||
| func (e *event) isDir() (bool, error) { | ||||
| 	if e.ftype != fTypeUnknown { | ||||
| 		return e.ftype == fTypeDirectory, nil | ||||
| 	} | ||||
| 	fi, err := os.Stat(e.Path()) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	return fi.IsDir(), nil | ||||
| } | ||||
							
								
								
									
										31
									
								
								vendor/github.com/rjeczalik/notify/event_stub.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/rjeczalik/notify/event_stub.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows | ||||
| // +build !kqueue,!solaris | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| // Platform independent event values. | ||||
| const ( | ||||
| 	osSpecificCreate Event = 1 << iota | ||||
| 	osSpecificRemove | ||||
| 	osSpecificWrite | ||||
| 	osSpecificRename | ||||
| 	// internal | ||||
| 	// recursive is used to distinguish recursive eventsets from non-recursive ones | ||||
| 	recursive | ||||
| 	// omit is used for dispatching internal events; only those events are sent | ||||
| 	// for which both the event and the watchpoint has omit in theirs event sets. | ||||
| 	omit | ||||
| ) | ||||
| 
 | ||||
| var osestr = map[Event]string{} | ||||
| 
 | ||||
| type event struct{} | ||||
| 
 | ||||
| func (e *event) Event() (_ Event)         { return } | ||||
| func (e *event) Path() (_ string)         { return } | ||||
| func (e *event) Sys() (_ interface{})     { return } | ||||
| func (e *event) isDir() (_ bool, _ error) { return } | ||||
							
								
								
									
										22
									
								
								vendor/github.com/rjeczalik/notify/event_trigger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/rjeczalik/notify/event_trigger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| type event struct { | ||||
| 	p  string | ||||
| 	e  Event | ||||
| 	d  bool | ||||
| 	pe interface{} | ||||
| } | ||||
| 
 | ||||
| func (e *event) Event() Event { return e.e } | ||||
| 
 | ||||
| func (e *event) Path() string { return e.p } | ||||
| 
 | ||||
| func (e *event) Sys() interface{} { return e.pe } | ||||
| 
 | ||||
| func (e *event) isDir() (bool, error) { return e.d, nil } | ||||
							
								
								
									
										275
									
								
								vendor/github.com/rjeczalik/notify/node.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								vendor/github.com/rjeczalik/notify/node.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,275 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| ) | ||||
| 
 | ||||
| var errSkip = errors.New("notify: skip") | ||||
| 
 | ||||
| type walkPathFunc func(nd node, isbase bool) error | ||||
| 
 | ||||
| type walkFunc func(node) error | ||||
| 
 | ||||
| func errnotexist(name string) error { | ||||
| 	return &os.PathError{ | ||||
| 		Op:   "Node", | ||||
| 		Path: name, | ||||
| 		Err:  os.ErrNotExist, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type node struct { | ||||
| 	Name  string | ||||
| 	Watch watchpoint | ||||
| 	Child map[string]node | ||||
| } | ||||
| 
 | ||||
| func newnode(name string) node { | ||||
| 	return node{ | ||||
| 		Name:  name, | ||||
| 		Watch: make(watchpoint), | ||||
| 		Child: make(map[string]node), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (nd node) addchild(name, base string) node { | ||||
| 	child, ok := nd.Child[base] | ||||
| 	if !ok { | ||||
| 		child = newnode(name) | ||||
| 		nd.Child[base] = child | ||||
| 	} | ||||
| 	return child | ||||
| } | ||||
| 
 | ||||
| func (nd node) Add(name string) node { | ||||
| 	i := indexbase(nd.Name, name) | ||||
| 	if i == -1 { | ||||
| 		return node{} | ||||
| 	} | ||||
| 	for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) { | ||||
| 		nd = nd.addchild(name[:i+j], name[i:i+j]) | ||||
| 		i += j + 1 | ||||
| 	} | ||||
| 	return nd.addchild(name, name[i:]) | ||||
| } | ||||
| 
 | ||||
| func (nd node) AddDir(fn walkFunc) error { | ||||
| 	stack := []node{nd} | ||||
| Traverse: | ||||
| 	for n := len(stack); n != 0; n = len(stack) { | ||||
| 		nd, stack = stack[n-1], stack[:n-1] | ||||
| 		switch err := fn(nd); err { | ||||
| 		case nil: | ||||
| 		case errSkip: | ||||
| 			continue Traverse | ||||
| 		default: | ||||
| 			return &os.PathError{ | ||||
| 				Op:   "error while traversing", | ||||
| 				Path: nd.Name, | ||||
| 				Err:  err, | ||||
| 			} | ||||
| 		} | ||||
| 		// TODO(rjeczalik): tolerate open failures - add failed names to | ||||
| 		// AddDirError and notify users which names are not added to the tree. | ||||
| 		fi, err := ioutil.ReadDir(nd.Name) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		for _, fi := range fi { | ||||
| 			if fi.Mode()&(os.ModeSymlink|os.ModeDir) == os.ModeDir { | ||||
| 				name := filepath.Join(nd.Name, fi.Name()) | ||||
| 				stack = append(stack, nd.addchild(name, name[len(nd.Name)+1:])) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (nd node) Get(name string) (node, error) { | ||||
| 	i := indexbase(nd.Name, name) | ||||
| 	if i == -1 { | ||||
| 		return node{}, errnotexist(name) | ||||
| 	} | ||||
| 	ok := false | ||||
| 	for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) { | ||||
| 		if nd, ok = nd.Child[name[i:i+j]]; !ok { | ||||
| 			return node{}, errnotexist(name) | ||||
| 		} | ||||
| 		i += j + 1 | ||||
| 	} | ||||
| 	if nd, ok = nd.Child[name[i:]]; !ok { | ||||
| 		return node{}, errnotexist(name) | ||||
| 	} | ||||
| 	return nd, nil | ||||
| } | ||||
| 
 | ||||
| func (nd node) Del(name string) error { | ||||
| 	i := indexbase(nd.Name, name) | ||||
| 	if i == -1 { | ||||
| 		return errnotexist(name) | ||||
| 	} | ||||
| 	stack := []node{nd} | ||||
| 	ok := false | ||||
| 	for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) { | ||||
| 		if nd, ok = nd.Child[name[i:i+j]]; !ok { | ||||
| 			return errnotexist(name[:i+j]) | ||||
| 		} | ||||
| 		stack = append(stack, nd) | ||||
| 	} | ||||
| 	if nd, ok = nd.Child[name[i:]]; !ok { | ||||
| 		return errnotexist(name) | ||||
| 	} | ||||
| 	nd.Child = nil | ||||
| 	nd.Watch = nil | ||||
| 	for name, i = base(nd.Name), len(stack); i != 0; name, i = base(nd.Name), i-1 { | ||||
| 		nd = stack[i-1] | ||||
| 		if nd := nd.Child[name]; len(nd.Watch) > 1 || len(nd.Child) != 0 { | ||||
| 			break | ||||
| 		} else { | ||||
| 			nd.Child = nil | ||||
| 			nd.Watch = nil | ||||
| 		} | ||||
| 		delete(nd.Child, name) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (nd node) Walk(fn walkFunc) error { | ||||
| 	stack := []node{nd} | ||||
| Traverse: | ||||
| 	for n := len(stack); n != 0; n = len(stack) { | ||||
| 		nd, stack = stack[n-1], stack[:n-1] | ||||
| 		switch err := fn(nd); err { | ||||
| 		case nil: | ||||
| 		case errSkip: | ||||
| 			continue Traverse | ||||
| 		default: | ||||
| 			return err | ||||
| 		} | ||||
| 		for name, nd := range nd.Child { | ||||
| 			if name == "" { | ||||
| 				// Node storing inactive watchpoints has empty name, skip it | ||||
| 				// form traversing. Root node has also an empty name, but it | ||||
| 				// never has a parent node. | ||||
| 				continue | ||||
| 			} | ||||
| 			stack = append(stack, nd) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (nd node) WalkPath(name string, fn walkPathFunc) error { | ||||
| 	i := indexbase(nd.Name, name) | ||||
| 	if i == -1 { | ||||
| 		return errnotexist(name) | ||||
| 	} | ||||
| 	ok := false | ||||
| 	for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) { | ||||
| 		switch err := fn(nd, false); err { | ||||
| 		case nil: | ||||
| 		case errSkip: | ||||
| 			return nil | ||||
| 		default: | ||||
| 			return err | ||||
| 		} | ||||
| 		if nd, ok = nd.Child[name[i:i+j]]; !ok { | ||||
| 			return errnotexist(name[:i+j]) | ||||
| 		} | ||||
| 		i += j + 1 | ||||
| 	} | ||||
| 	switch err := fn(nd, false); err { | ||||
| 	case nil: | ||||
| 	case errSkip: | ||||
| 		return nil | ||||
| 	default: | ||||
| 		return err | ||||
| 	} | ||||
| 	if nd, ok = nd.Child[name[i:]]; !ok { | ||||
| 		return errnotexist(name) | ||||
| 	} | ||||
| 	switch err := fn(nd, true); err { | ||||
| 	case nil, errSkip: | ||||
| 		return nil | ||||
| 	default: | ||||
| 		return err | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type root struct { | ||||
| 	nd node | ||||
| } | ||||
| 
 | ||||
| func (r root) addroot(name string) node { | ||||
| 	if vol := filepath.VolumeName(name); vol != "" { | ||||
| 		root, ok := r.nd.Child[vol] | ||||
| 		if !ok { | ||||
| 			root = r.nd.addchild(vol, vol) | ||||
| 		} | ||||
| 		return root | ||||
| 	} | ||||
| 	return r.nd | ||||
| } | ||||
| 
 | ||||
| func (r root) root(name string) (node, error) { | ||||
| 	if vol := filepath.VolumeName(name); vol != "" { | ||||
| 		nd, ok := r.nd.Child[vol] | ||||
| 		if !ok { | ||||
| 			return node{}, errnotexist(name) | ||||
| 		} | ||||
| 		return nd, nil | ||||
| 	} | ||||
| 	return r.nd, nil | ||||
| } | ||||
| 
 | ||||
| func (r root) Add(name string) node { | ||||
| 	return r.addroot(name).Add(name) | ||||
| } | ||||
| 
 | ||||
| func (r root) AddDir(dir string, fn walkFunc) error { | ||||
| 	return r.Add(dir).AddDir(fn) | ||||
| } | ||||
| 
 | ||||
| func (r root) Del(name string) error { | ||||
| 	nd, err := r.root(name) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nd.Del(name) | ||||
| } | ||||
| 
 | ||||
| func (r root) Get(name string) (node, error) { | ||||
| 	nd, err := r.root(name) | ||||
| 	if err != nil { | ||||
| 		return node{}, err | ||||
| 	} | ||||
| 	if nd.Name != name { | ||||
| 		if nd, err = nd.Get(name); err != nil { | ||||
| 			return node{}, err | ||||
| 		} | ||||
| 	} | ||||
| 	return nd, nil | ||||
| } | ||||
| 
 | ||||
| func (r root) Walk(name string, fn walkFunc) error { | ||||
| 	nd, err := r.Get(name) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nd.Walk(fn) | ||||
| } | ||||
| 
 | ||||
| func (r root) WalkPath(name string, fn walkPathFunc) error { | ||||
| 	nd, err := r.root(name) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nd.WalkPath(name, fn) | ||||
| } | ||||
							
								
								
									
										74
									
								
								vendor/github.com/rjeczalik/notify/notify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								vendor/github.com/rjeczalik/notify/notify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // BUG(rjeczalik): Notify does not collect watchpoints, when underlying watches | ||||
| // were removed by their os-specific watcher implementations. Instead users are | ||||
| // advised to listen on persistent paths to have guarantee they receive events | ||||
| // for the whole lifetime of their applications (to discuss see #69). | ||||
| 
 | ||||
| // BUG(ppknap): Linux (inotify) does not support watcher behavior masks like | ||||
| // InOneshot, InOnlydir etc. Instead users are advised to perform the filtering | ||||
| // themselves (to discuss see #71). | ||||
| 
 | ||||
| // BUG(ppknap): Notify  was not tested for short path name support under Windows | ||||
| // (ReadDirectoryChangesW). | ||||
| 
 | ||||
| // BUG(ppknap): Windows (ReadDirectoryChangesW) cannot recognize which notification | ||||
| // triggers FileActionModified event. (to discuss see #75). | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| var defaultTree = newTree() | ||||
| 
 | ||||
| // Watch sets up a watchpoint on path listening for events given by the events | ||||
| // argument. | ||||
| // | ||||
| // File or directory given by the path must exist, otherwise Watch will fail | ||||
| // with non-nil error. Notify resolves, for its internal purpose, any symlinks | ||||
| // the provided path may contain, so it may fail if the symlinks form a cycle. | ||||
| // It does so, since not all watcher implementations treat passed paths as-is. | ||||
| // E.g. FSEvents reports a real path for every event, setting a watchpoint | ||||
| // on /tmp will report events with paths rooted at /private/tmp etc. | ||||
| // | ||||
| // The c almost always is a buffered channel. Watch will not block sending to c | ||||
| // - the caller must ensure that c has sufficient buffer space to keep up with | ||||
| // the expected event rate. | ||||
| // | ||||
| // It is allowed to pass the same channel multiple times with different event | ||||
| // list or different paths. Calling Watch with different event lists for a single | ||||
| // watchpoint expands its event set. The only way to shrink it, is to call | ||||
| // Stop on its channel. | ||||
| // | ||||
| // Calling Watch with empty event list does expand nor shrink watchpoint's event | ||||
| // set. If c is the first channel to listen for events on the given path, Watch | ||||
| // will seamlessly create a watch on the filesystem. | ||||
| // | ||||
| // Notify dispatches copies of single filesystem event to all channels registered | ||||
| // for each path. If a single filesystem event contains multiple coalesced events, | ||||
| // each of them is dispatched separately. E.g. the following filesystem change: | ||||
| // | ||||
| //   ~ $ echo Hello > Notify.txt | ||||
| // | ||||
| // dispatches two events - notify.Create and notify.Write. However, it may depend | ||||
| // on the underlying watcher implementation whether OS reports both of them. | ||||
| // | ||||
| // Windows and recursive watches | ||||
| // | ||||
| // If a directory which path was used to create recursive watch under Windows | ||||
| // gets deleted, the OS will not report such event. It is advised to keep in | ||||
| // mind this limitation while setting recursive watchpoints for your application, | ||||
| // e.g. use persistent paths like %userprofile% or watch additionally parent | ||||
| // directory of a recursive watchpoint in order to receive delete events for it. | ||||
| func Watch(path string, c chan<- EventInfo, events ...Event) error { | ||||
| 	return defaultTree.Watch(path, c, events...) | ||||
| } | ||||
| 
 | ||||
| // Stop removes all watchpoints registered for c. All underlying watches are | ||||
| // also removed, for which c was the last channel listening for events. | ||||
| // | ||||
| // Stop does not close c. When Stop returns, it is guaranteed that c will | ||||
| // receive no more signals. | ||||
| func Stop(c chan<- EventInfo) { | ||||
| 	defaultTree.Stop(c) | ||||
| } | ||||
							
								
								
									
										22
									
								
								vendor/github.com/rjeczalik/notify/tree.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/rjeczalik/notify/tree.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| const buffer = 128 | ||||
| 
 | ||||
| type tree interface { | ||||
| 	Watch(string, chan<- EventInfo, ...Event) error | ||||
| 	Stop(chan<- EventInfo) | ||||
| 	Close() error | ||||
| } | ||||
| 
 | ||||
| func newTree() tree { | ||||
| 	c := make(chan EventInfo, buffer) | ||||
| 	w := newWatcher(c) | ||||
| 	if rw, ok := w.(recursiveWatcher); ok { | ||||
| 		return newRecursiveTree(rw, c) | ||||
| 	} | ||||
| 	return newNonrecursiveTree(w, c, make(chan EventInfo, buffer)) | ||||
| } | ||||
							
								
								
									
										292
									
								
								vendor/github.com/rjeczalik/notify/tree_nonrecursive.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								vendor/github.com/rjeczalik/notify/tree_nonrecursive.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,292 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import "sync" | ||||
| 
 | ||||
| // nonrecursiveTree TODO(rjeczalik) | ||||
| type nonrecursiveTree struct { | ||||
| 	rw   sync.RWMutex // protects root | ||||
| 	root root | ||||
| 	w    watcher | ||||
| 	c    chan EventInfo | ||||
| 	rec  chan EventInfo | ||||
| } | ||||
| 
 | ||||
| // newNonrecursiveTree TODO(rjeczalik) | ||||
| func newNonrecursiveTree(w watcher, c, rec chan EventInfo) *nonrecursiveTree { | ||||
| 	if rec == nil { | ||||
| 		rec = make(chan EventInfo, buffer) | ||||
| 	} | ||||
| 	t := &nonrecursiveTree{ | ||||
| 		root: root{nd: newnode("")}, | ||||
| 		w:    w, | ||||
| 		c:    c, | ||||
| 		rec:  rec, | ||||
| 	} | ||||
| 	go t.dispatch(c) | ||||
| 	go t.internal(rec) | ||||
| 	return t | ||||
| } | ||||
| 
 | ||||
| // dispatch TODO(rjeczalik) | ||||
| func (t *nonrecursiveTree) dispatch(c <-chan EventInfo) { | ||||
| 	for ei := range c { | ||||
| 		dbgprintf("dispatching %v on %q", ei.Event(), ei.Path()) | ||||
| 		go func(ei EventInfo) { | ||||
| 			var nd node | ||||
| 			var isrec bool | ||||
| 			dir, base := split(ei.Path()) | ||||
| 			fn := func(it node, isbase bool) error { | ||||
| 				isrec = isrec || it.Watch.IsRecursive() | ||||
| 				if isbase { | ||||
| 					nd = it | ||||
| 				} else { | ||||
| 					it.Watch.Dispatch(ei, recursive) | ||||
| 				} | ||||
| 				return nil | ||||
| 			} | ||||
| 			t.rw.RLock() | ||||
| 			// Notify recursive watchpoints found on the path. | ||||
| 			if err := t.root.WalkPath(dir, fn); err != nil { | ||||
| 				dbgprint("dispatch did not reach leaf:", err) | ||||
| 				t.rw.RUnlock() | ||||
| 				return | ||||
| 			} | ||||
| 			// Notify parent watchpoint. | ||||
| 			nd.Watch.Dispatch(ei, 0) | ||||
| 			isrec = isrec || nd.Watch.IsRecursive() | ||||
| 			// If leaf watchpoint exists, notify it. | ||||
| 			if nd, ok := nd.Child[base]; ok { | ||||
| 				isrec = isrec || nd.Watch.IsRecursive() | ||||
| 				nd.Watch.Dispatch(ei, 0) | ||||
| 			} | ||||
| 			t.rw.RUnlock() | ||||
| 			// If the event describes newly leaf directory created within | ||||
| 			if !isrec || ei.Event() != Create { | ||||
| 				return | ||||
| 			} | ||||
| 			if ok, err := ei.(isDirer).isDir(); !ok || err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			t.rec <- ei | ||||
| 		}(ei) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // internal TODO(rjeczalik) | ||||
| func (t *nonrecursiveTree) internal(rec <-chan EventInfo) { | ||||
| 	for ei := range rec { | ||||
| 		var nd node | ||||
| 		var eset = internal | ||||
| 		t.rw.Lock() | ||||
| 		t.root.WalkPath(ei.Path(), func(it node, _ bool) error { | ||||
| 			if e := it.Watch[t.rec]; e != 0 && e > eset { | ||||
| 				eset = e | ||||
| 			} | ||||
| 			nd = it | ||||
| 			return nil | ||||
| 		}) | ||||
| 		if eset == internal { | ||||
| 			t.rw.Unlock() | ||||
| 			continue | ||||
| 		} | ||||
| 		err := nd.Add(ei.Path()).AddDir(t.recFunc(eset)) | ||||
| 		t.rw.Unlock() | ||||
| 		if err != nil { | ||||
| 			dbgprintf("internal(%p) error: %v", rec, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // watchAdd TODO(rjeczalik) | ||||
| func (t *nonrecursiveTree) watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff { | ||||
| 	if e&recursive != 0 { | ||||
| 		diff := nd.Watch.Add(t.rec, e|Create|omit) | ||||
| 		nd.Watch.Add(c, e) | ||||
| 		return diff | ||||
| 	} | ||||
| 	return nd.Watch.Add(c, e) | ||||
| } | ||||
| 
 | ||||
| // watchDelMin TODO(rjeczalik) | ||||
| func (t *nonrecursiveTree) watchDelMin(min Event, nd node, c chan<- EventInfo, e Event) eventDiff { | ||||
| 	old, ok := nd.Watch[t.rec] | ||||
| 	if ok { | ||||
| 		nd.Watch[t.rec] = min | ||||
| 	} | ||||
| 	diff := nd.Watch.Del(c, e) | ||||
| 	if ok { | ||||
| 		switch old &^= diff[0] &^ diff[1]; { | ||||
| 		case old|internal == internal: | ||||
| 			delete(nd.Watch, t.rec) | ||||
| 			if set, ok := nd.Watch[nil]; ok && len(nd.Watch) == 1 && set == 0 { | ||||
| 				delete(nd.Watch, nil) | ||||
| 			} | ||||
| 		default: | ||||
| 			nd.Watch.Add(t.rec, old|Create) | ||||
| 			switch { | ||||
| 			case diff == none: | ||||
| 			case diff[1]|Create == diff[0]: | ||||
| 				diff = none | ||||
| 			default: | ||||
| 				diff[1] |= Create | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return diff | ||||
| } | ||||
| 
 | ||||
| // watchDel TODO(rjeczalik) | ||||
| func (t *nonrecursiveTree) watchDel(nd node, c chan<- EventInfo, e Event) eventDiff { | ||||
| 	return t.watchDelMin(0, nd, c, e) | ||||
| } | ||||
| 
 | ||||
| // Watch TODO(rjeczalik) | ||||
| func (t *nonrecursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error { | ||||
| 	if c == nil { | ||||
| 		panic("notify: Watch using nil channel") | ||||
| 	} | ||||
| 	// Expanding with empty event set is a nop. | ||||
| 	if len(events) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	path, isrec, err := cleanpath(path) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	eset := joinevents(events) | ||||
| 	t.rw.Lock() | ||||
| 	defer t.rw.Unlock() | ||||
| 	nd := t.root.Add(path) | ||||
| 	if isrec { | ||||
| 		return t.watchrec(nd, c, eset|recursive) | ||||
| 	} | ||||
| 	return t.watch(nd, c, eset) | ||||
| } | ||||
| 
 | ||||
| func (t *nonrecursiveTree) watch(nd node, c chan<- EventInfo, e Event) (err error) { | ||||
| 	diff := nd.Watch.Add(c, e) | ||||
| 	switch { | ||||
| 	case diff == none: | ||||
| 		return nil | ||||
| 	case diff[1] == 0: | ||||
| 		// TODO(rjeczalik): cleanup this panic after implementation is stable | ||||
| 		panic("eset is empty: " + nd.Name) | ||||
| 	case diff[0] == 0: | ||||
| 		err = t.w.Watch(nd.Name, diff[1]) | ||||
| 	default: | ||||
| 		err = t.w.Rewatch(nd.Name, diff[0], diff[1]) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		nd.Watch.Del(c, diff.Event()) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (t *nonrecursiveTree) recFunc(e Event) walkFunc { | ||||
| 	return func(nd node) error { | ||||
| 		switch diff := nd.Watch.Add(t.rec, e|omit|Create); { | ||||
| 		case diff == none: | ||||
| 		case diff[1] == 0: | ||||
| 			// TODO(rjeczalik): cleanup this panic after implementation is stable | ||||
| 			panic("eset is empty: " + nd.Name) | ||||
| 		case diff[0] == 0: | ||||
| 			t.w.Watch(nd.Name, diff[1]) | ||||
| 		default: | ||||
| 			t.w.Rewatch(nd.Name, diff[0], diff[1]) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (t *nonrecursiveTree) watchrec(nd node, c chan<- EventInfo, e Event) error { | ||||
| 	var traverse func(walkFunc) error | ||||
| 	// Non-recursive tree listens on Create event for every recursive | ||||
| 	// watchpoint in order to automagically set a watch for every | ||||
| 	// created directory. | ||||
| 	switch diff := nd.Watch.dryAdd(t.rec, e|Create); { | ||||
| 	case diff == none: | ||||
| 		t.watchAdd(nd, c, e) | ||||
| 		nd.Watch.Add(t.rec, e|omit|Create) | ||||
| 		return nil | ||||
| 	case diff[1] == 0: | ||||
| 		// TODO(rjeczalik): cleanup this panic after implementation is stable | ||||
| 		panic("eset is empty: " + nd.Name) | ||||
| 	case diff[0] == 0: | ||||
| 		// TODO(rjeczalik): BFS into directories and skip subtree as soon as first | ||||
| 		// recursive watchpoint is encountered. | ||||
| 		traverse = nd.AddDir | ||||
| 	default: | ||||
| 		traverse = nd.Walk | ||||
| 	} | ||||
| 	// TODO(rjeczalik): account every path that failed to be (re)watched | ||||
| 	// and retry. | ||||
| 	if err := traverse(t.recFunc(e)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	t.watchAdd(nd, c, e) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type walkWatchpointFunc func(Event, node) error | ||||
| 
 | ||||
| func (t *nonrecursiveTree) walkWatchpoint(nd node, fn walkWatchpointFunc) error { | ||||
| 	type minode struct { | ||||
| 		min Event | ||||
| 		nd  node | ||||
| 	} | ||||
| 	mnd := minode{nd: nd} | ||||
| 	stack := []minode{mnd} | ||||
| Traverse: | ||||
| 	for n := len(stack); n != 0; n = len(stack) { | ||||
| 		mnd, stack = stack[n-1], stack[:n-1] | ||||
| 		// There must be no recursive watchpoints if the node has no watchpoints | ||||
| 		// itself (every node in subtree rooted at recursive watchpoints must | ||||
| 		// have at least nil (total) and t.rec watchpoints). | ||||
| 		if len(mnd.nd.Watch) != 0 { | ||||
| 			switch err := fn(mnd.min, mnd.nd); err { | ||||
| 			case nil: | ||||
| 			case errSkip: | ||||
| 				continue Traverse | ||||
| 			default: | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		for _, nd := range mnd.nd.Child { | ||||
| 			stack = append(stack, minode{mnd.nd.Watch[t.rec], nd}) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Stop TODO(rjeczalik) | ||||
| func (t *nonrecursiveTree) Stop(c chan<- EventInfo) { | ||||
| 	fn := func(min Event, nd node) error { | ||||
| 		// TODO(rjeczalik): aggregate watcher errors and retry; in worst case | ||||
| 		// forward to the user. | ||||
| 		switch diff := t.watchDelMin(min, nd, c, all); { | ||||
| 		case diff == none: | ||||
| 			return nil | ||||
| 		case diff[1] == 0: | ||||
| 			t.w.Unwatch(nd.Name) | ||||
| 		default: | ||||
| 			t.w.Rewatch(nd.Name, diff[0], diff[1]) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	t.rw.Lock() | ||||
| 	err := t.walkWatchpoint(t.root.nd, fn) // TODO(rjeczalik): store max root per c | ||||
| 	t.rw.Unlock() | ||||
| 	dbgprintf("Stop(%p) error: %v\n", c, err) | ||||
| } | ||||
| 
 | ||||
| // Close TODO(rjeczalik) | ||||
| func (t *nonrecursiveTree) Close() error { | ||||
| 	err := t.w.Close() | ||||
| 	close(t.c) | ||||
| 	return err | ||||
| } | ||||
							
								
								
									
										354
									
								
								vendor/github.com/rjeczalik/notify/tree_recursive.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								vendor/github.com/rjeczalik/notify/tree_recursive.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,354 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import "sync" | ||||
| 
 | ||||
| // watchAdd TODO(rjeczalik) | ||||
| func watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff { | ||||
| 	diff := nd.Watch.Add(c, e) | ||||
| 	if wp := nd.Child[""].Watch; len(wp) != 0 { | ||||
| 		e = wp.Total() | ||||
| 		diff[0] |= e | ||||
| 		diff[1] |= e | ||||
| 		if diff[0] == diff[1] { | ||||
| 			return none | ||||
| 		} | ||||
| 	} | ||||
| 	return diff | ||||
| } | ||||
| 
 | ||||
| // watchAddInactive TODO(rjeczalik) | ||||
| func watchAddInactive(nd node, c chan<- EventInfo, e Event) eventDiff { | ||||
| 	wp := nd.Child[""].Watch | ||||
| 	if wp == nil { | ||||
| 		wp = make(watchpoint) | ||||
| 		nd.Child[""] = node{Watch: wp} | ||||
| 	} | ||||
| 	diff := wp.Add(c, e) | ||||
| 	e = nd.Watch.Total() | ||||
| 	diff[0] |= e | ||||
| 	diff[1] |= e | ||||
| 	if diff[0] == diff[1] { | ||||
| 		return none | ||||
| 	} | ||||
| 	return diff | ||||
| } | ||||
| 
 | ||||
| // watchCopy TODO(rjeczalik) | ||||
| func watchCopy(src, dst node) { | ||||
| 	for c, e := range src.Watch { | ||||
| 		if c == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		watchAddInactive(dst, c, e) | ||||
| 	} | ||||
| 	if wpsrc := src.Child[""].Watch; len(wpsrc) != 0 { | ||||
| 		wpdst := dst.Child[""].Watch | ||||
| 		for c, e := range wpsrc { | ||||
| 			if c == nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			wpdst.Add(c, e) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // watchDel TODO(rjeczalik) | ||||
| func watchDel(nd node, c chan<- EventInfo, e Event) eventDiff { | ||||
| 	diff := nd.Watch.Del(c, e) | ||||
| 	if wp := nd.Child[""].Watch; len(wp) != 0 { | ||||
| 		diffInactive := wp.Del(c, e) | ||||
| 		e = wp.Total() | ||||
| 		// TODO(rjeczalik): add e if e != all? | ||||
| 		diff[0] |= diffInactive[0] | e | ||||
| 		diff[1] |= diffInactive[1] | e | ||||
| 		if diff[0] == diff[1] { | ||||
| 			return none | ||||
| 		} | ||||
| 	} | ||||
| 	return diff | ||||
| } | ||||
| 
 | ||||
| // watchTotal TODO(rjeczalik) | ||||
| func watchTotal(nd node) Event { | ||||
| 	e := nd.Watch.Total() | ||||
| 	if wp := nd.Child[""].Watch; len(wp) != 0 { | ||||
| 		e |= wp.Total() | ||||
| 	} | ||||
| 	return e | ||||
| } | ||||
| 
 | ||||
| // watchIsRecursive TODO(rjeczalik) | ||||
| func watchIsRecursive(nd node) bool { | ||||
| 	ok := nd.Watch.IsRecursive() | ||||
| 	// TODO(rjeczalik): add a test for len(wp) != 0 change the condition. | ||||
| 	if wp := nd.Child[""].Watch; len(wp) != 0 { | ||||
| 		// If a watchpoint holds inactive watchpoints, it means it's a parent | ||||
| 		// one, which is recursive by nature even though it may be not recursive | ||||
| 		// itself. | ||||
| 		ok = true | ||||
| 	} | ||||
| 	return ok | ||||
| } | ||||
| 
 | ||||
| // recursiveTree TODO(rjeczalik) | ||||
| type recursiveTree struct { | ||||
| 	rw   sync.RWMutex // protects root | ||||
| 	root root | ||||
| 	// TODO(rjeczalik): merge watcher + recursiveWatcher after #5 and #6 | ||||
| 	w interface { | ||||
| 		watcher | ||||
| 		recursiveWatcher | ||||
| 	} | ||||
| 	c chan EventInfo | ||||
| } | ||||
| 
 | ||||
| // newRecursiveTree TODO(rjeczalik) | ||||
| func newRecursiveTree(w recursiveWatcher, c chan EventInfo) *recursiveTree { | ||||
| 	t := &recursiveTree{ | ||||
| 		root: root{nd: newnode("")}, | ||||
| 		w: struct { | ||||
| 			watcher | ||||
| 			recursiveWatcher | ||||
| 		}{w.(watcher), w}, | ||||
| 		c: c, | ||||
| 	} | ||||
| 	go t.dispatch() | ||||
| 	return t | ||||
| } | ||||
| 
 | ||||
| // dispatch TODO(rjeczalik) | ||||
| func (t *recursiveTree) dispatch() { | ||||
| 	for ei := range t.c { | ||||
| 		dbgprintf("dispatching %v on %q", ei.Event(), ei.Path()) | ||||
| 		go func(ei EventInfo) { | ||||
| 			nd, ok := node{}, false | ||||
| 			dir, base := split(ei.Path()) | ||||
| 			fn := func(it node, isbase bool) error { | ||||
| 				if isbase { | ||||
| 					nd = it | ||||
| 				} else { | ||||
| 					it.Watch.Dispatch(ei, recursive) | ||||
| 				} | ||||
| 				return nil | ||||
| 			} | ||||
| 			t.rw.RLock() | ||||
| 			defer t.rw.RUnlock() | ||||
| 			// Notify recursive watchpoints found on the path. | ||||
| 			if err := t.root.WalkPath(dir, fn); err != nil { | ||||
| 				dbgprint("dispatch did not reach leaf:", err) | ||||
| 				return | ||||
| 			} | ||||
| 			// Notify parent watchpoint. | ||||
| 			nd.Watch.Dispatch(ei, 0) | ||||
| 			// If leaf watchpoint exists, notify it. | ||||
| 			if nd, ok = nd.Child[base]; ok { | ||||
| 				nd.Watch.Dispatch(ei, 0) | ||||
| 			} | ||||
| 		}(ei) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Watch TODO(rjeczalik) | ||||
| func (t *recursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error { | ||||
| 	if c == nil { | ||||
| 		panic("notify: Watch using nil channel") | ||||
| 	} | ||||
| 	// Expanding with empty event set is a nop. | ||||
| 	if len(events) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	path, isrec, err := cleanpath(path) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	eventset := joinevents(events) | ||||
| 	if isrec { | ||||
| 		eventset |= recursive | ||||
| 	} | ||||
| 	t.rw.Lock() | ||||
| 	defer t.rw.Unlock() | ||||
| 	// case 1: cur is a child | ||||
| 	// | ||||
| 	// Look for parent watch which already covers the given path. | ||||
| 	parent := node{} | ||||
| 	self := false | ||||
| 	err = t.root.WalkPath(path, func(nd node, isbase bool) error { | ||||
| 		if watchTotal(nd) != 0 { | ||||
| 			parent = nd | ||||
| 			self = isbase | ||||
| 			return errSkip | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	cur := t.root.Add(path) // add after the walk, so it's less to traverse | ||||
| 	if err == nil && parent.Watch != nil { | ||||
| 		// Parent watch found. Register inactive watchpoint, so we have enough | ||||
| 		// information to shrink the eventset on eventual Stop. | ||||
| 		// return t.resetwatchpoint(parent, parent, c, eventset|inactive) | ||||
| 		var diff eventDiff | ||||
| 		if self { | ||||
| 			diff = watchAdd(cur, c, eventset) | ||||
| 		} else { | ||||
| 			diff = watchAddInactive(parent, c, eventset) | ||||
| 		} | ||||
| 		switch { | ||||
| 		case diff == none: | ||||
| 			// the parent watchpoint already covers requested subtree with its | ||||
| 			// eventset | ||||
| 		case diff[0] == 0: | ||||
| 			// TODO(rjeczalik): cleanup this panic after implementation is stable | ||||
| 			panic("dangling watchpoint: " + parent.Name) | ||||
| 		default: | ||||
| 			if isrec || watchIsRecursive(parent) { | ||||
| 				err = t.w.RecursiveRewatch(parent.Name, parent.Name, diff[0], diff[1]) | ||||
| 			} else { | ||||
| 				err = t.w.Rewatch(parent.Name, diff[0], diff[1]) | ||||
| 			} | ||||
| 			if err != nil { | ||||
| 				watchDel(parent, c, diff.Event()) | ||||
| 				return err | ||||
| 			} | ||||
| 			watchAdd(cur, c, eventset) | ||||
| 			// TODO(rjeczalik): account top-most path for c | ||||
| 			return nil | ||||
| 		} | ||||
| 		if !self { | ||||
| 			watchAdd(cur, c, eventset) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	// case 2: cur is new parent | ||||
| 	// | ||||
| 	// Look for children nodes, unwatch n-1 of them and rewatch the last one. | ||||
| 	var children []node | ||||
| 	fn := func(nd node) error { | ||||
| 		if len(nd.Watch) == 0 { | ||||
| 			return nil | ||||
| 		} | ||||
| 		children = append(children, nd) | ||||
| 		return errSkip | ||||
| 	} | ||||
| 	switch must(cur.Walk(fn)); len(children) { | ||||
| 	case 0: | ||||
| 		// no child watches, cur holds a new watch | ||||
| 	case 1: | ||||
| 		watchAdd(cur, c, eventset) // TODO(rjeczalik): update cache c subtree root? | ||||
| 		watchCopy(children[0], cur) | ||||
| 		err = t.w.RecursiveRewatch(children[0].Name, cur.Name, watchTotal(children[0]), | ||||
| 			watchTotal(cur)) | ||||
| 		if err != nil { | ||||
| 			// Clean inactive watchpoint. The c chan did not exist before. | ||||
| 			cur.Child[""] = node{} | ||||
| 			delete(cur.Watch, c) | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	default: | ||||
| 		watchAdd(cur, c, eventset) | ||||
| 		// Copy children inactive watchpoints to the new parent. | ||||
| 		for _, nd := range children { | ||||
| 			watchCopy(nd, cur) | ||||
| 		} | ||||
| 		// Watch parent subtree. | ||||
| 		if err = t.w.RecursiveWatch(cur.Name, watchTotal(cur)); err != nil { | ||||
| 			// Clean inactive watchpoint. The c chan did not exist before. | ||||
| 			cur.Child[""] = node{} | ||||
| 			delete(cur.Watch, c) | ||||
| 			return err | ||||
| 		} | ||||
| 		// Unwatch children subtrees. | ||||
| 		var e error | ||||
| 		for _, nd := range children { | ||||
| 			if watchIsRecursive(nd) { | ||||
| 				e = t.w.RecursiveUnwatch(nd.Name) | ||||
| 			} else { | ||||
| 				e = t.w.Unwatch(nd.Name) | ||||
| 			} | ||||
| 			if e != nil { | ||||
| 				err = nonil(err, e) | ||||
| 				// TODO(rjeczalik): child is still watched, warn all its watchpoints | ||||
| 				// about possible duplicate events via Error event | ||||
| 			} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	// case 3: cur is new, alone node | ||||
| 	switch diff := watchAdd(cur, c, eventset); { | ||||
| 	case diff == none: | ||||
| 		// TODO(rjeczalik): cleanup this panic after implementation is stable | ||||
| 		panic("watch requested but no parent watchpoint found: " + cur.Name) | ||||
| 	case diff[0] == 0: | ||||
| 		if isrec { | ||||
| 			err = t.w.RecursiveWatch(cur.Name, diff[1]) | ||||
| 		} else { | ||||
| 			err = t.w.Watch(cur.Name, diff[1]) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			watchDel(cur, c, diff.Event()) | ||||
| 			return err | ||||
| 		} | ||||
| 	default: | ||||
| 		// TODO(rjeczalik): cleanup this panic after implementation is stable | ||||
| 		panic("watch requested but no parent watchpoint found: " + cur.Name) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Stop TODO(rjeczalik) | ||||
| // | ||||
| // TODO(rjeczalik): Split parent watchpoint - transfer watches to children | ||||
| // if parent is no longer needed. This carries a risk that underlying | ||||
| // watcher calls could fail - reconsider if it's worth the effort. | ||||
| func (t *recursiveTree) Stop(c chan<- EventInfo) { | ||||
| 	var err error | ||||
| 	fn := func(nd node) (e error) { | ||||
| 		diff := watchDel(nd, c, all) | ||||
| 		switch { | ||||
| 		case diff == none && watchTotal(nd) == 0: | ||||
| 			// TODO(rjeczalik): There's no watchpoints deeper in the tree, | ||||
| 			// probably we should remove the nodes as well. | ||||
| 			return nil | ||||
| 		case diff == none: | ||||
| 			// Removing c from nd does not require shrinking its eventset. | ||||
| 		case diff[1] == 0: | ||||
| 			if watchIsRecursive(nd) { | ||||
| 				e = t.w.RecursiveUnwatch(nd.Name) | ||||
| 			} else { | ||||
| 				e = t.w.Unwatch(nd.Name) | ||||
| 			} | ||||
| 		default: | ||||
| 			if watchIsRecursive(nd) { | ||||
| 				e = t.w.RecursiveRewatch(nd.Name, nd.Name, diff[0], diff[1]) | ||||
| 			} else { | ||||
| 				e = t.w.Rewatch(nd.Name, diff[0], diff[1]) | ||||
| 			} | ||||
| 		} | ||||
| 		fn := func(nd node) error { | ||||
| 			watchDel(nd, c, all) | ||||
| 			return nil | ||||
| 		} | ||||
| 		err = nonil(err, e, nd.Walk(fn)) | ||||
| 		// TODO(rjeczalik): if e != nil store dummy chan in nd.Watch just to | ||||
| 		// retry un/rewatching next time and/or let the user handle the failure | ||||
| 		// vie Error event? | ||||
| 		return errSkip | ||||
| 	} | ||||
| 	t.rw.Lock() | ||||
| 	e := t.root.Walk("", fn) // TODO(rjeczalik): use max root per c | ||||
| 	t.rw.Unlock() | ||||
| 	if e != nil { | ||||
| 		err = nonil(err, e) | ||||
| 	} | ||||
| 	dbgprintf("Stop(%p) error: %v\n", c, err) | ||||
| } | ||||
| 
 | ||||
| // Close TODO(rjeczalik) | ||||
| func (t *recursiveTree) Close() error { | ||||
| 	err := t.w.Close() | ||||
| 	close(t.c) | ||||
| 	return err | ||||
| } | ||||
							
								
								
									
										150
									
								
								vendor/github.com/rjeczalik/notify/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								vendor/github.com/rjeczalik/notify/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,150 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| const all = ^Event(0) | ||||
| const sep = string(os.PathSeparator) | ||||
| 
 | ||||
| var errDepth = errors.New("exceeded allowed iteration count (circular symlink?)") | ||||
| 
 | ||||
| func min(i, j int) int { | ||||
| 	if i > j { | ||||
| 		return j | ||||
| 	} | ||||
| 	return i | ||||
| } | ||||
| 
 | ||||
| func max(i, j int) int { | ||||
| 	if i < j { | ||||
| 		return j | ||||
| 	} | ||||
| 	return i | ||||
| } | ||||
| 
 | ||||
| // must panics if err is non-nil. | ||||
| func must(err error) { | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // nonil gives first non-nil error from the given arguments. | ||||
| func nonil(err ...error) error { | ||||
| 	for _, err := range err { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func cleanpath(path string) (realpath string, isrec bool, err error) { | ||||
| 	if strings.HasSuffix(path, "...") { | ||||
| 		isrec = true | ||||
| 		path = path[:len(path)-3] | ||||
| 	} | ||||
| 	if path, err = filepath.Abs(path); err != nil { | ||||
| 		return "", false, err | ||||
| 	} | ||||
| 	if path, err = canonical(path); err != nil { | ||||
| 		return "", false, err | ||||
| 	} | ||||
| 	return path, isrec, nil | ||||
| } | ||||
| 
 | ||||
| // canonical resolves any symlink in the given path and returns it in a clean form. | ||||
| // It expects the path to be absolute. It fails to resolve circular symlinks by | ||||
| // maintaining a simple iteration limit. | ||||
| func canonical(p string) (string, error) { | ||||
| 	p, err := filepath.Abs(p) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	for i, j, depth := 1, 0, 1; i < len(p); i, depth = i+1, depth+1 { | ||||
| 		if depth > 128 { | ||||
| 			return "", &os.PathError{Op: "canonical", Path: p, Err: errDepth} | ||||
| 		} | ||||
| 		if j = strings.IndexRune(p[i:], '/'); j == -1 { | ||||
| 			j, i = i, len(p) | ||||
| 		} else { | ||||
| 			j, i = i, i+j | ||||
| 		} | ||||
| 		fi, err := os.Lstat(p[:i]) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if fi.Mode()&os.ModeSymlink == os.ModeSymlink { | ||||
| 			s, err := os.Readlink(p[:i]) | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 			if filepath.IsAbs(s) { | ||||
| 				p = "/" + s + p[i:] | ||||
| 			} else { | ||||
| 				p = p[:j] + s + p[i:] | ||||
| 			} | ||||
| 			i = 1 // no guarantee s is canonical, start all over | ||||
| 		} | ||||
| 	} | ||||
| 	return filepath.Clean(p), nil | ||||
| } | ||||
| 
 | ||||
| func joinevents(events []Event) (e Event) { | ||||
| 	if len(events) == 0 { | ||||
| 		e = All | ||||
| 	} else { | ||||
| 		for _, event := range events { | ||||
| 			e |= event | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func split(s string) (string, string) { | ||||
| 	if i := lastIndexSep(s); i != -1 { | ||||
| 		return s[:i], s[i+1:] | ||||
| 	} | ||||
| 	return "", s | ||||
| } | ||||
| 
 | ||||
| func base(s string) string { | ||||
| 	if i := lastIndexSep(s); i != -1 { | ||||
| 		return s[i+1:] | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
| 
 | ||||
| func indexbase(root, name string) int { | ||||
| 	if n, m := len(root), len(name); m >= n && name[:n] == root && | ||||
| 		(n == m || name[n] == os.PathSeparator) { | ||||
| 		return min(n+1, m) | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
| 
 | ||||
| func indexSep(s string) int { | ||||
| 	for i := 0; i < len(s); i++ { | ||||
| 		if s[i] == os.PathSeparator { | ||||
| 			return i | ||||
| 		} | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
| 
 | ||||
| func lastIndexSep(s string) int { | ||||
| 	for i := len(s) - 1; i >= 0; i-- { | ||||
| 		if s[i] == os.PathSeparator { | ||||
| 			return i | ||||
| 		} | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
							
								
								
									
										85
									
								
								vendor/github.com/rjeczalik/notify/watcher.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								vendor/github.com/rjeczalik/notify/watcher.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import "errors" | ||||
| 
 | ||||
| var ( | ||||
| 	errAlreadyWatched  = errors.New("path is already watched") | ||||
| 	errNotWatched      = errors.New("path is not being watched") | ||||
| 	errInvalidEventSet = errors.New("invalid event set provided") | ||||
| ) | ||||
| 
 | ||||
| // Watcher is a intermediate interface for wrapping inotify, ReadDirChangesW, | ||||
| // FSEvents, kqueue and poller implementations. | ||||
| // | ||||
| // The watcher implementation is expected to do its own mapping between paths and | ||||
| // create watchers if underlying event notification does not support it. For | ||||
| // the ease of implementation it is guaranteed that paths provided via Watch and | ||||
| // Unwatch methods are absolute and clean. | ||||
| type watcher interface { | ||||
| 	// Watch requests a watcher creation for the given path and given event set. | ||||
| 	Watch(path string, event Event) error | ||||
| 
 | ||||
| 	// Unwatch requests a watcher deletion for the given path and given event set. | ||||
| 	Unwatch(path string) error | ||||
| 
 | ||||
| 	// Rewatch provides a functionality for modifying existing watch-points, like | ||||
| 	// expanding its event set. | ||||
| 	// | ||||
| 	// Rewatch modifies existing watch-point under for the given path. It passes | ||||
| 	// the existing event set currently registered for the given path, and the | ||||
| 	// new, requested event set. | ||||
| 	// | ||||
| 	// It is guaranteed that Tree will not pass to Rewatch zero value for any | ||||
| 	// of its arguments. If old == new and watcher can be upgraded to | ||||
| 	// recursiveWatcher interface, a watch for the corresponding path is expected | ||||
| 	// to be changed from recursive to the non-recursive one. | ||||
| 	Rewatch(path string, old, new Event) error | ||||
| 
 | ||||
| 	// Close unwatches all paths that are registered. When Close returns, it | ||||
| 	// is expected it will report no more events. | ||||
| 	Close() error | ||||
| } | ||||
| 
 | ||||
| // RecursiveWatcher is an interface for a Watcher for those OS, which do support | ||||
| // recursive watching over directories. | ||||
| type recursiveWatcher interface { | ||||
| 	RecursiveWatch(path string, event Event) error | ||||
| 
 | ||||
| 	// RecursiveUnwatch removes a recursive watch-point given by the path. For | ||||
| 	// native recursive implementation there is no difference in functionality | ||||
| 	// between Unwatch and RecursiveUnwatch, however for those platforms, that | ||||
| 	// requires emulation for recursive watch-points, the implementation differs. | ||||
| 	RecursiveUnwatch(path string) error | ||||
| 
 | ||||
| 	// RecursiveRewatcher provides a functionality for modifying and/or relocating | ||||
| 	// existing recursive watch-points. | ||||
| 	// | ||||
| 	// To relocate a watch-point means to unwatch oldpath and set a watch-point on | ||||
| 	// newpath. | ||||
| 	// | ||||
| 	// To modify a watch-point means either to expand or shrink its event set. | ||||
| 	// | ||||
| 	// Tree can want to either relocate, modify or relocate and modify a watch-point | ||||
| 	// via single RecursiveRewatch call. | ||||
| 	// | ||||
| 	// If oldpath == newpath, the watch-point is expected to change its event set value | ||||
| 	// from oldevent to newevent. | ||||
| 	// | ||||
| 	// If oldevent == newevent, the watch-point is expected to relocate from oldpath | ||||
| 	// to the newpath. | ||||
| 	// | ||||
| 	// If oldpath != newpath and oldevent != newevent, the watch-point is expected | ||||
| 	// to relocate from oldpath to the newpath first and then change its event set | ||||
| 	// value from oldevent to the newevent. In other words the end result must be | ||||
| 	// a watch-point set on newpath with newevent value of its event set. | ||||
| 	// | ||||
| 	// It is guaranteed that Tree will not pass to RecurisveRewatcha zero value | ||||
| 	// for any of its arguments. If oldpath == newpath and oldevent == newevent, | ||||
| 	// a watch for the corresponding path is expected to be changed for | ||||
| 	// non-recursive to the recursive one. | ||||
| 	RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error | ||||
| } | ||||
							
								
								
									
										161
									
								
								vendor/github.com/rjeczalik/notify/watcher_fen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								vendor/github.com/rjeczalik/notify/watcher_fen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,161 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build solaris | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
| ) | ||||
| 
 | ||||
| // newTrigger returns implementation of trigger. | ||||
| func newTrigger(pthLkp map[string]*watched) trigger { | ||||
| 	return &fen{ | ||||
| 		pthLkp: pthLkp, | ||||
| 		cf:     newCfen(), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // fen is a structure implementing trigger for FEN. | ||||
| type fen struct { | ||||
| 	// p is a FEN port identifier | ||||
| 	p int | ||||
| 	// pthLkp is a structure mapping monitored files/dir with data about them, | ||||
| 	// shared with parent trg structure | ||||
| 	pthLkp map[string]*watched | ||||
| 	// cf wraps C operations for FEN | ||||
| 	cf cfen | ||||
| } | ||||
| 
 | ||||
| // watched is a data structure representing watched file/directory. | ||||
| type watched struct { | ||||
| 	trgWatched | ||||
| } | ||||
| 
 | ||||
| // Stop implements trigger. | ||||
| func (f *fen) Stop() error { | ||||
| 	return f.cf.portAlert(f.p) | ||||
| } | ||||
| 
 | ||||
| // Close implements trigger. | ||||
| func (f *fen) Close() (err error) { | ||||
| 	return syscall.Close(f.p) | ||||
| } | ||||
| 
 | ||||
| // NewWatched implements trigger. | ||||
| func (*fen) NewWatched(p string, fi os.FileInfo) (*watched, error) { | ||||
| 	return &watched{trgWatched{p: p, fi: fi}}, nil | ||||
| } | ||||
| 
 | ||||
| // Record implements trigger. | ||||
| func (f *fen) Record(w *watched) { | ||||
| 	f.pthLkp[w.p] = w | ||||
| } | ||||
| 
 | ||||
| // Del implements trigger. | ||||
| func (f *fen) Del(w *watched) { | ||||
| 	delete(f.pthLkp, w.p) | ||||
| } | ||||
| 
 | ||||
| func inter2pe(n interface{}) PortEvent { | ||||
| 	pe, ok := n.(PortEvent) | ||||
| 	if !ok { | ||||
| 		panic(fmt.Sprintf("fen: type should be PortEvent, %T instead", n)) | ||||
| 	} | ||||
| 	return pe | ||||
| } | ||||
| 
 | ||||
| // Watched implements trigger. | ||||
| func (f *fen) Watched(n interface{}) (*watched, int64, error) { | ||||
| 	pe := inter2pe(n) | ||||
| 	fo, ok := pe.PortevObject.(*FileObj) | ||||
| 	if !ok || fo == nil { | ||||
| 		panic(fmt.Sprintf("fen: type should be *FileObj, %T instead", fo)) | ||||
| 	} | ||||
| 	w, ok := f.pthLkp[fo.Name] | ||||
| 	if !ok { | ||||
| 		return nil, 0, errNotWatched | ||||
| 	} | ||||
| 	return w, int64(pe.PortevEvents), nil | ||||
| } | ||||
| 
 | ||||
| // init initializes FEN. | ||||
| func (f *fen) Init() (err error) { | ||||
| 	f.p, err = f.cf.portCreate() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func fi2fo(fi os.FileInfo, p string) FileObj { | ||||
| 	st, ok := fi.Sys().(*syscall.Stat_t) | ||||
| 	if !ok { | ||||
| 		panic(fmt.Sprintf("fen: type should be *syscall.Stat_t, %T instead", st)) | ||||
| 	} | ||||
| 	return FileObj{Name: p, Atim: st.Atim, Mtim: st.Mtim, Ctim: st.Ctim} | ||||
| } | ||||
| 
 | ||||
| // Unwatch implements trigger. | ||||
| func (f *fen) Unwatch(w *watched) error { | ||||
| 	return f.cf.portDissociate(f.p, FileObj{Name: w.p}) | ||||
| } | ||||
| 
 | ||||
| // Watch implements trigger. | ||||
| func (f *fen) Watch(fi os.FileInfo, w *watched, e int64) error { | ||||
| 	return f.cf.portAssociate(f.p, fi2fo(fi, w.p), int(e)) | ||||
| } | ||||
| 
 | ||||
| // Wait implements trigger. | ||||
| func (f *fen) Wait() (interface{}, error) { | ||||
| 	var ( | ||||
| 		pe  PortEvent | ||||
| 		err error | ||||
| 	) | ||||
| 	err = f.cf.portGet(f.p, &pe) | ||||
| 	return pe, err | ||||
| } | ||||
| 
 | ||||
| // IsStop implements trigger. | ||||
| func (f *fen) IsStop(n interface{}, err error) bool { | ||||
| 	return err == syscall.EBADF || inter2pe(n).PortevSource == srcAlert | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	encode = func(e Event, dir bool) (o int64) { | ||||
| 		// Create event is not supported by FEN. Instead FileModified event will | ||||
| 		// be registered. If this event will be reported on dir which is to be | ||||
| 		// monitored for Create, dir will be rescanned and Create events will | ||||
| 		// be generated and returned for new files. In case of files, | ||||
| 		// if not requested FileModified event is reported, it will be ignored. | ||||
| 		o = int64(e &^ Create) | ||||
| 		if (e&Create != 0 && dir) || e&Write != 0 { | ||||
| 			o = (o &^ int64(Write)) | int64(FileModified) | ||||
| 		} | ||||
| 		// Following events are 'exception events' and as such cannot be requested | ||||
| 		// explicitly for monitoring or filtered out. If the will be reported | ||||
| 		// by FEN and not subscribed with by user, they will be filtered out by | ||||
| 		// watcher's logic. | ||||
| 		o &= int64(^Rename & ^Remove &^ FileDelete &^ FileRenameTo &^ | ||||
| 			FileRenameFrom &^ Unmounted &^ MountedOver) | ||||
| 		return | ||||
| 	} | ||||
| 	nat2not = map[Event]Event{ | ||||
| 		FileModified:   Write, | ||||
| 		FileRenameFrom: Rename, | ||||
| 		FileDelete:     Remove, | ||||
| 		FileAccess:     Event(0), | ||||
| 		FileAttrib:     Event(0), | ||||
| 		FileRenameTo:   Event(0), | ||||
| 		FileTrunc:      Event(0), | ||||
| 		FileNoFollow:   Event(0), | ||||
| 		Unmounted:      Event(0), | ||||
| 		MountedOver:    Event(0), | ||||
| 	} | ||||
| 	not2nat = map[Event]Event{ | ||||
| 		Write:  FileModified, | ||||
| 		Rename: FileRenameFrom, | ||||
| 		Remove: FileDelete, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										141
									
								
								vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build solaris | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| // #include <port.h> | ||||
| // #include <stdio.h> | ||||
| // #include <stdlib.h> | ||||
| // struct file_obj* newFo() { return (struct file_obj*) malloc(sizeof(struct file_obj)); } | ||||
| // port_event_t* newPe() { return (port_event_t*) malloc(sizeof(port_event_t)); } | ||||
| // uintptr_t conv(struct file_obj* fo) { return (uintptr_t) fo; } | ||||
| // struct file_obj* dconv(uintptr_t fo) { return (struct file_obj*) fo; } | ||||
| import "C" | ||||
| 
 | ||||
| import ( | ||||
| 	"syscall" | ||||
| 	"unsafe" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	fileAccess     = Event(C.FILE_ACCESS) | ||||
| 	fileModified   = Event(C.FILE_MODIFIED) | ||||
| 	fileAttrib     = Event(C.FILE_ATTRIB) | ||||
| 	fileDelete     = Event(C.FILE_DELETE) | ||||
| 	fileRenameTo   = Event(C.FILE_RENAME_TO) | ||||
| 	fileRenameFrom = Event(C.FILE_RENAME_FROM) | ||||
| 	fileTrunc      = Event(C.FILE_TRUNC) | ||||
| 	fileNoFollow   = Event(C.FILE_NOFOLLOW) | ||||
| 	unmounted      = Event(C.UNMOUNTED) | ||||
| 	mountedOver    = Event(C.MOUNTEDOVER) | ||||
| ) | ||||
| 
 | ||||
| // PortEvent is a notify's equivalent of port_event_t. | ||||
| type PortEvent struct { | ||||
| 	PortevEvents int         // PortevEvents is an equivalent of portev_events. | ||||
| 	PortevSource uint8       // PortevSource is an equivalent of portev_source. | ||||
| 	PortevPad    uint8       // Portevpad is an equivalent of portev_pad. | ||||
| 	PortevObject interface{} // PortevObject is an equivalent of portev_object. | ||||
| 	PortevUser   uintptr     // PortevUser is an equivalent of portev_user. | ||||
| } | ||||
| 
 | ||||
| // FileObj is a notify's equivalent of file_obj. | ||||
| type FileObj struct { | ||||
| 	Atim syscall.Timespec // Atim is an equivalent of fo_atime. | ||||
| 	Mtim syscall.Timespec // Mtim is an equivalent of fo_mtime. | ||||
| 	Ctim syscall.Timespec // Ctim is an equivalent of fo_ctime. | ||||
| 	Pad  [3]uintptr       // Pad is an equivalent of fo_pad. | ||||
| 	Name string           // Name is an equivalent of fo_name. | ||||
| } | ||||
| 
 | ||||
| type cfen struct { | ||||
| 	p2pe map[string]*C.port_event_t | ||||
| 	p2fo map[string]*C.struct_file_obj | ||||
| } | ||||
| 
 | ||||
| func newCfen() cfen { | ||||
| 	return cfen{ | ||||
| 		p2pe: make(map[string]*C.port_event_t), | ||||
| 		p2fo: make(map[string]*C.struct_file_obj), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func unix2C(sec int64, nsec int64) (C.time_t, C.long) { | ||||
| 	return C.time_t(sec), C.long(nsec) | ||||
| } | ||||
| 
 | ||||
| func (c *cfen) portAssociate(p int, fo FileObj, e int) (err error) { | ||||
| 	cfo := C.newFo() | ||||
| 	cfo.fo_atime.tv_sec, cfo.fo_atime.tv_nsec = unix2C(fo.Atim.Unix()) | ||||
| 	cfo.fo_mtime.tv_sec, cfo.fo_mtime.tv_nsec = unix2C(fo.Mtim.Unix()) | ||||
| 	cfo.fo_ctime.tv_sec, cfo.fo_ctime.tv_nsec = unix2C(fo.Ctim.Unix()) | ||||
| 	cfo.fo_name = C.CString(fo.Name) | ||||
| 	c.p2fo[fo.Name] = cfo | ||||
| 	_, err = C.port_associate(C.int(p), srcFile, C.conv(cfo), C.int(e), nil) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (c *cfen) portDissociate(port int, fo FileObj) (err error) { | ||||
| 	cfo, ok := c.p2fo[fo.Name] | ||||
| 	if !ok { | ||||
| 		return errNotWatched | ||||
| 	} | ||||
| 	_, err = C.port_dissociate(C.int(port), srcFile, C.conv(cfo)) | ||||
| 	C.free(unsafe.Pointer(cfo.fo_name)) | ||||
| 	C.free(unsafe.Pointer(cfo)) | ||||
| 	delete(c.p2fo, fo.Name) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| const srcAlert = C.PORT_SOURCE_ALERT | ||||
| const srcFile = C.PORT_SOURCE_FILE | ||||
| const alertSet = C.PORT_ALERT_SET | ||||
| 
 | ||||
| func cfo2fo(cfo *C.struct_file_obj) *FileObj { | ||||
| 	// Currently remaining attributes are not used. | ||||
| 	if cfo == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	var fo FileObj | ||||
| 	fo.Name = C.GoString(cfo.fo_name) | ||||
| 	return &fo | ||||
| } | ||||
| 
 | ||||
| func (c *cfen) portGet(port int, pe *PortEvent) (err error) { | ||||
| 	cpe := C.newPe() | ||||
| 	if _, err = C.port_get(C.int(port), cpe, nil); err != nil { | ||||
| 		C.free(unsafe.Pointer(cpe)) | ||||
| 		return | ||||
| 	} | ||||
| 	pe.PortevEvents, pe.PortevSource, pe.PortevPad = | ||||
| 		int(cpe.portev_events), uint8(cpe.portev_source), uint8(cpe.portev_pad) | ||||
| 	pe.PortevObject = cfo2fo(C.dconv(cpe.portev_object)) | ||||
| 	pe.PortevUser = uintptr(cpe.portev_user) | ||||
| 	C.free(unsafe.Pointer(cpe)) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (c *cfen) portCreate() (int, error) { | ||||
| 	p, err := C.port_create() | ||||
| 	return int(p), err | ||||
| } | ||||
| 
 | ||||
| func (c *cfen) portAlert(p int) (err error) { | ||||
| 	_, err = C.port_alert(C.int(p), alertSet, C.int(666), nil) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (c *cfen) free() { | ||||
| 	for i := range c.p2fo { | ||||
| 		C.free(unsafe.Pointer(c.p2fo[i].fo_name)) | ||||
| 		C.free(unsafe.Pointer(c.p2fo[i])) | ||||
| 	} | ||||
| 	for i := range c.p2pe { | ||||
| 		C.free(unsafe.Pointer(c.p2pe[i])) | ||||
| 	} | ||||
| 	c.p2fo = make(map[string]*C.struct_file_obj) | ||||
| 	c.p2pe = make(map[string]*C.port_event_t) | ||||
| } | ||||
							
								
								
									
										311
									
								
								vendor/github.com/rjeczalik/notify/watcher_fsevents.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								vendor/github.com/rjeczalik/notify/watcher_fsevents.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,311 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build darwin,!kqueue | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"strings" | ||||
| 	"sync/atomic" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped) | ||||
| 	filter  = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed | | ||||
| 		FSEventsModified | FSEventsInodeMetaMod) | ||||
| ) | ||||
| 
 | ||||
| // FSEvent represents single file event. It is created out of values passed by | ||||
| // FSEvents to FSEventStreamCallback function. | ||||
| type FSEvent struct { | ||||
| 	Path  string // real path of the file or directory | ||||
| 	ID    uint64 // ID of the event (FSEventStreamEventId) | ||||
| 	Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags) | ||||
| } | ||||
| 
 | ||||
| // splitflags separates event flags from single set into slice of flags. | ||||
| func splitflags(set uint32) (e []uint32) { | ||||
| 	for i := uint32(1); set != 0; i, set = i<<1, set>>1 { | ||||
| 		if (set & 1) != 0 { | ||||
| 			e = append(e, i) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // watch represents a filesystem watchpoint. It is a higher level abstraction | ||||
| // over FSEvents' stream, which implements filtering of file events based | ||||
| // on path and event set. It emulates non-recursive watch-point by filtering out | ||||
| // events which paths are more than 1 level deeper than the watched path. | ||||
| type watch struct { | ||||
| 	// prev stores last event set  per path in order to filter out old flags | ||||
| 	// for new events, which appratenly FSEvents likes to retain. It's a disgusting | ||||
| 	// hack, it should be researched how to get rid of it. | ||||
| 	prev    map[string]uint32 | ||||
| 	c       chan<- EventInfo | ||||
| 	stream  *stream | ||||
| 	path    string | ||||
| 	events  uint32 | ||||
| 	isrec   int32 | ||||
| 	flushed bool | ||||
| } | ||||
| 
 | ||||
| // Example format: | ||||
| // | ||||
| //   ~ $ (trigger command) # (event set) -> (effective event set) | ||||
| // | ||||
| // Heuristics: | ||||
| // | ||||
| // 1. Create event is removed when it was present in previous event set. | ||||
| // Example: | ||||
| // | ||||
| //   ~ $ echo > file # Create|Write -> Create|Write | ||||
| //   ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod | ||||
| // | ||||
| // 2. Remove event is removed if it was present in previouse event set. | ||||
| // Example: | ||||
| // | ||||
| //   ~ $ touch file # Create -> Create | ||||
| //   ~ $ rm file    # Create|Remove -> Remove | ||||
| //   ~ $ touch file # Create|Remove -> Create | ||||
| // | ||||
| // 3. Write event is removed if not followed by InodeMetaMod on existing | ||||
| // file. Example: | ||||
| // | ||||
| //   ~ $ echo > file   # Create|Write -> Create|Write | ||||
| //   ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner | ||||
| // | ||||
| // 4. Write&InodeMetaMod is removed when effective event set contain Remove event. | ||||
| // Example: | ||||
| // | ||||
| //   ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod | ||||
| //   ~ $ rm file     # Remove|Write|InodeMetaMod -> Remove | ||||
| // | ||||
| func (w *watch) strip(base string, set uint32) uint32 { | ||||
| 	const ( | ||||
| 		write = FSEventsModified | FSEventsInodeMetaMod | ||||
| 		both  = FSEventsCreated | FSEventsRemoved | ||||
| 	) | ||||
| 	switch w.prev[base] { | ||||
| 	case FSEventsCreated: | ||||
| 		set &^= FSEventsCreated | ||||
| 		if set&FSEventsRemoved != 0 { | ||||
| 			w.prev[base] = FSEventsRemoved | ||||
| 			set &^= write | ||||
| 		} | ||||
| 	case FSEventsRemoved: | ||||
| 		set &^= FSEventsRemoved | ||||
| 		if set&FSEventsCreated != 0 { | ||||
| 			w.prev[base] = FSEventsCreated | ||||
| 		} | ||||
| 	default: | ||||
| 		switch set & both { | ||||
| 		case FSEventsCreated: | ||||
| 			w.prev[base] = FSEventsCreated | ||||
| 		case FSEventsRemoved: | ||||
| 			w.prev[base] = FSEventsRemoved | ||||
| 			set &^= write | ||||
| 		} | ||||
| 	} | ||||
| 	dbgprintf("split()=%v\n", Event(set)) | ||||
| 	return set | ||||
| } | ||||
| 
 | ||||
| // Dispatch is a stream function which forwards given file events for the watched | ||||
| // path to underlying FileInfo channel. | ||||
| func (w *watch) Dispatch(ev []FSEvent) { | ||||
| 	events := atomic.LoadUint32(&w.events) | ||||
| 	isrec := (atomic.LoadInt32(&w.isrec) == 1) | ||||
| 	for i := range ev { | ||||
| 		if ev[i].Flags&FSEventsHistoryDone != 0 { | ||||
| 			w.flushed = true | ||||
| 			continue | ||||
| 		} | ||||
| 		if !w.flushed { | ||||
| 			continue | ||||
| 		} | ||||
| 		dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags), | ||||
| 			ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev)) | ||||
| 		if ev[i].Flags&failure != 0 { | ||||
| 			// TODO(rjeczalik): missing error handling | ||||
| 			continue | ||||
| 		} | ||||
| 		if !strings.HasPrefix(ev[i].Path, w.path) { | ||||
| 			continue | ||||
| 		} | ||||
| 		n := len(w.path) | ||||
| 		base := "" | ||||
| 		if len(ev[i].Path) > n { | ||||
| 			if ev[i].Path[n] != '/' { | ||||
| 				continue | ||||
| 			} | ||||
| 			base = ev[i].Path[n+1:] | ||||
| 			if !isrec && strings.IndexByte(base, '/') != -1 { | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 		// TODO(rjeczalik): get diff only from filtered events? | ||||
| 		e := w.strip(string(base), ev[i].Flags) & events | ||||
| 		if e == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		for _, e := range splitflags(e) { | ||||
| 			dbgprintf("%d: single event: %v", ev[i].ID, Event(e)) | ||||
| 			w.c <- &event{ | ||||
| 				fse:   ev[i], | ||||
| 				event: Event(e), | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Stop closes underlying FSEvents stream and stops dispatching events. | ||||
| func (w *watch) Stop() { | ||||
| 	w.stream.Stop() | ||||
| 	// TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events, | ||||
| 	// so the following hack can be removed. It should flush all the streams | ||||
| 	// concurrently as we care not to block too much here. | ||||
| 	atomic.StoreUint32(&w.events, 0) | ||||
| 	atomic.StoreInt32(&w.isrec, 0) | ||||
| } | ||||
| 
 | ||||
| // fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents | ||||
| // framework. | ||||
| type fsevents struct { | ||||
| 	watches map[string]*watch | ||||
| 	c       chan<- EventInfo | ||||
| } | ||||
| 
 | ||||
| func newWatcher(c chan<- EventInfo) watcher { | ||||
| 	return &fsevents{ | ||||
| 		watches: make(map[string]*watch), | ||||
| 		c:       c, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) { | ||||
| 	if _, ok := fse.watches[path]; ok { | ||||
| 		return errAlreadyWatched | ||||
| 	} | ||||
| 	w := &watch{ | ||||
| 		prev:   make(map[string]uint32), | ||||
| 		c:      fse.c, | ||||
| 		path:   path, | ||||
| 		events: uint32(event), | ||||
| 		isrec:  isrec, | ||||
| 	} | ||||
| 	w.stream = newStream(path, w.Dispatch) | ||||
| 	if err = w.stream.Start(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fse.watches[path] = w | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (fse *fsevents) unwatch(path string) (err error) { | ||||
| 	w, ok := fse.watches[path] | ||||
| 	if !ok { | ||||
| 		return errNotWatched | ||||
| 	} | ||||
| 	w.stream.Stop() | ||||
| 	delete(fse.watches, path) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Watch implements Watcher interface. It fails with non-nil error when setting | ||||
| // the watch-point by FSEvents fails or with errAlreadyWatched error when | ||||
| // the given path is already watched. | ||||
| func (fse *fsevents) Watch(path string, event Event) error { | ||||
| 	return fse.watch(path, event, 0) | ||||
| } | ||||
| 
 | ||||
| // Unwatch implements Watcher interface. It fails with errNotWatched when | ||||
| // the given path is not being watched. | ||||
| func (fse *fsevents) Unwatch(path string) error { | ||||
| 	return fse.unwatch(path) | ||||
| } | ||||
| 
 | ||||
| // Rewatch implements Watcher interface. It fails with errNotWatched when | ||||
| // the given path is not being watched or with errInvalidEventSet when oldevent | ||||
| // does not match event set the watch-point currently holds. | ||||
| func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error { | ||||
| 	w, ok := fse.watches[path] | ||||
| 	if !ok { | ||||
| 		return errNotWatched | ||||
| 	} | ||||
| 	if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) { | ||||
| 		return errInvalidEventSet | ||||
| 	} | ||||
| 	atomic.StoreInt32(&w.isrec, 0) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil | ||||
| // error when setting the watch-point by FSEvents fails or with errAlreadyWatched | ||||
| // error when the given path is already watched. | ||||
| func (fse *fsevents) RecursiveWatch(path string, event Event) error { | ||||
| 	return fse.watch(path, event, 1) | ||||
| } | ||||
| 
 | ||||
| // RecursiveUnwatch implements RecursiveWatcher interface. It fails with | ||||
| // errNotWatched when the given path is not being watched. | ||||
| // | ||||
| // TODO(rjeczalik): fail if w.isrec == 0? | ||||
| func (fse *fsevents) RecursiveUnwatch(path string) error { | ||||
| 	return fse.unwatch(path) | ||||
| } | ||||
| 
 | ||||
| // RecrusiveRewatch implements RecursiveWatcher interface. It fails: | ||||
| // | ||||
| //   * with errNotWatched when the given path is not being watched | ||||
| //   * with errInvalidEventSet when oldevent does not match the current event set | ||||
| //   * with errAlreadyWatched when watch-point given by the oldpath was meant to | ||||
| //     be relocated to newpath, but the newpath is already watched | ||||
| //   * a non-nil error when setting the watch-point with FSEvents fails | ||||
| // | ||||
| // TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs | ||||
| // that follows. | ||||
| func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error { | ||||
| 	switch [2]bool{oldpath == newpath, oldevent == newevent} { | ||||
| 	case [2]bool{true, true}: | ||||
| 		w, ok := fse.watches[oldpath] | ||||
| 		if !ok { | ||||
| 			return errNotWatched | ||||
| 		} | ||||
| 		atomic.StoreInt32(&w.isrec, 1) | ||||
| 		return nil | ||||
| 	case [2]bool{true, false}: | ||||
| 		w, ok := fse.watches[oldpath] | ||||
| 		if !ok { | ||||
| 			return errNotWatched | ||||
| 		} | ||||
| 		if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) { | ||||
| 			return errors.New("invalid event state diff") | ||||
| 		} | ||||
| 		atomic.StoreInt32(&w.isrec, 1) | ||||
| 		return nil | ||||
| 	default: | ||||
| 		// TODO(rjeczalik): rewatch newpath only if exists? | ||||
| 		// TODO(rjeczalik): migrate w.prev to new watch? | ||||
| 		if _, ok := fse.watches[newpath]; ok { | ||||
| 			return errAlreadyWatched | ||||
| 		} | ||||
| 		if err := fse.Unwatch(oldpath); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// TODO(rjeczalik): revert unwatch if watch fails? | ||||
| 		return fse.watch(newpath, newevent, 1) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Close unwatches all watch-points. | ||||
| func (fse *fsevents) Close() error { | ||||
| 	for _, w := range fse.watches { | ||||
| 		w.Stop() | ||||
| 	} | ||||
| 	fse.watches = nil | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										193
									
								
								vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build darwin,!kqueue | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| /* | ||||
| #include <CoreServices/CoreServices.h> | ||||
| 
 | ||||
| typedef void (*CFRunLoopPerformCallBack)(void*); | ||||
| 
 | ||||
| void gosource(void *); | ||||
| void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t); | ||||
| 
 | ||||
| static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) { | ||||
| 	context->info = (void*) info; | ||||
| 	return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags); | ||||
| } | ||||
| 
 | ||||
| #cgo LDFLAGS: -framework CoreServices | ||||
| */ | ||||
| import "C" | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"unsafe" | ||||
| ) | ||||
| 
 | ||||
| var nilstream C.FSEventStreamRef | ||||
| 
 | ||||
| // Default arguments for FSEventStreamCreate function. | ||||
| var ( | ||||
| 	latency C.CFTimeInterval | ||||
| 	flags   = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer) | ||||
| 	since   = uint64(C.FSEventsGetCurrentEventId()) | ||||
| ) | ||||
| 
 | ||||
| var runloop C.CFRunLoopRef // global runloop which all streams are registered with | ||||
| var wg sync.WaitGroup      // used to wait until the runloop starts | ||||
| 
 | ||||
| // source is used for synchronization purposes - it signals when runloop has | ||||
| // started and is ready via the wg. It also serves purpose of a dummy source, | ||||
| // thanks to it the runloop does not return as it also has at least one source | ||||
| // registered. | ||||
| var source = C.CFRunLoopSourceCreate(nil, 0, &C.CFRunLoopSourceContext{ | ||||
| 	perform: (C.CFRunLoopPerformCallBack)(C.gosource), | ||||
| }) | ||||
| 
 | ||||
| // Errors returned when FSEvents functions fail. | ||||
| var ( | ||||
| 	errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL")) | ||||
| 	errStart  = os.NewSyscallError("FSEventStreamStart", errors.New("false")) | ||||
| ) | ||||
| 
 | ||||
| // initializes the global runloop and ensures any created stream awaits its | ||||
| // readiness. | ||||
| func init() { | ||||
| 	wg.Add(1) | ||||
| 	go func() { | ||||
| 		// There is exactly one run loop per thread. Lock this goroutine to its | ||||
| 		// thread to ensure that it's not rescheduled on a different thread while | ||||
| 		// setting up the run loop. | ||||
| 		runtime.LockOSThread() | ||||
| 		runloop = C.CFRunLoopGetCurrent() | ||||
| 		C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode) | ||||
| 		C.CFRunLoopRun() | ||||
| 		panic("runloop has just unexpectedly stopped") | ||||
| 	}() | ||||
| 	C.CFRunLoopSourceSignal(source) | ||||
| } | ||||
| 
 | ||||
| //export gosource | ||||
| func gosource(unsafe.Pointer) { | ||||
| 	wg.Done() | ||||
| } | ||||
| 
 | ||||
| //export gostream | ||||
| func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) { | ||||
| 	const ( | ||||
| 		offchar = unsafe.Sizeof((*C.char)(nil)) | ||||
| 		offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0)) | ||||
| 		offid   = unsafe.Sizeof(C.FSEventStreamEventId(0)) | ||||
| 	) | ||||
| 	if n == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	ev := make([]FSEvent, 0, int(n)) | ||||
| 	for i := uintptr(0); i < uintptr(n); i++ { | ||||
| 		switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); { | ||||
| 		case flags&uint32(FSEventsEventIdsWrapped) != 0: | ||||
| 			atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId())) | ||||
| 		default: | ||||
| 			ev = append(ev, FSEvent{ | ||||
| 				Path:  C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))), | ||||
| 				Flags: flags, | ||||
| 				ID:    *(*uint64)(unsafe.Pointer(ids + i*offid)), | ||||
| 			}) | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 	streamFuncs.get(info)(ev) | ||||
| } | ||||
| 
 | ||||
| // StreamFunc is a callback called when stream receives file events. | ||||
| type streamFunc func([]FSEvent) | ||||
| 
 | ||||
| var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}} | ||||
| 
 | ||||
| type streamFuncRegistry struct { | ||||
| 	mu sync.Mutex | ||||
| 	m  map[uintptr]streamFunc | ||||
| 	i  uintptr | ||||
| } | ||||
| 
 | ||||
| func (r *streamFuncRegistry) get(id uintptr) streamFunc { | ||||
| 	r.mu.Lock() | ||||
| 	defer r.mu.Unlock() | ||||
| 	return r.m[id] | ||||
| } | ||||
| 
 | ||||
| func (r *streamFuncRegistry) add(fn streamFunc) uintptr { | ||||
| 	r.mu.Lock() | ||||
| 	defer r.mu.Unlock() | ||||
| 	r.i++ | ||||
| 	r.m[r.i] = fn | ||||
| 	return r.i | ||||
| } | ||||
| 
 | ||||
| func (r *streamFuncRegistry) delete(id uintptr) { | ||||
| 	r.mu.Lock() | ||||
| 	defer r.mu.Unlock() | ||||
| 	delete(r.m, id) | ||||
| } | ||||
| 
 | ||||
| // Stream represents single watch-point which listens for events scheduled by | ||||
| // the global runloop. | ||||
| type stream struct { | ||||
| 	path string | ||||
| 	ref  C.FSEventStreamRef | ||||
| 	info uintptr | ||||
| } | ||||
| 
 | ||||
| // NewStream creates a stream for given path, listening for file events and | ||||
| // calling fn upon receiving any. | ||||
| func newStream(path string, fn streamFunc) *stream { | ||||
| 	return &stream{ | ||||
| 		path: path, | ||||
| 		info: streamFuncs.add(fn), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Start creates a FSEventStream for the given path and schedules it with | ||||
| // global runloop. It's a nop if the stream was already started. | ||||
| func (s *stream) Start() error { | ||||
| 	if s.ref != nilstream { | ||||
| 		return nil | ||||
| 	} | ||||
| 	wg.Wait() | ||||
| 	p := C.CFStringCreateWithCStringNoCopy(nil, C.CString(s.path), C.kCFStringEncodingUTF8, nil) | ||||
| 	path := C.CFArrayCreate(nil, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil) | ||||
| 	ctx := C.FSEventStreamContext{} | ||||
| 	ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags) | ||||
| 	if ref == nilstream { | ||||
| 		return errCreate | ||||
| 	} | ||||
| 	C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode) | ||||
| 	if C.FSEventStreamStart(ref) == C.Boolean(0) { | ||||
| 		C.FSEventStreamInvalidate(ref) | ||||
| 		return errStart | ||||
| 	} | ||||
| 	C.CFRunLoopWakeUp(runloop) | ||||
| 	s.ref = ref | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Stop stops underlying FSEventStream and unregisters it from global runloop. | ||||
| func (s *stream) Stop() { | ||||
| 	if s.ref == nilstream { | ||||
| 		return | ||||
| 	} | ||||
| 	wg.Wait() | ||||
| 	C.FSEventStreamStop(s.ref) | ||||
| 	C.FSEventStreamInvalidate(s.ref) | ||||
| 	C.CFRunLoopWakeUp(runloop) | ||||
| 	s.ref = nilstream | ||||
| 	streamFuncs.delete(s.info) | ||||
| } | ||||
							
								
								
									
										405
									
								
								vendor/github.com/rjeczalik/notify/watcher_inotify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										405
									
								
								vendor/github.com/rjeczalik/notify/watcher_inotify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,405 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build linux | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"unsafe" | ||||
| 
 | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
| 
 | ||||
| // eventBufferSize defines the size of the buffer given to read(2) function. One | ||||
| // should not depend on this value, since it was arbitrary chosen and may be | ||||
| // changed in the future. | ||||
| const eventBufferSize = 64 * (unix.SizeofInotifyEvent + unix.PathMax + 1) | ||||
| 
 | ||||
| // consumersCount defines the number of consumers in producer-consumer based | ||||
| // implementation. Each consumer is run in a separate goroutine and has read | ||||
| // access to watched files map. | ||||
| const consumersCount = 2 | ||||
| 
 | ||||
| const invalidDescriptor = -1 | ||||
| 
 | ||||
| // watched is a pair of file path and inotify mask used as a value in | ||||
| // watched files map. | ||||
| type watched struct { | ||||
| 	path string | ||||
| 	mask uint32 | ||||
| } | ||||
| 
 | ||||
| // inotify implements Watcher interface. | ||||
| type inotify struct { | ||||
| 	sync.RWMutex                       // protects inotify.m map | ||||
| 	m            map[int32]*watched    // watch descriptor to watched object | ||||
| 	fd           int32                 // inotify file descriptor | ||||
| 	pipefd       []int                 // pipe's read and write descriptors | ||||
| 	epfd         int                   // epoll descriptor | ||||
| 	epes         []unix.EpollEvent     // epoll events | ||||
| 	buffer       [eventBufferSize]byte // inotify event buffer | ||||
| 	wg           sync.WaitGroup        // wait group used to close main loop | ||||
| 	c            chan<- EventInfo      // event dispatcher channel | ||||
| } | ||||
| 
 | ||||
| // NewWatcher creates new non-recursive inotify backed by inotify. | ||||
| func newWatcher(c chan<- EventInfo) watcher { | ||||
| 	i := &inotify{ | ||||
| 		m:      make(map[int32]*watched), | ||||
| 		fd:     invalidDescriptor, | ||||
| 		pipefd: []int{invalidDescriptor, invalidDescriptor}, | ||||
| 		epfd:   invalidDescriptor, | ||||
| 		epes:   make([]unix.EpollEvent, 0), | ||||
| 		c:      c, | ||||
| 	} | ||||
| 	runtime.SetFinalizer(i, func(i *inotify) { | ||||
| 		i.epollclose() | ||||
| 		if i.fd != invalidDescriptor { | ||||
| 			unix.Close(int(i.fd)) | ||||
| 		} | ||||
| 	}) | ||||
| 	return i | ||||
| } | ||||
| 
 | ||||
| // Watch implements notify.watcher interface. | ||||
| func (i *inotify) Watch(path string, e Event) error { | ||||
| 	return i.watch(path, e) | ||||
| } | ||||
| 
 | ||||
| // Rewatch implements notify.watcher interface. | ||||
| func (i *inotify) Rewatch(path string, _, newevent Event) error { | ||||
| 	return i.watch(path, newevent) | ||||
| } | ||||
| 
 | ||||
| // watch adds a new watcher to the set of watched objects or modifies the existing | ||||
| // one. If called for the first time, this function initializes inotify filesystem | ||||
| // monitor and starts producer-consumers goroutines. | ||||
| func (i *inotify) watch(path string, e Event) (err error) { | ||||
| 	if e&^(All|Event(unix.IN_ALL_EVENTS)) != 0 { | ||||
| 		return errors.New("notify: unknown event") | ||||
| 	} | ||||
| 	if err = i.lazyinit(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	iwd, err := unix.InotifyAddWatch(int(i.fd), path, encode(e)) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	i.RLock() | ||||
| 	wd := i.m[int32(iwd)] | ||||
| 	i.RUnlock() | ||||
| 	if wd == nil { | ||||
| 		i.Lock() | ||||
| 		if i.m[int32(iwd)] == nil { | ||||
| 			i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)} | ||||
| 		} | ||||
| 		i.Unlock() | ||||
| 	} else { | ||||
| 		i.Lock() | ||||
| 		wd.mask = uint32(e) | ||||
| 		i.Unlock() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // lazyinit sets up all required file descriptors and starts 1+consumersCount | ||||
| // goroutines. The producer goroutine blocks until file-system notifications | ||||
| // occur. Then, all events are read from system buffer and sent to consumer | ||||
| // goroutines which construct valid notify events. This method uses | ||||
| // Double-Checked Locking optimization. | ||||
| func (i *inotify) lazyinit() error { | ||||
| 	if atomic.LoadInt32(&i.fd) == invalidDescriptor { | ||||
| 		i.Lock() | ||||
| 		defer i.Unlock() | ||||
| 		if atomic.LoadInt32(&i.fd) == invalidDescriptor { | ||||
| 			fd, err := unix.InotifyInit1(unix.IN_CLOEXEC) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			i.fd = int32(fd) | ||||
| 			if err = i.epollinit(); err != nil { | ||||
| 				_, _ = i.epollclose(), unix.Close(int(fd)) // Ignore errors. | ||||
| 				i.fd = invalidDescriptor | ||||
| 				return err | ||||
| 			} | ||||
| 			esch := make(chan []*event) | ||||
| 			go i.loop(esch) | ||||
| 			i.wg.Add(consumersCount) | ||||
| 			for n := 0; n < consumersCount; n++ { | ||||
| 				go i.send(esch) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // epollinit opens an epoll file descriptor and creates a pipe which will be | ||||
| // used to wake up the epoll_wait(2) function. Then, file descriptor associated | ||||
| // with inotify event queue and the read end of the pipe are added to epoll set. | ||||
| // Note that `fd` member must be set before this function is called. | ||||
| func (i *inotify) epollinit() (err error) { | ||||
| 	if i.epfd, err = unix.EpollCreate1(0); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = unix.Pipe(i.pipefd); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	i.epes = []unix.EpollEvent{ | ||||
| 		{Events: unix.EPOLLIN, Fd: i.fd}, | ||||
| 		{Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])}, | ||||
| 	} | ||||
| 	if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1]) | ||||
| } | ||||
| 
 | ||||
| // epollclose closes the file descriptor created by the call to epoll_create(2) | ||||
| // and two file descriptors opened by pipe(2) function. | ||||
| func (i *inotify) epollclose() (err error) { | ||||
| 	if i.epfd != invalidDescriptor { | ||||
| 		if err = unix.Close(i.epfd); err == nil { | ||||
| 			i.epfd = invalidDescriptor | ||||
| 		} | ||||
| 	} | ||||
| 	for n, fd := range i.pipefd { | ||||
| 		if fd != invalidDescriptor { | ||||
| 			switch e := unix.Close(fd); { | ||||
| 			case e != nil && err == nil: | ||||
| 				err = e | ||||
| 			case e == nil: | ||||
| 				i.pipefd[n] = invalidDescriptor | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // loop blocks until either inotify or pipe file descriptor is ready for I/O. | ||||
| // All read operations triggered by filesystem notifications are forwarded to | ||||
| // one of the event's consumers. If pipe fd became ready, loop function closes | ||||
| // all file descriptors opened by lazyinit method and returns afterwards. | ||||
| func (i *inotify) loop(esch chan<- []*event) { | ||||
| 	epes := make([]unix.EpollEvent, 1) | ||||
| 	fd := atomic.LoadInt32(&i.fd) | ||||
| 	for { | ||||
| 		switch _, err := unix.EpollWait(i.epfd, epes, -1); err { | ||||
| 		case nil: | ||||
| 			switch epes[0].Fd { | ||||
| 			case fd: | ||||
| 				esch <- i.read() | ||||
| 				epes[0].Fd = 0 | ||||
| 			case int32(i.pipefd[0]): | ||||
| 				i.Lock() | ||||
| 				defer i.Unlock() | ||||
| 				if err = unix.Close(int(fd)); err != nil && err != unix.EINTR { | ||||
| 					panic("notify: close(2) error " + err.Error()) | ||||
| 				} | ||||
| 				atomic.StoreInt32(&i.fd, invalidDescriptor) | ||||
| 				if err = i.epollclose(); err != nil && err != unix.EINTR { | ||||
| 					panic("notify: epollclose error " + err.Error()) | ||||
| 				} | ||||
| 				close(esch) | ||||
| 				return | ||||
| 			} | ||||
| 		case unix.EINTR: | ||||
| 			continue | ||||
| 		default: // We should never reach this line. | ||||
| 			panic("notify: epoll_wait(2) error " + err.Error()) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // read reads events from an inotify file descriptor. It does not handle errors | ||||
| // returned from read(2) function since they are not critical to watcher logic. | ||||
| func (i *inotify) read() (es []*event) { | ||||
| 	n, err := unix.Read(int(i.fd), i.buffer[:]) | ||||
| 	if err != nil || n < unix.SizeofInotifyEvent { | ||||
| 		return | ||||
| 	} | ||||
| 	var sys *unix.InotifyEvent | ||||
| 	nmin := n - unix.SizeofInotifyEvent | ||||
| 	for pos, path := 0, ""; pos <= nmin; { | ||||
| 		sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos])) | ||||
| 		pos += unix.SizeofInotifyEvent | ||||
| 		if path = ""; sys.Len > 0 { | ||||
| 			endpos := pos + int(sys.Len) | ||||
| 			path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00")) | ||||
| 			pos = endpos | ||||
| 		} | ||||
| 		es = append(es, &event{ | ||||
| 			sys: unix.InotifyEvent{ | ||||
| 				Wd:     sys.Wd, | ||||
| 				Mask:   sys.Mask, | ||||
| 				Cookie: sys.Cookie, | ||||
| 			}, | ||||
| 			path: path, | ||||
| 		}) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // send is a consumer function which sends events to event dispatcher channel. | ||||
| // It is run in a separate goroutine in order to not block loop method when | ||||
| // possibly expensive write operations are performed on inotify map. | ||||
| func (i *inotify) send(esch <-chan []*event) { | ||||
| 	for es := range esch { | ||||
| 		for _, e := range i.transform(es) { | ||||
| 			if e != nil { | ||||
| 				i.c <- e | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	i.wg.Done() | ||||
| } | ||||
| 
 | ||||
| // transform prepares events read from inotify file descriptor for sending to | ||||
| // user. It removes invalid events and these which are no longer present in | ||||
| // inotify map. This method may also split one raw event into two different ones | ||||
| // when system-dependent result is required. | ||||
| func (i *inotify) transform(es []*event) []*event { | ||||
| 	var multi []*event | ||||
| 	i.RLock() | ||||
| 	for idx, e := range es { | ||||
| 		if e.sys.Mask&(unix.IN_IGNORED|unix.IN_Q_OVERFLOW) != 0 { | ||||
| 			es[idx] = nil | ||||
| 			continue | ||||
| 		} | ||||
| 		wd, ok := i.m[e.sys.Wd] | ||||
| 		if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 { | ||||
| 			es[idx] = nil | ||||
| 			continue | ||||
| 		} | ||||
| 		if e.path == "" { | ||||
| 			e.path = wd.path | ||||
| 		} else { | ||||
| 			e.path = filepath.Join(wd.path, e.path) | ||||
| 		} | ||||
| 		multi = append(multi, decode(Event(wd.mask), e)) | ||||
| 		if e.event == 0 { | ||||
| 			es[idx] = nil | ||||
| 		} | ||||
| 	} | ||||
| 	i.RUnlock() | ||||
| 	es = append(es, multi...) | ||||
| 	return es | ||||
| } | ||||
| 
 | ||||
| // encode converts notify system-independent events to valid inotify mask | ||||
| // which can be passed to inotify_add_watch(2) function. | ||||
| func encode(e Event) uint32 { | ||||
| 	if e&Create != 0 { | ||||
| 		e = (e ^ Create) | InCreate | InMovedTo | ||||
| 	} | ||||
| 	if e&Remove != 0 { | ||||
| 		e = (e ^ Remove) | InDelete | InDeleteSelf | ||||
| 	} | ||||
| 	if e&Write != 0 { | ||||
| 		e = (e ^ Write) | InModify | ||||
| 	} | ||||
| 	if e&Rename != 0 { | ||||
| 		e = (e ^ Rename) | InMovedFrom | InMoveSelf | ||||
| 	} | ||||
| 	return uint32(e) | ||||
| } | ||||
| 
 | ||||
| // decode uses internally stored mask to distinguish whether system-independent | ||||
| // or system-dependent event is requested. The first one is created by modifying | ||||
| // `e` argument. decode method sets e.event value to 0 when an event should be | ||||
| // skipped. System-dependent event is set as the function's return value which | ||||
| // can be nil when the event should not be passed on. | ||||
| func decode(mask Event, e *event) (syse *event) { | ||||
| 	if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 { | ||||
| 		syse = &event{sys: unix.InotifyEvent{ | ||||
| 			Wd:     e.sys.Wd, | ||||
| 			Mask:   e.sys.Mask, | ||||
| 			Cookie: e.sys.Cookie, | ||||
| 		}, event: Event(sysmask), path: e.path} | ||||
| 	} | ||||
| 	imask := encode(mask) | ||||
| 	switch { | ||||
| 	case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0: | ||||
| 		e.event = Create | ||||
| 	case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0: | ||||
| 		e.event = Remove | ||||
| 	case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0: | ||||
| 		e.event = Write | ||||
| 	case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0: | ||||
| 		e.event = Rename | ||||
| 	default: | ||||
| 		e.event = 0 | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Unwatch implements notify.watcher interface. It looks for watch descriptor | ||||
| // related to registered path and if found, calls inotify_rm_watch(2) function. | ||||
| // This method is allowed to return EINVAL error when concurrently requested to | ||||
| // delete identical path. | ||||
| func (i *inotify) Unwatch(path string) (err error) { | ||||
| 	iwd := int32(invalidDescriptor) | ||||
| 	i.RLock() | ||||
| 	for iwdkey, wd := range i.m { | ||||
| 		if wd.path == path { | ||||
| 			iwd = iwdkey | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	i.RUnlock() | ||||
| 	if iwd == invalidDescriptor { | ||||
| 		return errors.New("notify: path " + path + " is already watched") | ||||
| 	} | ||||
| 	fd := atomic.LoadInt32(&i.fd) | ||||
| 	if err = removeInotifyWatch(fd, iwd); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	i.Lock() | ||||
| 	delete(i.m, iwd) | ||||
| 	i.Unlock() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Close implements notify.watcher interface. It removes all existing watch | ||||
| // descriptors and wakes up producer goroutine by sending data to the write end | ||||
| // of the pipe. The function waits for a signal from producer which means that | ||||
| // all operations on current monitoring instance are done. | ||||
| func (i *inotify) Close() (err error) { | ||||
| 	i.Lock() | ||||
| 	if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor { | ||||
| 		i.Unlock() | ||||
| 		return nil | ||||
| 	} | ||||
| 	for iwd := range i.m { | ||||
| 		if e := removeInotifyWatch(i.fd, iwd); e != nil && err == nil { | ||||
| 			err = e | ||||
| 		} | ||||
| 		delete(i.m, iwd) | ||||
| 	} | ||||
| 	switch _, errwrite := unix.Write(i.pipefd[1], []byte{0x00}); { | ||||
| 	case errwrite != nil && err == nil: | ||||
| 		err = errwrite | ||||
| 		fallthrough | ||||
| 	case errwrite != nil: | ||||
| 		i.Unlock() | ||||
| 	default: | ||||
| 		i.Unlock() | ||||
| 		i.wg.Wait() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // if path was removed, notify already removed the watch and returns EINVAL error | ||||
| func removeInotifyWatch(fd int32, iwd int32) (err error) { | ||||
| 	if _, err = unix.InotifyRmWatch(int(fd), uint32(iwd)); err != nil && err != unix.EINVAL { | ||||
| 		return | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										189
									
								
								vendor/github.com/rjeczalik/notify/watcher_kqueue.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								vendor/github.com/rjeczalik/notify/watcher_kqueue.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,189 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build darwin,kqueue dragonfly freebsd netbsd openbsd | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
| ) | ||||
| 
 | ||||
| // newTrigger returns implementation of trigger. | ||||
| func newTrigger(pthLkp map[string]*watched) trigger { | ||||
| 	return &kq{ | ||||
| 		pthLkp: pthLkp, | ||||
| 		idLkp:  make(map[int]*watched), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // kq is a structure implementing trigger for kqueue. | ||||
| type kq struct { | ||||
| 	// fd is a kqueue file descriptor | ||||
| 	fd int | ||||
| 	// pipefds are file descriptors used to stop `Kevent` call. | ||||
| 	pipefds [2]int | ||||
| 	// idLkp is a data structure mapping file descriptors with data about watching | ||||
| 	// represented by them files/directories. | ||||
| 	idLkp map[int]*watched | ||||
| 	// pthLkp is a structure mapping monitored files/dir with data about them, | ||||
| 	// shared with parent trg structure | ||||
| 	pthLkp map[string]*watched | ||||
| } | ||||
| 
 | ||||
| // watched is a data structure representing watched file/directory. | ||||
| type watched struct { | ||||
| 	trgWatched | ||||
| 	// fd is a file descriptor for watched file/directory. | ||||
| 	fd int | ||||
| } | ||||
| 
 | ||||
| // Stop implements trigger. | ||||
| func (k *kq) Stop() (err error) { | ||||
| 	// trigger event used to interrupt Kevent call. | ||||
| 	_, err = syscall.Write(k.pipefds[1], []byte{0x00}) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Close implements trigger. | ||||
| func (k *kq) Close() error { | ||||
| 	return syscall.Close(k.fd) | ||||
| } | ||||
| 
 | ||||
| // NewWatched implements trigger. | ||||
| func (*kq) NewWatched(p string, fi os.FileInfo) (*watched, error) { | ||||
| 	fd, err := syscall.Open(p, syscall.O_NONBLOCK|syscall.O_RDONLY, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &watched{ | ||||
| 		trgWatched: trgWatched{p: p, fi: fi}, | ||||
| 		fd:         fd, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // Record implements trigger. | ||||
| func (k *kq) Record(w *watched) { | ||||
| 	k.idLkp[w.fd], k.pthLkp[w.p] = w, w | ||||
| } | ||||
| 
 | ||||
| // Del implements trigger. | ||||
| func (k *kq) Del(w *watched) { | ||||
| 	syscall.Close(w.fd) | ||||
| 	delete(k.idLkp, w.fd) | ||||
| 	delete(k.pthLkp, w.p) | ||||
| } | ||||
| 
 | ||||
| func inter2kq(n interface{}) syscall.Kevent_t { | ||||
| 	kq, ok := n.(syscall.Kevent_t) | ||||
| 	if !ok { | ||||
| 		panic(fmt.Sprintf("kqueue: type should be Kevent_t, %T instead", n)) | ||||
| 	} | ||||
| 	return kq | ||||
| } | ||||
| 
 | ||||
| // Init implements trigger. | ||||
| func (k *kq) Init() (err error) { | ||||
| 	if k.fd, err = syscall.Kqueue(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	// Creates pipe used to stop `Kevent` call by registering it, | ||||
| 	// watching read end and writing to other end of it. | ||||
| 	if err = syscall.Pipe(k.pipefds[:]); err != nil { | ||||
| 		return nonil(err, k.Close()) | ||||
| 	} | ||||
| 	var kevn [1]syscall.Kevent_t | ||||
| 	syscall.SetKevent(&kevn[0], k.pipefds[0], syscall.EVFILT_READ, syscall.EV_ADD) | ||||
| 	if _, err = syscall.Kevent(k.fd, kevn[:], nil, nil); err != nil { | ||||
| 		return nonil(err, k.Close()) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Unwatch implements trigger. | ||||
| func (k *kq) Unwatch(w *watched) (err error) { | ||||
| 	var kevn [1]syscall.Kevent_t | ||||
| 	syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, syscall.EV_DELETE) | ||||
| 
 | ||||
| 	_, err = syscall.Kevent(k.fd, kevn[:], nil, nil) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Watch implements trigger. | ||||
| func (k *kq) Watch(fi os.FileInfo, w *watched, e int64) (err error) { | ||||
| 	var kevn [1]syscall.Kevent_t | ||||
| 	syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, | ||||
| 		syscall.EV_ADD|syscall.EV_CLEAR) | ||||
| 	kevn[0].Fflags = uint32(e) | ||||
| 
 | ||||
| 	_, err = syscall.Kevent(k.fd, kevn[:], nil, nil) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Wait implements trigger. | ||||
| func (k *kq) Wait() (interface{}, error) { | ||||
| 	var ( | ||||
| 		kevn [1]syscall.Kevent_t | ||||
| 		err  error | ||||
| 	) | ||||
| 	kevn[0] = syscall.Kevent_t{} | ||||
| 	_, err = syscall.Kevent(k.fd, nil, kevn[:], nil) | ||||
| 
 | ||||
| 	return kevn[0], err | ||||
| } | ||||
| 
 | ||||
| // Watched implements trigger. | ||||
| func (k *kq) Watched(n interface{}) (*watched, int64, error) { | ||||
| 	kevn, ok := n.(syscall.Kevent_t) | ||||
| 	if !ok { | ||||
| 		panic(fmt.Sprintf("kq: type should be syscall.Kevent_t, %T instead", kevn)) | ||||
| 	} | ||||
| 	if _, ok = k.idLkp[int(kevn.Ident)]; !ok { | ||||
| 		return nil, 0, errNotWatched | ||||
| 	} | ||||
| 	return k.idLkp[int(kevn.Ident)], int64(kevn.Fflags), nil | ||||
| } | ||||
| 
 | ||||
| // IsStop implements trigger. | ||||
| func (k *kq) IsStop(n interface{}, err error) bool { | ||||
| 	return int(inter2kq(n).Ident) == k.pipefds[0] | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	encode = func(e Event, dir bool) (o int64) { | ||||
| 		// Create event is not supported by kqueue. Instead NoteWrite event will | ||||
| 		// be registered for a directory. If this event will be reported on dir | ||||
| 		// which is to be monitored for Create, dir will be rescanned | ||||
| 		// and Create events will be generated and returned for new files. | ||||
| 		// In case of files, if not requested NoteRename event is reported, | ||||
| 		// it will be ignored. | ||||
| 		o = int64(e &^ Create) | ||||
| 		if (e&Create != 0 && dir) || e&Write != 0 { | ||||
| 			o = (o &^ int64(Write)) | int64(NoteWrite) | ||||
| 		} | ||||
| 		if e&Rename != 0 { | ||||
| 			o = (o &^ int64(Rename)) | int64(NoteRename) | ||||
| 		} | ||||
| 		if e&Remove != 0 { | ||||
| 			o = (o &^ int64(Remove)) | int64(NoteDelete) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	nat2not = map[Event]Event{ | ||||
| 		NoteWrite:  Write, | ||||
| 		NoteRename: Rename, | ||||
| 		NoteDelete: Remove, | ||||
| 		NoteExtend: Event(0), | ||||
| 		NoteAttrib: Event(0), | ||||
| 		NoteRevoke: Event(0), | ||||
| 		NoteLink:   Event(0), | ||||
| 	} | ||||
| 	not2nat = map[Event]Event{ | ||||
| 		Write:  NoteWrite, | ||||
| 		Rename: NoteRename, | ||||
| 		Remove: NoteDelete, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										582
									
								
								vendor/github.com/rjeczalik/notify/watcher_readdcw.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								vendor/github.com/rjeczalik/notify/watcher_readdcw.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,582 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build windows | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"syscall" | ||||
| 	"unsafe" | ||||
| ) | ||||
| 
 | ||||
| // readBufferSize defines the size of an array in which read statuses are stored. | ||||
| // The buffer have to be DWORD-aligned and, if notify is used in monitoring a | ||||
| // directory over the network, its size must not be greater than 64KB. Each of | ||||
| // watched directories uses its own buffer for storing events. | ||||
| const readBufferSize = 4096 | ||||
| 
 | ||||
| // Since all operations which go through the Windows completion routine are done | ||||
| // asynchronously, filter may set one of the constants belor. They were defined | ||||
| // in order to distinguish whether current folder should be re-registered in | ||||
| // ReadDirectoryChangesW function or some control operations need to be executed. | ||||
| const ( | ||||
| 	stateRewatch uint32 = 1 << (28 + iota) | ||||
| 	stateUnwatch | ||||
| 	stateCPClose | ||||
| ) | ||||
| 
 | ||||
| // Filter used in current implementation was split into four segments: | ||||
| //  - bits  0-11 store ReadDirectoryChangesW filters, | ||||
| //  - bits 12-19 store File notify actions, | ||||
| //  - bits 20-27 store notify specific events and flags, | ||||
| //  - bits 28-31 store states which are used in loop's FSM. | ||||
| // Constants below are used as masks to retrieve only specific filter parts. | ||||
| const ( | ||||
| 	onlyNotifyChanges uint32 = 0x00000FFF | ||||
| 	onlyNGlobalEvents uint32 = 0x0FF00000 | ||||
| 	onlyMachineStates uint32 = 0xF0000000 | ||||
| ) | ||||
| 
 | ||||
| // grip represents a single watched directory. It stores the data required by | ||||
| // ReadDirectoryChangesW function. Only the filter, recursive, and handle members | ||||
| // may by modified by watcher implementation. Rest of the them have to remain | ||||
| // constant since they are used by Windows completion routine. This indicates that | ||||
| // grip can be removed only when all operations on the file handle are finished. | ||||
| type grip struct { | ||||
| 	handle    syscall.Handle | ||||
| 	filter    uint32 | ||||
| 	recursive bool | ||||
| 	pathw     []uint16 | ||||
| 	buffer    [readBufferSize]byte | ||||
| 	parent    *watched | ||||
| 	ovlapped  *overlappedEx | ||||
| } | ||||
| 
 | ||||
| // overlappedEx stores information used in asynchronous input and output. | ||||
| // Additionally, overlappedEx contains a pointer to 'grip' item which is used in | ||||
| // order to gather the structure in which the overlappedEx object was created. | ||||
| type overlappedEx struct { | ||||
| 	syscall.Overlapped | ||||
| 	parent *grip | ||||
| } | ||||
| 
 | ||||
| // newGrip creates a new file handle that can be used in overlapped operations. | ||||
| // Then, the handle is associated with I/O completion port 'cph' and its value | ||||
| // is stored in newly created 'grip' object. | ||||
| func newGrip(cph syscall.Handle, parent *watched, filter uint32) (*grip, error) { | ||||
| 	g := &grip{ | ||||
| 		handle:    syscall.InvalidHandle, | ||||
| 		filter:    filter, | ||||
| 		recursive: parent.recursive, | ||||
| 		pathw:     parent.pathw, | ||||
| 		parent:    parent, | ||||
| 		ovlapped:  &overlappedEx{}, | ||||
| 	} | ||||
| 	if err := g.register(cph); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	g.ovlapped.parent = g | ||||
| 	return g, nil | ||||
| } | ||||
| 
 | ||||
| // NOTE : Thread safe | ||||
| func (g *grip) register(cph syscall.Handle) (err error) { | ||||
| 	if g.handle, err = syscall.CreateFile( | ||||
| 		&g.pathw[0], | ||||
| 		syscall.FILE_LIST_DIRECTORY, | ||||
| 		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, | ||||
| 		nil, | ||||
| 		syscall.OPEN_EXISTING, | ||||
| 		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, | ||||
| 		0, | ||||
| 	); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if _, err = syscall.CreateIoCompletionPort(g.handle, cph, 0, 0); err != nil { | ||||
| 		syscall.CloseHandle(g.handle) | ||||
| 		return | ||||
| 	} | ||||
| 	return g.readDirChanges() | ||||
| } | ||||
| 
 | ||||
| // readDirChanges tells the system to store file change information in grip's | ||||
| // buffer. Directory changes that occur between calls to this function are added | ||||
| // to the buffer and then, returned with the next call. | ||||
| func (g *grip) readDirChanges() error { | ||||
| 	return syscall.ReadDirectoryChanges( | ||||
| 		g.handle, | ||||
| 		&g.buffer[0], | ||||
| 		uint32(unsafe.Sizeof(g.buffer)), | ||||
| 		g.recursive, | ||||
| 		encode(g.filter), | ||||
| 		nil, | ||||
| 		(*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)), | ||||
| 		0, | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| // encode transforms a generic filter, which contains platform independent and | ||||
| // implementation specific bit fields, to value that can be used as NotifyFilter | ||||
| // parameter in ReadDirectoryChangesW function. | ||||
| func encode(filter uint32) uint32 { | ||||
| 	e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges)) | ||||
| 	if e&dirmarker != 0 { | ||||
| 		return uint32(FileNotifyChangeDirName) | ||||
| 	} | ||||
| 	if e&Create != 0 { | ||||
| 		e = (e ^ Create) | FileNotifyChangeFileName | ||||
| 	} | ||||
| 	if e&Remove != 0 { | ||||
| 		e = (e ^ Remove) | FileNotifyChangeFileName | ||||
| 	} | ||||
| 	if e&Write != 0 { | ||||
| 		e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize | | ||||
| 			FileNotifyChangeCreation | FileNotifyChangeSecurity | ||||
| 	} | ||||
| 	if e&Rename != 0 { | ||||
| 		e = (e ^ Rename) | FileNotifyChangeFileName | ||||
| 	} | ||||
| 	return uint32(e) | ||||
| } | ||||
| 
 | ||||
| // watched is made in order to check whether an action comes from a directory or | ||||
| // file. This approach requires two file handlers per single monitored folder. The | ||||
| // second grip handles actions which include creating or deleting a directory. If | ||||
| // these processes are not monitored, only the first grip is created. | ||||
| type watched struct { | ||||
| 	filter    uint32 | ||||
| 	recursive bool | ||||
| 	count     uint8 | ||||
| 	pathw     []uint16 | ||||
| 	digrip    [2]*grip | ||||
| } | ||||
| 
 | ||||
| // newWatched creates a new watched instance. It splits the filter variable into | ||||
| // two parts. The first part is responsible for watching all events which can be | ||||
| // created for a file in watched directory structure and the second one watches | ||||
| // only directory Create/Remove actions. If all operations succeed, the Create | ||||
| // message is sent to I/O completion port queue for further processing. | ||||
| func newWatched(cph syscall.Handle, filter uint32, recursive bool, | ||||
| 	path string) (wd *watched, err error) { | ||||
| 	wd = &watched{ | ||||
| 		filter:    filter, | ||||
| 		recursive: recursive, | ||||
| 	} | ||||
| 	if wd.pathw, err = syscall.UTF16FromString(path); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = wd.recreate(cph); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return wd, nil | ||||
| } | ||||
| 
 | ||||
| // TODO : doc | ||||
| func (wd *watched) recreate(cph syscall.Handle) (err error) { | ||||
| 	filefilter := wd.filter &^ uint32(FileNotifyChangeDirName) | ||||
| 	if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove) | ||||
| 	if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	wd.filter &^= onlyMachineStates | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // TODO : doc | ||||
| func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool, | ||||
| 	newflag uint32) (err error) { | ||||
| 	if reset { | ||||
| 		wd.digrip[idx] = nil | ||||
| 	} else { | ||||
| 		if wd.digrip[idx] == nil { | ||||
| 			if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil { | ||||
| 				wd.closeHandle() | ||||
| 				return | ||||
| 			} | ||||
| 		} else { | ||||
| 			wd.digrip[idx].filter = newflag | ||||
| 			wd.digrip[idx].recursive = wd.recursive | ||||
| 			if err = wd.digrip[idx].register(cph); err != nil { | ||||
| 				wd.closeHandle() | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		wd.count++ | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // closeHandle closes handles that are stored in digrip array. Function always | ||||
| // tries to close all of the handlers before it exits, even when there are errors | ||||
| // returned from the operating system kernel. | ||||
| func (wd *watched) closeHandle() (err error) { | ||||
| 	for _, g := range wd.digrip { | ||||
| 		if g != nil && g.handle != syscall.InvalidHandle { | ||||
| 			switch suberr := syscall.CloseHandle(g.handle); { | ||||
| 			case suberr == nil: | ||||
| 				g.handle = syscall.InvalidHandle | ||||
| 			case err == nil: | ||||
| 				err = suberr | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // watcher implements Watcher interface. It stores a set of watched directories. | ||||
| // All operations which remove watched objects from map `m` must be performed in | ||||
| // loop goroutine since these structures are used internally by operating system. | ||||
| type readdcw struct { | ||||
| 	sync.Mutex | ||||
| 	m     map[string]*watched | ||||
| 	cph   syscall.Handle | ||||
| 	start bool | ||||
| 	wg    sync.WaitGroup | ||||
| 	c     chan<- EventInfo | ||||
| } | ||||
| 
 | ||||
| // NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW. | ||||
| func newWatcher(c chan<- EventInfo) watcher { | ||||
| 	r := &readdcw{ | ||||
| 		m:   make(map[string]*watched), | ||||
| 		cph: syscall.InvalidHandle, | ||||
| 		c:   c, | ||||
| 	} | ||||
| 	runtime.SetFinalizer(r, func(r *readdcw) { | ||||
| 		if r.cph != syscall.InvalidHandle { | ||||
| 			syscall.CloseHandle(r.cph) | ||||
| 		} | ||||
| 	}) | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // Watch implements notify.Watcher interface. | ||||
| func (r *readdcw) Watch(path string, event Event) error { | ||||
| 	return r.watch(path, event, false) | ||||
| } | ||||
| 
 | ||||
| // RecursiveWatch implements notify.RecursiveWatcher interface. | ||||
| func (r *readdcw) RecursiveWatch(path string, event Event) error { | ||||
| 	return r.watch(path, event, true) | ||||
| } | ||||
| 
 | ||||
| // watch inserts a directory to the group of watched folders. If watched folder | ||||
| // already exists, function tries to rewatch it with new filters(NOT VALID). Moreover, | ||||
| // watch starts the main event loop goroutine when called for the first time. | ||||
| func (r *readdcw) watch(path string, event Event, recursive bool) (err error) { | ||||
| 	if event&^(All|fileNotifyChangeAll) != 0 { | ||||
| 		return errors.New("notify: unknown event") | ||||
| 	} | ||||
| 	r.Lock() | ||||
| 	wd, ok := r.m[path] | ||||
| 	r.Unlock() | ||||
| 	if !ok { | ||||
| 		if err = r.lazyinit(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		r.Lock() | ||||
| 		defer r.Unlock() | ||||
| 		if wd, ok = r.m[path]; ok { | ||||
| 			dbgprint("watch: exists already") | ||||
| 			return | ||||
| 		} | ||||
| 		if wd, err = newWatched(r.cph, uint32(event), recursive, path); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		r.m[path] = wd | ||||
| 		dbgprint("watch: new watch added") | ||||
| 	} else { | ||||
| 		dbgprint("watch: exists already") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // lazyinit creates an I/O completion port and starts the main event processing | ||||
| // loop. This method uses Double-Checked Locking optimization. | ||||
| func (r *readdcw) lazyinit() (err error) { | ||||
| 	invalid := uintptr(syscall.InvalidHandle) | ||||
| 	if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid { | ||||
| 		r.Lock() | ||||
| 		defer r.Unlock() | ||||
| 		if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid { | ||||
| 			cph := syscall.InvalidHandle | ||||
| 			if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			r.cph, r.start = cph, true | ||||
| 			go r.loop() | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // TODO(pknap) : doc | ||||
| func (r *readdcw) loop() { | ||||
| 	var n, key uint32 | ||||
| 	var overlapped *syscall.Overlapped | ||||
| 	for { | ||||
| 		err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE) | ||||
| 		if key == stateCPClose { | ||||
| 			r.Lock() | ||||
| 			handle := r.cph | ||||
| 			r.cph = syscall.InvalidHandle | ||||
| 			r.Unlock() | ||||
| 			syscall.CloseHandle(handle) | ||||
| 			r.wg.Done() | ||||
| 			return | ||||
| 		} | ||||
| 		if overlapped == nil { | ||||
| 			// TODO: check key == rewatch delete or 0(panic) | ||||
| 			continue | ||||
| 		} | ||||
| 		overEx := (*overlappedEx)(unsafe.Pointer(overlapped)) | ||||
| 		if n != 0 { | ||||
| 			r.loopevent(n, overEx) | ||||
| 			if err = overEx.parent.readDirChanges(); err != nil { | ||||
| 				// TODO: error handling | ||||
| 			} | ||||
| 		} | ||||
| 		r.loopstate(overEx) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TODO(pknap) : doc | ||||
| func (r *readdcw) loopstate(overEx *overlappedEx) { | ||||
| 	r.Lock() | ||||
| 	defer r.Unlock() | ||||
| 	filter := overEx.parent.parent.filter | ||||
| 	if filter&onlyMachineStates == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if overEx.parent.parent.count--; overEx.parent.parent.count == 0 { | ||||
| 		switch filter & onlyMachineStates { | ||||
| 		case stateRewatch: | ||||
| 			dbgprint("loopstate rewatch") | ||||
| 			overEx.parent.parent.recreate(r.cph) | ||||
| 		case stateUnwatch: | ||||
| 			dbgprint("loopstate unwatch") | ||||
| 			delete(r.m, syscall.UTF16ToString(overEx.parent.pathw)) | ||||
| 		case stateCPClose: | ||||
| 		default: | ||||
| 			panic(`notify: windows loopstate logic error`) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TODO(pknap) : doc | ||||
| func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) { | ||||
| 	events := []*event{} | ||||
| 	var currOffset uint32 | ||||
| 	for { | ||||
| 		raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset])) | ||||
| 		name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1]) | ||||
| 		events = append(events, &event{ | ||||
| 			pathw:  overEx.parent.pathw, | ||||
| 			filter: overEx.parent.filter, | ||||
| 			action: raw.Action, | ||||
| 			name:   name, | ||||
| 		}) | ||||
| 		if raw.NextEntryOffset == 0 { | ||||
| 			break | ||||
| 		} | ||||
| 		if currOffset += raw.NextEntryOffset; currOffset >= n { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.send(events) | ||||
| } | ||||
| 
 | ||||
| // TODO(pknap) : doc | ||||
| func (r *readdcw) send(es []*event) { | ||||
| 	for _, e := range es { | ||||
| 		var syse Event | ||||
| 		if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		switch { | ||||
| 		case e.action == syscall.FILE_ACTION_MODIFIED: | ||||
| 			e.ftype = fTypeUnknown | ||||
| 		case e.filter&uint32(dirmarker) != 0: | ||||
| 			e.ftype = fTypeDirectory | ||||
| 		default: | ||||
| 			e.ftype = fTypeFile | ||||
| 		} | ||||
| 		switch { | ||||
| 		case e.e == 0: | ||||
| 			e.e = syse | ||||
| 		case syse != 0: | ||||
| 			r.c <- &event{ | ||||
| 				pathw:  e.pathw, | ||||
| 				name:   e.name, | ||||
| 				ftype:  e.ftype, | ||||
| 				action: e.action, | ||||
| 				filter: e.filter, | ||||
| 				e:      syse, | ||||
| 			} | ||||
| 		} | ||||
| 		r.c <- e | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Rewatch implements notify.Rewatcher interface. | ||||
| func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error { | ||||
| 	return r.rewatch(path, uint32(oldevent), uint32(newevent), false) | ||||
| } | ||||
| 
 | ||||
| // RecursiveRewatch implements notify.RecursiveRewatcher interface. | ||||
| func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent, | ||||
| 	newevent Event) error { | ||||
| 	if oldpath != newpath { | ||||
| 		if err := r.unwatch(oldpath); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return r.watch(newpath, newevent, true) | ||||
| 	} | ||||
| 	return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true) | ||||
| } | ||||
| 
 | ||||
| // TODO : (pknap) doc. | ||||
| func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) { | ||||
| 	if Event(newevent)&^(All|fileNotifyChangeAll) != 0 { | ||||
| 		return errors.New("notify: unknown event") | ||||
| 	} | ||||
| 	var wd *watched | ||||
| 	r.Lock() | ||||
| 	defer r.Unlock() | ||||
| 	if wd, err = r.nonStateWatchedLocked(path); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent { | ||||
| 		panic(`notify: windows re-watcher logic error`) | ||||
| 	} | ||||
| 	wd.filter = stateRewatch | newevent | ||||
| 	wd.recursive, recursive = recursive, wd.recursive | ||||
| 	if err = wd.closeHandle(); err != nil { | ||||
| 		wd.filter = oldevent | ||||
| 		wd.recursive = recursive | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // TODO : pknap | ||||
| func (r *readdcw) nonStateWatchedLocked(path string) (wd *watched, err error) { | ||||
| 	wd, ok := r.m[path] | ||||
| 	if !ok || wd == nil { | ||||
| 		err = errors.New(`notify: ` + path + ` path is unwatched`) | ||||
| 		return | ||||
| 	} | ||||
| 	if wd.filter&onlyMachineStates != 0 { | ||||
| 		err = errors.New(`notify: another re/unwatching operation in progress`) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Unwatch implements notify.Watcher interface. | ||||
| func (r *readdcw) Unwatch(path string) error { | ||||
| 	return r.unwatch(path) | ||||
| } | ||||
| 
 | ||||
| // RecursiveUnwatch implements notify.RecursiveWatcher interface. | ||||
| func (r *readdcw) RecursiveUnwatch(path string) error { | ||||
| 	return r.unwatch(path) | ||||
| } | ||||
| 
 | ||||
| // TODO : pknap | ||||
| func (r *readdcw) unwatch(path string) (err error) { | ||||
| 	var wd *watched | ||||
| 	r.Lock() | ||||
| 	defer r.Unlock() | ||||
| 	if wd, err = r.nonStateWatchedLocked(path); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	wd.filter |= stateUnwatch | ||||
| 	if err = wd.closeHandle(); err != nil { | ||||
| 		wd.filter &^= stateUnwatch | ||||
| 		return | ||||
| 	} | ||||
| 	if _, attrErr := syscall.GetFileAttributes(&wd.pathw[0]); attrErr != nil { | ||||
| 		for _, g := range wd.digrip { | ||||
| 			if g != nil { | ||||
| 				dbgprint("unwatch: posting") | ||||
| 				if err = syscall.PostQueuedCompletionStatus(r.cph, 0, 0, (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped))); err != nil { | ||||
| 					wd.filter &^= stateUnwatch | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Close resets the whole watcher object, closes all existing file descriptors, | ||||
| // and sends stateCPClose state as completion key to the main watcher's loop. | ||||
| func (r *readdcw) Close() (err error) { | ||||
| 	r.Lock() | ||||
| 	if !r.start { | ||||
| 		r.Unlock() | ||||
| 		return nil | ||||
| 	} | ||||
| 	for _, wd := range r.m { | ||||
| 		wd.filter &^= onlyMachineStates | ||||
| 		wd.filter |= stateCPClose | ||||
| 		if e := wd.closeHandle(); e != nil && err == nil { | ||||
| 			err = e | ||||
| 		} | ||||
| 	} | ||||
| 	r.start = false | ||||
| 	r.Unlock() | ||||
| 	r.wg.Add(1) | ||||
| 	if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil { | ||||
| 		return e | ||||
| 	} | ||||
| 	r.wg.Wait() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // decode creates a notify event from both non-raw filter and action which was | ||||
| // returned from completion routine. Function may return Event(0) in case when | ||||
| // filter was replaced by a new value which does not contain fields that are | ||||
| // valid with passed action. | ||||
| func decode(filter, action uint32) (Event, Event) { | ||||
| 	switch action { | ||||
| 	case syscall.FILE_ACTION_ADDED: | ||||
| 		return gensys(filter, Create, FileActionAdded) | ||||
| 	case syscall.FILE_ACTION_REMOVED: | ||||
| 		return gensys(filter, Remove, FileActionRemoved) | ||||
| 	case syscall.FILE_ACTION_MODIFIED: | ||||
| 		return gensys(filter, Write, FileActionModified) | ||||
| 	case syscall.FILE_ACTION_RENAMED_OLD_NAME: | ||||
| 		return gensys(filter, Rename, FileActionRenamedOldName) | ||||
| 	case syscall.FILE_ACTION_RENAMED_NEW_NAME: | ||||
| 		return gensys(filter, Rename, FileActionRenamedNewName) | ||||
| 	} | ||||
| 	panic(`notify: cannot decode internal mask`) | ||||
| } | ||||
| 
 | ||||
| // gensys decides whether the Windows action, system-independent event or both | ||||
| // of them should be returned. Since the grip's filter may be atomically changed | ||||
| // during watcher lifetime, it is possible that neither Windows nor notify masks | ||||
| // are watched by the user when this function is called. | ||||
| func gensys(filter uint32, ge, se Event) (gene, syse Event) { | ||||
| 	isdir := filter&uint32(dirmarker) != 0 | ||||
| 	if isdir && filter&uint32(FileNotifyChangeDirName) != 0 || | ||||
| 		!isdir && filter&uint32(FileNotifyChangeFileName) != 0 || | ||||
| 		filter&uint32(fileNotifyChangeModified) != 0 { | ||||
| 		syse = se | ||||
| 	} | ||||
| 	if filter&uint32(ge) != 0 { | ||||
| 		gene = ge | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										23
									
								
								vendor/github.com/rjeczalik/notify/watcher_stub.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/rjeczalik/notify/watcher_stub.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows | ||||
| // +build !kqueue,!solaris | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import "errors" | ||||
| 
 | ||||
| type stub struct{ error } | ||||
| 
 | ||||
| // newWatcher stub. | ||||
| func newWatcher(chan<- EventInfo) watcher { | ||||
| 	return stub{errors.New("notify: not implemented")} | ||||
| } | ||||
| 
 | ||||
| // Following methods implement notify.watcher interface. | ||||
| func (s stub) Watch(string, Event) error          { return s } | ||||
| func (s stub) Rewatch(string, Event, Event) error { return s } | ||||
| func (s stub) Unwatch(string) (err error)         { return s } | ||||
| func (s stub) Close() error                       { return s } | ||||
							
								
								
									
										449
									
								
								vendor/github.com/rjeczalik/notify/watcher_trigger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										449
									
								
								vendor/github.com/rjeczalik/notify/watcher_trigger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,449 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris | ||||
| 
 | ||||
| // watcher_trigger is used for FEN and kqueue which behave similarly: | ||||
| // only files and dirs can be watched directly, but not files inside dirs. | ||||
| // As a result Create events have to be generated by implementation when | ||||
| // after Write event is returned for watched dir, it is rescanned and Create | ||||
| // event is returned for new files and these are automatically added | ||||
| // to watchlist. In case of removal of watched directory, native system returns | ||||
| // events for all files, but for Rename, they also need to be generated. | ||||
| // As a result native system works as something like trigger for rescan, | ||||
| // but contains additional data about dir in which changes occurred. For files | ||||
| // detailed data is returned. | ||||
| // Usage of watcher_trigger requires: | ||||
| // - trigger implementation, | ||||
| // - encode func, | ||||
| // - not2nat, nat2not maps. | ||||
| // Required manual operations on filesystem can lead to loss of precision. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
| ) | ||||
| 
 | ||||
| // trigger is to be implemented by platform implementation like FEN or kqueue. | ||||
| type trigger interface { | ||||
| 	// Close closes watcher's main native file descriptor. | ||||
| 	Close() error | ||||
| 	// Stop waiting for new events. | ||||
| 	Stop() error | ||||
| 	// Create new instance of watched. | ||||
| 	NewWatched(string, os.FileInfo) (*watched, error) | ||||
| 	// Record internally new *watched instance. | ||||
| 	Record(*watched) | ||||
| 	// Del removes internal copy of *watched instance. | ||||
| 	Del(*watched) | ||||
| 	// Watched returns *watched instance and native events for native type. | ||||
| 	Watched(interface{}) (*watched, int64, error) | ||||
| 	// Init initializes native watcher call. | ||||
| 	Init() error | ||||
| 	// Watch starts watching provided file/dir. | ||||
| 	Watch(os.FileInfo, *watched, int64) error | ||||
| 	// Unwatch stops watching provided file/dir. | ||||
| 	Unwatch(*watched) error | ||||
| 	// Wait for new events. | ||||
| 	Wait() (interface{}, error) | ||||
| 	// IsStop checks if Wait finished because of request watcher's stop. | ||||
| 	IsStop(n interface{}, err error) bool | ||||
| } | ||||
| 
 | ||||
| // trgWatched is a the base data structure representing watched file/directory. | ||||
| // The platform specific full data structure (watched) must embed this type. | ||||
| type trgWatched struct { | ||||
| 	// p is a path to watched file/directory. | ||||
| 	p string | ||||
| 	// fi provides information about watched file/dir. | ||||
| 	fi os.FileInfo | ||||
| 	// eDir represents events watched directly. | ||||
| 	eDir Event | ||||
| 	// eNonDir represents events watched indirectly. | ||||
| 	eNonDir Event | ||||
| } | ||||
| 
 | ||||
| // encode Event to native representation. Implementation is to be provided by | ||||
| // platform specific implementation. | ||||
| var encode func(Event, bool) int64 | ||||
| 
 | ||||
| var ( | ||||
| 	// nat2not matches native events to notify's ones. To be initialized by | ||||
| 	// platform dependent implementation. | ||||
| 	nat2not map[Event]Event | ||||
| 	// not2nat matches notify's events to native ones. To be initialized by | ||||
| 	// platform dependent implementation. | ||||
| 	not2nat map[Event]Event | ||||
| ) | ||||
| 
 | ||||
| // trg is a main structure implementing watcher. | ||||
| type trg struct { | ||||
| 	sync.Mutex | ||||
| 	// s is a channel used to stop monitoring. | ||||
| 	s chan struct{} | ||||
| 	// c is a channel used to pass events further. | ||||
| 	c chan<- EventInfo | ||||
| 	// pthLkp is a data structure mapping file names with data about watching | ||||
| 	// represented by them files/directories. | ||||
| 	pthLkp map[string]*watched | ||||
| 	// t is a platform dependent implementation of trigger. | ||||
| 	t trigger | ||||
| } | ||||
| 
 | ||||
| // newWatcher returns new watcher's implementation. | ||||
| func newWatcher(c chan<- EventInfo) watcher { | ||||
| 	t := &trg{ | ||||
| 		s:      make(chan struct{}, 1), | ||||
| 		pthLkp: make(map[string]*watched, 0), | ||||
| 		c:      c, | ||||
| 	} | ||||
| 	t.t = newTrigger(t.pthLkp) | ||||
| 	if err := t.t.Init(); err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	go t.monitor() | ||||
| 	return t | ||||
| } | ||||
| 
 | ||||
| // Close implements watcher. | ||||
| func (t *trg) Close() (err error) { | ||||
| 	t.Lock() | ||||
| 	if err = t.t.Stop(); err != nil { | ||||
| 		t.Unlock() | ||||
| 		return | ||||
| 	} | ||||
| 	<-t.s | ||||
| 	var e error | ||||
| 	for _, w := range t.pthLkp { | ||||
| 		if e = t.unwatch(w.p, w.fi); e != nil { | ||||
| 			dbgprintf("trg: unwatch %q failed: %q\n", w.p, e) | ||||
| 			err = nonil(err, e) | ||||
| 		} | ||||
| 	} | ||||
| 	if e = t.t.Close(); e != nil { | ||||
| 		dbgprintf("trg: closing native watch failed: %q\n", e) | ||||
| 		err = nonil(err, e) | ||||
| 	} | ||||
| 	if remaining := len(t.pthLkp); remaining != 0 { | ||||
| 		err = nonil(err, fmt.Errorf("Not all watches were removed: len(t.pthLkp) == %v", len(t.pthLkp))) | ||||
| 	} | ||||
| 	t.Unlock() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // send reported events one by one through chan. | ||||
| func (t *trg) send(evn []event) { | ||||
| 	for i := range evn { | ||||
| 		t.c <- &evn[i] | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // singlewatch starts to watch given p file/directory. | ||||
| func (t *trg) singlewatch(p string, e Event, direct mode, fi os.FileInfo) (err error) { | ||||
| 	w, ok := t.pthLkp[p] | ||||
| 	if !ok { | ||||
| 		if w, err = t.t.NewWatched(p, fi); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	switch direct { | ||||
| 	case dir: | ||||
| 		w.eDir |= e | ||||
| 	case ndir: | ||||
| 		w.eNonDir |= e | ||||
| 	case both: | ||||
| 		w.eDir |= e | ||||
| 		w.eNonDir |= e | ||||
| 	} | ||||
| 	if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if !ok { | ||||
| 		t.t.Record(w) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return errAlreadyWatched | ||||
| } | ||||
| 
 | ||||
| // decode converts event received from native to notify.Event | ||||
| // representation taking into account requested events (w). | ||||
| func decode(o int64, w Event) (e Event) { | ||||
| 	for f, n := range nat2not { | ||||
| 		if o&int64(f) != 0 { | ||||
| 			if w&f != 0 { | ||||
| 				e |= f | ||||
| 			} | ||||
| 			if w&n != 0 { | ||||
| 				e |= n | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (t *trg) watch(p string, e Event, fi os.FileInfo) error { | ||||
| 	if err := t.singlewatch(p, e, dir, fi); err != nil { | ||||
| 		if err != errAlreadyWatched { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if fi.IsDir() { | ||||
| 		err := t.walk(p, func(fi os.FileInfo) (err error) { | ||||
| 			if err = t.singlewatch(filepath.Join(p, fi.Name()), e, ndir, | ||||
| 				fi); err != nil { | ||||
| 				if err != errAlreadyWatched { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // walk runs f func on each file/dir from p directory. | ||||
| func (t *trg) walk(p string, fn func(os.FileInfo) error) error { | ||||
| 	fp, err := os.Open(p) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	ls, err := fp.Readdir(0) | ||||
| 	fp.Close() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for i := range ls { | ||||
| 		if err := fn(ls[i]); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (t *trg) unwatch(p string, fi os.FileInfo) error { | ||||
| 	if fi.IsDir() { | ||||
| 		err := t.walk(p, func(fi os.FileInfo) error { | ||||
| 			err := t.singleunwatch(filepath.Join(p, fi.Name()), ndir) | ||||
| 			if err != errNotWatched { | ||||
| 				return err | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return t.singleunwatch(p, dir) | ||||
| } | ||||
| 
 | ||||
| // Watch implements Watcher interface. | ||||
| func (t *trg) Watch(p string, e Event) error { | ||||
| 	fi, err := os.Stat(p) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	t.Lock() | ||||
| 	err = t.watch(p, e, fi) | ||||
| 	t.Unlock() | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Unwatch implements Watcher interface. | ||||
| func (t *trg) Unwatch(p string) error { | ||||
| 	fi, err := os.Stat(p) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	t.Lock() | ||||
| 	err = t.unwatch(p, fi) | ||||
| 	t.Unlock() | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Rewatch implements Watcher interface. | ||||
| // | ||||
| // TODO(rjeczalik): This is a naive hack. Rewrite might help. | ||||
| func (t *trg) Rewatch(p string, _, e Event) error { | ||||
| 	fi, err := os.Stat(p) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	t.Lock() | ||||
| 	if err = t.unwatch(p, fi); err == nil { | ||||
| 		// TODO(rjeczalik): If watch fails then we leave trigger in inconsistent | ||||
| 		// state. Handle? Panic? Native version of rewatch? | ||||
| 		err = t.watch(p, e, fi) | ||||
| 	} | ||||
| 	t.Unlock() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (*trg) file(w *watched, n interface{}, e Event) (evn []event) { | ||||
| 	evn = append(evn, event{w.p, e, w.fi.IsDir(), n}) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (t *trg) dir(w *watched, n interface{}, e, ge Event) (evn []event) { | ||||
| 	// If it's dir and delete we have to send it and continue, because | ||||
| 	// other processing relies on opening (in this case not existing) dir. | ||||
| 	// Events for contents of this dir are reported by native impl. | ||||
| 	// However events for rename must be generated for all monitored files | ||||
| 	// inside of moved directory, because native impl does not report it independently | ||||
| 	// for each file descriptor being moved in result of move action on | ||||
| 	// parent directory. | ||||
| 	if (ge & (not2nat[Rename] | not2nat[Remove])) != 0 { | ||||
| 		// Write is reported also for Remove on directory. Because of that | ||||
| 		// we have to filter it out explicitly. | ||||
| 		evn = append(evn, event{w.p, e & ^Write & ^not2nat[Write], true, n}) | ||||
| 		if ge¬2nat[Rename] != 0 { | ||||
| 			for p := range t.pthLkp { | ||||
| 				if strings.HasPrefix(p, w.p+string(os.PathSeparator)) { | ||||
| 					if err := t.singleunwatch(p, both); err != nil && err != errNotWatched && | ||||
| 						!os.IsNotExist(err) { | ||||
| 						dbgprintf("trg: failed stop watching moved file (%q): %q\n", | ||||
| 							p, err) | ||||
| 					} | ||||
| 					if (w.eDir|w.eNonDir)&(not2nat[Rename]|Rename) != 0 { | ||||
| 						evn = append(evn, event{ | ||||
| 							p, (w.eDir | w.eNonDir) & e &^ Write &^ not2nat[Write], | ||||
| 							w.fi.IsDir(), nil, | ||||
| 						}) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		t.t.Del(w) | ||||
| 		return | ||||
| 	} | ||||
| 	if (ge & not2nat[Write]) != 0 { | ||||
| 		switch err := t.walk(w.p, func(fi os.FileInfo) error { | ||||
| 			p := filepath.Join(w.p, fi.Name()) | ||||
| 			switch err := t.singlewatch(p, w.eDir, ndir, fi); { | ||||
| 			case os.IsNotExist(err) && ((w.eDir & Remove) != 0): | ||||
| 				evn = append(evn, event{p, Remove, fi.IsDir(), n}) | ||||
| 			case err == errAlreadyWatched: | ||||
| 			case err != nil: | ||||
| 				dbgprintf("trg: watching %q failed: %q", p, err) | ||||
| 			case (w.eDir & Create) != 0: | ||||
| 				evn = append(evn, event{p, Create, fi.IsDir(), n}) | ||||
| 			default: | ||||
| 			} | ||||
| 			return nil | ||||
| 		}); { | ||||
| 		case os.IsNotExist(err): | ||||
| 			return | ||||
| 		case err != nil: | ||||
| 			dbgprintf("trg: dir processing failed: %q", err) | ||||
| 		default: | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| type mode uint | ||||
| 
 | ||||
| const ( | ||||
| 	dir mode = iota | ||||
| 	ndir | ||||
| 	both | ||||
| ) | ||||
| 
 | ||||
| // unwatch stops watching p file/directory. | ||||
| func (t *trg) singleunwatch(p string, direct mode) error { | ||||
| 	w, ok := t.pthLkp[p] | ||||
| 	if !ok { | ||||
| 		return errNotWatched | ||||
| 	} | ||||
| 	switch direct { | ||||
| 	case dir: | ||||
| 		w.eDir = 0 | ||||
| 	case ndir: | ||||
| 		w.eNonDir = 0 | ||||
| 	case both: | ||||
| 		w.eDir, w.eNonDir = 0, 0 | ||||
| 	} | ||||
| 	if err := t.t.Unwatch(w); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if w.eNonDir|w.eDir != 0 { | ||||
| 		mod := dir | ||||
| 		if w.eNonDir != 0 { | ||||
| 			mod = ndir | ||||
| 		} | ||||
| 		if err := t.singlewatch(p, w.eNonDir|w.eDir, mod, | ||||
| 			w.fi); err != nil && err != errAlreadyWatched { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		t.t.Del(w) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (t *trg) monitor() { | ||||
| 	var ( | ||||
| 		n   interface{} | ||||
| 		err error | ||||
| 	) | ||||
| 	for { | ||||
| 		switch n, err = t.t.Wait(); { | ||||
| 		case err == syscall.EINTR: | ||||
| 		case t.t.IsStop(n, err): | ||||
| 			t.s <- struct{}{} | ||||
| 			return | ||||
| 		case err != nil: | ||||
| 			dbgprintf("trg: failed to read events: %q\n", err) | ||||
| 		default: | ||||
| 			t.send(t.process(n)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // process event returned by native call. | ||||
| func (t *trg) process(n interface{}) (evn []event) { | ||||
| 	t.Lock() | ||||
| 	w, ge, err := t.t.Watched(n) | ||||
| 	if err != nil { | ||||
| 		t.Unlock() | ||||
| 		dbgprintf("trg: %v event lookup failed: %q", Event(ge), err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	e := decode(ge, w.eDir|w.eNonDir) | ||||
| 	if ge&int64(not2nat[Remove]|not2nat[Rename]) == 0 { | ||||
| 		switch fi, err := os.Stat(w.p); { | ||||
| 		case err != nil: | ||||
| 		default: | ||||
| 			if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil { | ||||
| 				dbgprintf("trg: %q is no longer watched: %q", w.p, err) | ||||
| 				t.t.Del(w) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if e == Event(0) && (!w.fi.IsDir() || (ge&int64(not2nat[Write])) == 0) { | ||||
| 		t.Unlock() | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if w.fi.IsDir() { | ||||
| 		evn = append(evn, t.dir(w, n, e, Event(ge))...) | ||||
| 	} else { | ||||
| 		evn = append(evn, t.file(w, n, e)...) | ||||
| 	} | ||||
| 	if Event(ge)&(not2nat[Remove]|not2nat[Rename]) != 0 { | ||||
| 		t.t.Del(w) | ||||
| 	} | ||||
| 	t.Unlock() | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										103
									
								
								vendor/github.com/rjeczalik/notify/watchpoint.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/github.com/rjeczalik/notify/watchpoint.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| // EventDiff describes a change to an event set - EventDiff[0] is an old state, | ||||
| // while EventDiff[1] is a new state. If event set has not changed (old == new), | ||||
| // functions typically return the None value. | ||||
| type eventDiff [2]Event | ||||
| 
 | ||||
| func (diff eventDiff) Event() Event { | ||||
| 	return diff[1] &^ diff[0] | ||||
| } | ||||
| 
 | ||||
| // Watchpoint | ||||
| // | ||||
| // The nil key holds total event set - logical sum for all registered events. | ||||
| // It speeds up computing EventDiff for Add method. | ||||
| // | ||||
| // The rec key holds an event set for a watchpoints created by RecursiveWatch | ||||
| // for a Watcher implementation which is not natively recursive. | ||||
| type watchpoint map[chan<- EventInfo]Event | ||||
| 
 | ||||
| // None is an empty event diff, think null object. | ||||
| var none eventDiff | ||||
| 
 | ||||
| // rec is just a placeholder | ||||
| var rec = func() (ch chan<- EventInfo) { | ||||
| 	ch = make(chan<- EventInfo) | ||||
| 	close(ch) | ||||
| 	return | ||||
| }() | ||||
| 
 | ||||
| func (wp watchpoint) dryAdd(ch chan<- EventInfo, e Event) eventDiff { | ||||
| 	if e &^= internal; wp[ch]&e == e { | ||||
| 		return none | ||||
| 	} | ||||
| 	total := wp[ch] &^ internal | ||||
| 	return eventDiff{total, total | e} | ||||
| } | ||||
| 
 | ||||
| // Add assumes neither c nor e are nil or zero values. | ||||
| func (wp watchpoint) Add(c chan<- EventInfo, e Event) (diff eventDiff) { | ||||
| 	wp[c] |= e | ||||
| 	diff[0] = wp[nil] | ||||
| 	diff[1] = diff[0] | e | ||||
| 	wp[nil] = diff[1] &^ omit | ||||
| 	// Strip diff from internal events. | ||||
| 	diff[0] &^= internal | ||||
| 	diff[1] &^= internal | ||||
| 	if diff[0] == diff[1] { | ||||
| 		return none | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (wp watchpoint) Del(c chan<- EventInfo, e Event) (diff eventDiff) { | ||||
| 	wp[c] &^= e | ||||
| 	if wp[c] == 0 { | ||||
| 		delete(wp, c) | ||||
| 	} | ||||
| 	diff[0] = wp[nil] | ||||
| 	delete(wp, nil) | ||||
| 	if len(wp) != 0 { | ||||
| 		// Recalculate total event set. | ||||
| 		for _, e := range wp { | ||||
| 			diff[1] |= e | ||||
| 		} | ||||
| 		wp[nil] = diff[1] &^ omit | ||||
| 	} | ||||
| 	// Strip diff from internal events. | ||||
| 	diff[0] &^= internal | ||||
| 	diff[1] &^= internal | ||||
| 	if diff[0] == diff[1] { | ||||
| 		return none | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (wp watchpoint) Dispatch(ei EventInfo, extra Event) { | ||||
| 	e := eventmask(ei, extra) | ||||
| 	if !matches(wp[nil], e) { | ||||
| 		return | ||||
| 	} | ||||
| 	for ch, eset := range wp { | ||||
| 		if ch != nil && matches(eset, e) { | ||||
| 			select { | ||||
| 			case ch <- ei: | ||||
| 			default: // Drop event if receiver is too slow | ||||
| 				dbgprintf("dropped %s on %q: receiver too slow", ei.Event(), ei.Path()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (wp watchpoint) Total() Event { | ||||
| 	return wp[nil] &^ internal | ||||
| } | ||||
| 
 | ||||
| func (wp watchpoint) IsRecursive() bool { | ||||
| 	return wp[nil]&recursive != 0 | ||||
| } | ||||
							
								
								
									
										23
									
								
								vendor/github.com/rjeczalik/notify/watchpoint_other.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/rjeczalik/notify/watchpoint_other.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build !windows | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| // eventmask uses ei to create a new event which contains internal flags used by | ||||
| // notify package logic. | ||||
| func eventmask(ei EventInfo, extra Event) Event { | ||||
| 	return ei.Event() | extra | ||||
| } | ||||
| 
 | ||||
| // matches reports a match only when: | ||||
| // | ||||
| //   - for user events, when event is present in the given set | ||||
| //   - for internal events, when additionally both event and set have omit bit set | ||||
| // | ||||
| // Internal events must not be sent to user channels and vice versa. | ||||
| func matches(set, event Event) bool { | ||||
| 	return (set&omit)^(event&omit) == 0 && set&event == event | ||||
| } | ||||
							
								
								
									
										38
									
								
								vendor/github.com/rjeczalik/notify/watchpoint_readdcw.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/rjeczalik/notify/watchpoint_readdcw.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be | ||||
| // found in the LICENSE file. | ||||
| 
 | ||||
| // +build windows | ||||
| 
 | ||||
| package notify | ||||
| 
 | ||||
| // eventmask uses ei to create a new event which contains internal flags used by | ||||
| // notify package logic. If one of FileAction* masks is detected, this function | ||||
| // adds corresponding FileNotifyChange* values. This allows non registered | ||||
| // FileAction* events to be passed on. | ||||
| func eventmask(ei EventInfo, extra Event) (e Event) { | ||||
| 	if e = ei.Event() | extra; e&fileActionAll != 0 { | ||||
| 		if ev, ok := ei.(*event); ok { | ||||
| 			switch ev.ftype { | ||||
| 			case fTypeFile: | ||||
| 				e |= FileNotifyChangeFileName | ||||
| 			case fTypeDirectory: | ||||
| 				e |= FileNotifyChangeDirName | ||||
| 			case fTypeUnknown: | ||||
| 				e |= fileNotifyChangeModified | ||||
| 			} | ||||
| 			return e &^ fileActionAll | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // matches reports a match only when: | ||||
| // | ||||
| //   - for user events, when event is present in the given set | ||||
| //   - for internal events, when additionally both event and set have omit bit set | ||||
| // | ||||
| // Internal events must not be sent to user channels and vice versa. | ||||
| func matches(set, event Event) bool { | ||||
| 	return (set&omit)^(event&omit) == 0 && (set&event == event || set&fileNotifyChangeModified&event != 0) | ||||
| } | ||||
							
								
								
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							| @ -594,6 +594,12 @@ | ||||
| 			"revision": "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e", | ||||
| 			"revisionTime": "2018-04-08T09:29:02Z" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"checksumSHA1": "T2BgxGcsgrldYNFPQcmw46/K4qM=", | ||||
| 			"path": "github.com/rjeczalik/notify", | ||||
| 			"revision": "d152f3ce359a5464dc41e84a8919fc67e55bbbf0", | ||||
| 			"revisionTime": "2018-03-12T21:30:58Z" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"path": "github.com/rs/cors", | ||||
| 			"revision": "a62a804a8a009876ca59105f7899938a1349f4b3", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user