├── .gitignore ├── .travis.yml ├── CHANGES.md ├── Dockerfile ├── Godeps ├── Godeps.json └── Readme ├── LICENSE.txt ├── Makefile ├── README.md ├── appveyor.yml ├── cmd └── gotail │ ├── .gitignore │ ├── Makefile │ └── gotail.go ├── ratelimiter ├── Licence ├── leakybucket.go ├── leakybucket_test.go ├── memory.go └── storage.go ├── tail.go ├── tail_posix.go ├── tail_test.go ├── tail_windows.go ├── util └── util.go ├── vendor └── gopkg.in │ ├── fsnotify │ └── fsnotify.v1 │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── fsnotify.go │ │ ├── inotify.go │ │ ├── inotify_poller.go │ │ ├── kqueue.go │ │ ├── open_mode_bsd.go │ │ ├── open_mode_darwin.go │ │ └── windows.go │ └── tomb.v1 │ ├── LICENSE │ ├── README.md │ └── tomb.go ├── watch ├── filechanges.go ├── inotify.go ├── inotify_tracker.go ├── polling.go └── watch.go └── winfile └── winfile.go /.gitignore: -------------------------------------------------------------------------------- 1 | .test 2 | .go 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | script: 4 | - go test -race -v ./... 5 | 6 | go: 7 | - 1.5 8 | - 1.6 9 | - 1.7 10 | - 1.8 11 | - tip 12 | 13 | matrix: 14 | allow_failures: 15 | - go: tip 16 | 17 | install: 18 | - go get gopkg.in/fsnotify/fsnotify.v1 19 | - go get gopkg.in/tomb.v1 20 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # API v1 (gopkg.in/hpcloud/tail.v1) 2 | 3 | ## April, 2016 4 | 5 | * Migrated to godep, as depman is not longer supported 6 | * Introduced golang vendoring feature 7 | * Fixed issue [#57](https://github.com/hpcloud/tail/issues/57) related to reopen deleted file 8 | 9 | ## July, 2015 10 | 11 | * Fix inotify watcher leak; remove `Cleanup` (#51) 12 | 13 | # API v0 (gopkg.in/hpcloud/tail.v0) 14 | 15 | ## June, 2015 16 | 17 | * Don't return partial lines (PR #40) 18 | * Use stable version of fsnotify (#46) 19 | 20 | ## July, 2014 21 | 22 | * Fix tail for Windows (PR #36) 23 | 24 | ## May, 2014 25 | 26 | * Improved rate limiting using leaky bucket (PR #29) 27 | * Fix odd line splitting (PR #30) 28 | 29 | ## Apr, 2014 30 | 31 | * LimitRate now discards read buffer (PR #28) 32 | * allow reading of longer lines if MaxLineSize is unset (PR #24) 33 | * updated deps.json to latest fsnotify (441bbc86b1) 34 | 35 | ## Feb, 2014 36 | 37 | * added `Config.Logger` to suppress library logging 38 | 39 | ## Nov, 2013 40 | 41 | * add Cleanup to remove leaky inotify watches (PR #20) 42 | 43 | ## Aug, 2013 44 | 45 | * redesigned Location field (PR #12) 46 | * add tail.Tell (PR #14) 47 | 48 | ## July, 2013 49 | 50 | * Rate limiting (PR #10) 51 | 52 | ## May, 2013 53 | 54 | * Detect file deletions/renames in polling file watcher (PR #1) 55 | * Detect file truncation 56 | * Fix potential race condition when reopening the file (issue 5) 57 | * Fix potential blocking of `tail.Stop` (issue 4) 58 | * Fix uncleaned up ChangeEvents goroutines after calling tail.Stop 59 | * Support Follow=false 60 | 61 | ## Feb, 2013 62 | 63 | * Initial open source release 64 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang 2 | 3 | RUN mkdir -p $GOPATH/src/github.com/hpcloud/tail/ 4 | ADD . $GOPATH/src/github.com/hpcloud/tail/ 5 | 6 | # expecting to fetch dependencies successfully. 7 | RUN go get -v github.com/hpcloud/tail 8 | 9 | # expecting to run the test successfully. 10 | RUN go test -v github.com/hpcloud/tail 11 | 12 | # expecting to install successfully 13 | RUN go install -v github.com/hpcloud/tail 14 | RUN go install -v github.com/hpcloud/tail/cmd/gotail 15 | 16 | RUN $GOPATH/bin/gotail -h || true 17 | 18 | ENV PATH $GOPATH/bin:$PATH 19 | CMD ["gotail"] 20 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/hpcloud/tail", 3 | "GoVersion": "go1.5.1", 4 | "Deps": [ 5 | { 6 | "ImportPath": "gopkg.in/fsnotify.v1", 7 | "Comment": "v1.2.1", 8 | "Rev": "7be54206639f256967dd82fa767397ba5f8f48f5" 9 | }, 10 | { 11 | "ImportPath": "gopkg.in/tomb.v1", 12 | "Rev": "c131134a1947e9afd9cecfe11f4c6dff0732ae58" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | # © Copyright 2015 Hewlett Packard Enterprise Development LP 4 | Copyright (c) 2014 ActiveState 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | test: *.go 4 | go test -v -race ./... 5 | 6 | fmt: 7 | gofmt -w . 8 | 9 | # Run the test in an isolated environment. 10 | fulltest: 11 | docker build -t hpcloud/tail . 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/hpcloud/tail.svg)](https://travis-ci.org/hpcloud/tail) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/vrl3paf9md0a7bgk/branch/master?svg=true)](https://ci.appveyor.com/project/Nino-K/tail/branch/master) 3 | 4 | # Go package for tail-ing files 5 | 6 | A Go package striving to emulate the features of the BSD `tail` program. 7 | 8 | ```Go 9 | t, err := tail.TailFile("/var/log/nginx.log", tail.Config{Follow: true}) 10 | for line := range t.Lines { 11 | fmt.Println(line.Text) 12 | } 13 | ``` 14 | 15 | See [API documentation](http://godoc.org/github.com/hpcloud/tail). 16 | 17 | ## Log rotation 18 | 19 | Tail comes with full support for truncation/move detection as it is 20 | designed to work with log rotation tools. 21 | 22 | ## Installing 23 | 24 | go get github.com/hpcloud/tail/... 25 | 26 | ## Windows support 27 | 28 | This package [needs assistance](https://github.com/hpcloud/tail/labels/Windows) for full Windows support. 29 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.{build} 2 | skip_tags: true 3 | cache: C:\Users\appveyor\AppData\Local\NuGet\Cache 4 | build_script: 5 | - SET GOPATH=c:\workspace 6 | - go test -v -race ./... 7 | test: off 8 | clone_folder: c:\workspace\src\github.com\hpcloud\tail 9 | branches: 10 | only: 11 | - master 12 | -------------------------------------------------------------------------------- /cmd/gotail/.gitignore: -------------------------------------------------------------------------------- 1 | gotail 2 | -------------------------------------------------------------------------------- /cmd/gotail/Makefile: -------------------------------------------------------------------------------- 1 | default: gotail 2 | 3 | gotail: *.go ../../*.go 4 | go build 5 | -------------------------------------------------------------------------------- /cmd/gotail/gotail.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 | // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/hpcloud/tail" 12 | ) 13 | 14 | func args2config() (tail.Config, int64) { 15 | config := tail.Config{Follow: true} 16 | n := int64(0) 17 | maxlinesize := int(0) 18 | flag.Int64Var(&n, "n", 0, "tail from the last Nth location") 19 | flag.IntVar(&maxlinesize, "max", 0, "max line size") 20 | flag.BoolVar(&config.Follow, "f", false, "wait for additional data to be appended to the file") 21 | flag.BoolVar(&config.ReOpen, "F", false, "follow, and track file rename/rotation") 22 | flag.BoolVar(&config.Poll, "p", false, "use polling, instead of inotify") 23 | flag.Parse() 24 | if config.ReOpen { 25 | config.Follow = true 26 | } 27 | config.MaxLineSize = maxlinesize 28 | return config, n 29 | } 30 | 31 | func main() { 32 | config, n := args2config() 33 | if flag.NFlag() < 1 { 34 | fmt.Println("need one or more files as arguments") 35 | os.Exit(1) 36 | } 37 | 38 | if n != 0 { 39 | config.Location = &tail.SeekInfo{-n, os.SEEK_END} 40 | } 41 | 42 | done := make(chan bool) 43 | for _, filename := range flag.Args() { 44 | go tailFile(filename, config, done) 45 | } 46 | 47 | for _, _ = range flag.Args() { 48 | <-done 49 | } 50 | } 51 | 52 | func tailFile(filename string, config tail.Config, done chan bool) { 53 | defer func() { done <- true }() 54 | t, err := tail.TailFile(filename, config) 55 | if err != nil { 56 | fmt.Println(err) 57 | return 58 | } 59 | for line := range t.Lines { 60 | fmt.Println(line.Text) 61 | } 62 | err = t.Wait() 63 | if err != nil { 64 | fmt.Println(err) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ratelimiter/Licence: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 99designs 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /ratelimiter/leakybucket.go: -------------------------------------------------------------------------------- 1 | // Package ratelimiter implements the Leaky Bucket ratelimiting algorithm with memcached and in-memory backends. 2 | package ratelimiter 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | type LeakyBucket struct { 9 | Size uint16 10 | Fill float64 11 | LeakInterval time.Duration // time.Duration for 1 unit of size to leak 12 | Lastupdate time.Time 13 | Now func() time.Time 14 | } 15 | 16 | func NewLeakyBucket(size uint16, leakInterval time.Duration) *LeakyBucket { 17 | bucket := LeakyBucket{ 18 | Size: size, 19 | Fill: 0, 20 | LeakInterval: leakInterval, 21 | Now: time.Now, 22 | Lastupdate: time.Now(), 23 | } 24 | 25 | return &bucket 26 | } 27 | 28 | func (b *LeakyBucket) updateFill() { 29 | now := b.Now() 30 | if b.Fill > 0 { 31 | elapsed := now.Sub(b.Lastupdate) 32 | 33 | b.Fill -= float64(elapsed) / float64(b.LeakInterval) 34 | if b.Fill < 0 { 35 | b.Fill = 0 36 | } 37 | } 38 | b.Lastupdate = now 39 | } 40 | 41 | func (b *LeakyBucket) Pour(amount uint16) bool { 42 | b.updateFill() 43 | 44 | var newfill float64 = b.Fill + float64(amount) 45 | 46 | if newfill > float64(b.Size) { 47 | return false 48 | } 49 | 50 | b.Fill = newfill 51 | 52 | return true 53 | } 54 | 55 | // The time at which this bucket will be completely drained 56 | func (b *LeakyBucket) DrainedAt() time.Time { 57 | return b.Lastupdate.Add(time.Duration(b.Fill * float64(b.LeakInterval))) 58 | } 59 | 60 | // The duration until this bucket is completely drained 61 | func (b *LeakyBucket) TimeToDrain() time.Duration { 62 | return b.DrainedAt().Sub(b.Now()) 63 | } 64 | 65 | func (b *LeakyBucket) TimeSinceLastUpdate() time.Duration { 66 | return b.Now().Sub(b.Lastupdate) 67 | } 68 | 69 | type LeakyBucketSer struct { 70 | Size uint16 71 | Fill float64 72 | LeakInterval time.Duration // time.Duration for 1 unit of size to leak 73 | Lastupdate time.Time 74 | } 75 | 76 | func (b *LeakyBucket) Serialise() *LeakyBucketSer { 77 | bucket := LeakyBucketSer{ 78 | Size: b.Size, 79 | Fill: b.Fill, 80 | LeakInterval: b.LeakInterval, 81 | Lastupdate: b.Lastupdate, 82 | } 83 | 84 | return &bucket 85 | } 86 | 87 | func (b *LeakyBucketSer) DeSerialise() *LeakyBucket { 88 | bucket := LeakyBucket{ 89 | Size: b.Size, 90 | Fill: b.Fill, 91 | LeakInterval: b.LeakInterval, 92 | Lastupdate: b.Lastupdate, 93 | Now: time.Now, 94 | } 95 | 96 | return &bucket 97 | } 98 | -------------------------------------------------------------------------------- /ratelimiter/leakybucket_test.go: -------------------------------------------------------------------------------- 1 | package ratelimiter 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestPour(t *testing.T) { 9 | bucket := NewLeakyBucket(60, time.Second) 10 | bucket.Lastupdate = time.Unix(0, 0) 11 | 12 | bucket.Now = func() time.Time { return time.Unix(1, 0) } 13 | 14 | if bucket.Pour(61) { 15 | t.Error("Expected false") 16 | } 17 | 18 | if !bucket.Pour(10) { 19 | t.Error("Expected true") 20 | } 21 | 22 | if !bucket.Pour(49) { 23 | t.Error("Expected true") 24 | } 25 | 26 | if bucket.Pour(2) { 27 | t.Error("Expected false") 28 | } 29 | 30 | bucket.Now = func() time.Time { return time.Unix(61, 0) } 31 | if !bucket.Pour(60) { 32 | t.Error("Expected true") 33 | } 34 | 35 | if bucket.Pour(1) { 36 | t.Error("Expected false") 37 | } 38 | 39 | bucket.Now = func() time.Time { return time.Unix(70, 0) } 40 | 41 | if !bucket.Pour(1) { 42 | t.Error("Expected true") 43 | } 44 | 45 | } 46 | 47 | func TestTimeSinceLastUpdate(t *testing.T) { 48 | bucket := NewLeakyBucket(60, time.Second) 49 | bucket.Now = func() time.Time { return time.Unix(1, 0) } 50 | bucket.Pour(1) 51 | bucket.Now = func() time.Time { return time.Unix(2, 0) } 52 | 53 | sinceLast := bucket.TimeSinceLastUpdate() 54 | if sinceLast != time.Second*1 { 55 | t.Errorf("Expected time since last update to be less than 1 second, got %d", sinceLast) 56 | } 57 | } 58 | 59 | func TestTimeToDrain(t *testing.T) { 60 | bucket := NewLeakyBucket(60, time.Second) 61 | bucket.Now = func() time.Time { return time.Unix(1, 0) } 62 | bucket.Pour(10) 63 | 64 | if bucket.TimeToDrain() != time.Second*10 { 65 | t.Error("Time to drain should be 10 seconds") 66 | } 67 | 68 | bucket.Now = func() time.Time { return time.Unix(2, 0) } 69 | 70 | if bucket.TimeToDrain() != time.Second*9 { 71 | t.Error("Time to drain should be 9 seconds") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ratelimiter/memory.go: -------------------------------------------------------------------------------- 1 | package ratelimiter 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | const ( 9 | GC_SIZE int = 100 10 | GC_PERIOD time.Duration = 60 * time.Second 11 | ) 12 | 13 | type Memory struct { 14 | store map[string]LeakyBucket 15 | lastGCCollected time.Time 16 | } 17 | 18 | func NewMemory() *Memory { 19 | m := new(Memory) 20 | m.store = make(map[string]LeakyBucket) 21 | m.lastGCCollected = time.Now() 22 | return m 23 | } 24 | 25 | func (m *Memory) GetBucketFor(key string) (*LeakyBucket, error) { 26 | 27 | bucket, ok := m.store[key] 28 | if !ok { 29 | return nil, errors.New("miss") 30 | } 31 | 32 | return &bucket, nil 33 | } 34 | 35 | func (m *Memory) SetBucketFor(key string, bucket LeakyBucket) error { 36 | 37 | if len(m.store) > GC_SIZE { 38 | m.GarbageCollect() 39 | } 40 | 41 | m.store[key] = bucket 42 | 43 | return nil 44 | } 45 | 46 | func (m *Memory) GarbageCollect() { 47 | now := time.Now() 48 | 49 | // rate limit GC to once per minute 50 | if now.Unix() >= m.lastGCCollected.Add(GC_PERIOD).Unix() { 51 | for key, bucket := range m.store { 52 | // if the bucket is drained, then GC 53 | if bucket.DrainedAt().Unix() < now.Unix() { 54 | delete(m.store, key) 55 | } 56 | } 57 | 58 | m.lastGCCollected = now 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ratelimiter/storage.go: -------------------------------------------------------------------------------- 1 | package ratelimiter 2 | 3 | type Storage interface { 4 | GetBucketFor(string) (*LeakyBucket, error) 5 | SetBucketFor(string, LeakyBucket) error 6 | } 7 | -------------------------------------------------------------------------------- /tail.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 | // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 | 4 | package tail 5 | 6 | import ( 7 | "bufio" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "log" 13 | "os" 14 | "strings" 15 | "sync" 16 | "time" 17 | 18 | "github.com/hpcloud/tail/ratelimiter" 19 | "github.com/hpcloud/tail/util" 20 | "github.com/hpcloud/tail/watch" 21 | "gopkg.in/tomb.v1" 22 | ) 23 | 24 | var ( 25 | ErrStop = errors.New("tail should now stop") 26 | ) 27 | 28 | type Line struct { 29 | Text string 30 | Time time.Time 31 | Err error // Error from tail 32 | } 33 | 34 | // NewLine returns a Line with present time. 35 | func NewLine(text string) *Line { 36 | return &Line{text, time.Now(), nil} 37 | } 38 | 39 | // SeekInfo represents arguments to `os.Seek` 40 | type SeekInfo struct { 41 | Offset int64 42 | Whence int // os.SEEK_* 43 | } 44 | 45 | type logger interface { 46 | Fatal(v ...interface{}) 47 | Fatalf(format string, v ...interface{}) 48 | Fatalln(v ...interface{}) 49 | Panic(v ...interface{}) 50 | Panicf(format string, v ...interface{}) 51 | Panicln(v ...interface{}) 52 | Print(v ...interface{}) 53 | Printf(format string, v ...interface{}) 54 | Println(v ...interface{}) 55 | } 56 | 57 | // Config is used to specify how a file must be tailed. 58 | type Config struct { 59 | // File-specifc 60 | Location *SeekInfo // Seek to this location before tailing 61 | ReOpen bool // Reopen recreated files (tail -F) 62 | MustExist bool // Fail early if the file does not exist 63 | Poll bool // Poll for file changes instead of using inotify 64 | Pipe bool // Is a named pipe (mkfifo) 65 | RateLimiter *ratelimiter.LeakyBucket 66 | 67 | // Generic IO 68 | Follow bool // Continue looking for new lines (tail -f) 69 | MaxLineSize int // If non-zero, split longer lines into multiple lines 70 | 71 | // Logger, when nil, is set to tail.DefaultLogger 72 | // To disable logging: set field to tail.DiscardingLogger 73 | Logger logger 74 | } 75 | 76 | type Tail struct { 77 | Filename string 78 | Lines chan *Line 79 | Config 80 | 81 | file *os.File 82 | reader *bufio.Reader 83 | 84 | watcher watch.FileWatcher 85 | changes *watch.FileChanges 86 | 87 | tomb.Tomb // provides: Done, Kill, Dying 88 | 89 | lk sync.Mutex 90 | } 91 | 92 | var ( 93 | // DefaultLogger is used when Config.Logger == nil 94 | DefaultLogger = log.New(os.Stderr, "", log.LstdFlags) 95 | // DiscardingLogger can be used to disable logging output 96 | DiscardingLogger = log.New(ioutil.Discard, "", 0) 97 | ) 98 | 99 | // TailFile begins tailing the file. Output stream is made available 100 | // via the `Tail.Lines` channel. To handle errors during tailing, 101 | // invoke the `Wait` or `Err` method after finishing reading from the 102 | // `Lines` channel. 103 | func TailFile(filename string, config Config) (*Tail, error) { 104 | if config.ReOpen && !config.Follow { 105 | util.Fatal("cannot set ReOpen without Follow.") 106 | } 107 | 108 | t := &Tail{ 109 | Filename: filename, 110 | Lines: make(chan *Line), 111 | Config: config, 112 | } 113 | 114 | // when Logger was not specified in config, use default logger 115 | if t.Logger == nil { 116 | t.Logger = log.New(os.Stderr, "", log.LstdFlags) 117 | } 118 | 119 | if t.Poll { 120 | t.watcher = watch.NewPollingFileWatcher(filename) 121 | } else { 122 | t.watcher = watch.NewInotifyFileWatcher(filename) 123 | } 124 | 125 | if t.MustExist { 126 | var err error 127 | t.file, err = OpenFile(t.Filename) 128 | if err != nil { 129 | return nil, err 130 | } 131 | } 132 | 133 | go t.tailFileSync() 134 | 135 | return t, nil 136 | } 137 | 138 | // Return the file's current position, like stdio's ftell(). 139 | // But this value is not very accurate. 140 | // it may readed one line in the chan(tail.Lines), 141 | // so it may lost one line. 142 | func (tail *Tail) Tell() (offset int64, err error) { 143 | if tail.file == nil { 144 | return 145 | } 146 | offset, err = tail.file.Seek(0, os.SEEK_CUR) 147 | if err != nil { 148 | return 149 | } 150 | 151 | tail.lk.Lock() 152 | defer tail.lk.Unlock() 153 | if tail.reader == nil { 154 | return 155 | } 156 | 157 | offset -= int64(tail.reader.Buffered()) 158 | return 159 | } 160 | 161 | // Stop stops the tailing activity. 162 | func (tail *Tail) Stop() error { 163 | tail.Kill(nil) 164 | return tail.Wait() 165 | } 166 | 167 | // StopAtEOF stops tailing as soon as the end of the file is reached. 168 | func (tail *Tail) StopAtEOF() error { 169 | tail.Kill(errStopAtEOF) 170 | return tail.Wait() 171 | } 172 | 173 | var errStopAtEOF = errors.New("tail: stop at eof") 174 | 175 | func (tail *Tail) close() { 176 | close(tail.Lines) 177 | tail.closeFile() 178 | } 179 | 180 | func (tail *Tail) closeFile() { 181 | if tail.file != nil { 182 | tail.file.Close() 183 | tail.file = nil 184 | } 185 | } 186 | 187 | func (tail *Tail) reopen() error { 188 | tail.closeFile() 189 | for { 190 | var err error 191 | tail.file, err = OpenFile(tail.Filename) 192 | if err != nil { 193 | if os.IsNotExist(err) { 194 | tail.Logger.Printf("Waiting for %s to appear...", tail.Filename) 195 | if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil { 196 | if err == tomb.ErrDying { 197 | return err 198 | } 199 | return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err) 200 | } 201 | continue 202 | } 203 | return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err) 204 | } 205 | break 206 | } 207 | return nil 208 | } 209 | 210 | func (tail *Tail) readLine() (string, error) { 211 | tail.lk.Lock() 212 | line, err := tail.reader.ReadString('\n') 213 | tail.lk.Unlock() 214 | if err != nil { 215 | // Note ReadString "returns the data read before the error" in 216 | // case of an error, including EOF, so we return it as is. The 217 | // caller is expected to process it if err is EOF. 218 | return line, err 219 | } 220 | 221 | line = strings.TrimRight(line, "\n") 222 | 223 | return line, err 224 | } 225 | 226 | func (tail *Tail) tailFileSync() { 227 | defer tail.Done() 228 | defer tail.close() 229 | 230 | if !tail.MustExist { 231 | // deferred first open. 232 | err := tail.reopen() 233 | if err != nil { 234 | if err != tomb.ErrDying { 235 | tail.Kill(err) 236 | } 237 | return 238 | } 239 | } 240 | 241 | // Seek to requested location on first open of the file. 242 | if tail.Location != nil { 243 | _, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence) 244 | tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location) 245 | if err != nil { 246 | tail.Killf("Seek error on %s: %s", tail.Filename, err) 247 | return 248 | } 249 | } 250 | 251 | tail.openReader() 252 | 253 | var offset int64 254 | var err error 255 | 256 | // Read line by line. 257 | for { 258 | // do not seek in named pipes 259 | if !tail.Pipe { 260 | // grab the position in case we need to back up in the event of a half-line 261 | offset, err = tail.Tell() 262 | if err != nil { 263 | tail.Kill(err) 264 | return 265 | } 266 | } 267 | 268 | line, err := tail.readLine() 269 | 270 | // Process `line` even if err is EOF. 271 | if err == nil { 272 | cooloff := !tail.sendLine(line) 273 | if cooloff { 274 | // Wait a second before seeking till the end of 275 | // file when rate limit is reached. 276 | msg := ("Too much log activity; waiting a second " + 277 | "before resuming tailing") 278 | tail.Lines <- &Line{msg, time.Now(), errors.New(msg)} 279 | select { 280 | case <-time.After(time.Second): 281 | case <-tail.Dying(): 282 | return 283 | } 284 | if err := tail.seekEnd(); err != nil { 285 | tail.Kill(err) 286 | return 287 | } 288 | } 289 | } else if err == io.EOF { 290 | if !tail.Follow { 291 | if line != "" { 292 | tail.sendLine(line) 293 | } 294 | return 295 | } 296 | 297 | if tail.Follow && line != "" { 298 | // this has the potential to never return the last line if 299 | // it's not followed by a newline; seems a fair trade here 300 | err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0}) 301 | if err != nil { 302 | tail.Kill(err) 303 | return 304 | } 305 | } 306 | 307 | // When EOF is reached, wait for more data to become 308 | // available. Wait strategy is based on the `tail.watcher` 309 | // implementation (inotify or polling). 310 | err := tail.waitForChanges() 311 | if err != nil { 312 | if err != ErrStop { 313 | tail.Kill(err) 314 | } 315 | return 316 | } 317 | } else { 318 | // non-EOF error 319 | tail.Killf("Error reading %s: %s", tail.Filename, err) 320 | return 321 | } 322 | 323 | select { 324 | case <-tail.Dying(): 325 | if tail.Err() == errStopAtEOF { 326 | continue 327 | } 328 | return 329 | default: 330 | } 331 | } 332 | } 333 | 334 | // waitForChanges waits until the file has been appended, deleted, 335 | // moved or truncated. When moved or deleted - the file will be 336 | // reopened if ReOpen is true. Truncated files are always reopened. 337 | func (tail *Tail) waitForChanges() error { 338 | if tail.changes == nil { 339 | pos, err := tail.file.Seek(0, os.SEEK_CUR) 340 | if err != nil { 341 | return err 342 | } 343 | tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos) 344 | if err != nil { 345 | return err 346 | } 347 | } 348 | 349 | select { 350 | case <-tail.changes.Modified: 351 | return nil 352 | case <-tail.changes.Deleted: 353 | tail.changes = nil 354 | if tail.ReOpen { 355 | // XXX: we must not log from a library. 356 | tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename) 357 | if err := tail.reopen(); err != nil { 358 | return err 359 | } 360 | tail.Logger.Printf("Successfully reopened %s", tail.Filename) 361 | tail.openReader() 362 | return nil 363 | } else { 364 | tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename) 365 | return ErrStop 366 | } 367 | case <-tail.changes.Truncated: 368 | // Always reopen truncated files (Follow is true) 369 | tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename) 370 | if err := tail.reopen(); err != nil { 371 | return err 372 | } 373 | tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename) 374 | tail.openReader() 375 | return nil 376 | case <-tail.Dying(): 377 | return ErrStop 378 | } 379 | panic("unreachable") 380 | } 381 | 382 | func (tail *Tail) openReader() { 383 | if tail.MaxLineSize > 0 { 384 | // add 2 to account for newline characters 385 | tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2) 386 | } else { 387 | tail.reader = bufio.NewReader(tail.file) 388 | } 389 | } 390 | 391 | func (tail *Tail) seekEnd() error { 392 | return tail.seekTo(SeekInfo{Offset: 0, Whence: os.SEEK_END}) 393 | } 394 | 395 | func (tail *Tail) seekTo(pos SeekInfo) error { 396 | _, err := tail.file.Seek(pos.Offset, pos.Whence) 397 | if err != nil { 398 | return fmt.Errorf("Seek error on %s: %s", tail.Filename, err) 399 | } 400 | // Reset the read buffer whenever the file is re-seek'ed 401 | tail.reader.Reset(tail.file) 402 | return nil 403 | } 404 | 405 | // sendLine sends the line(s) to Lines channel, splitting longer lines 406 | // if necessary. Return false if rate limit is reached. 407 | func (tail *Tail) sendLine(line string) bool { 408 | now := time.Now() 409 | lines := []string{line} 410 | 411 | // Split longer lines 412 | if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize { 413 | lines = util.PartitionString(line, tail.MaxLineSize) 414 | } 415 | 416 | for _, line := range lines { 417 | tail.Lines <- &Line{line, now, nil} 418 | } 419 | 420 | if tail.Config.RateLimiter != nil { 421 | ok := tail.Config.RateLimiter.Pour(uint16(len(lines))) 422 | if !ok { 423 | tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n", 424 | tail.Filename) 425 | return false 426 | } 427 | } 428 | 429 | return true 430 | } 431 | 432 | // Cleanup removes inotify watches added by the tail package. This function is 433 | // meant to be invoked from a process's exit handler. Linux kernel may not 434 | // automatically remove inotify watches after the process exits. 435 | func (tail *Tail) Cleanup() { 436 | watch.Cleanup(tail.Filename) 437 | } 438 | -------------------------------------------------------------------------------- /tail_posix.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd netbsd openbsd 2 | 3 | package tail 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | func OpenFile(name string) (file *os.File, err error) { 10 | return os.Open(name) 11 | } 12 | -------------------------------------------------------------------------------- /tail_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 | // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 | 4 | // TODO: 5 | // * repeat all the tests with Poll:true 6 | 7 | package tail 8 | 9 | import ( 10 | _ "fmt" 11 | "io/ioutil" 12 | "os" 13 | "strings" 14 | "testing" 15 | "time" 16 | 17 | "github.com/hpcloud/tail/ratelimiter" 18 | "github.com/hpcloud/tail/watch" 19 | ) 20 | 21 | func init() { 22 | // Clear the temporary test directory 23 | err := os.RemoveAll(".test") 24 | if err != nil { 25 | panic(err) 26 | } 27 | } 28 | 29 | func TestMain(m *testing.M) { 30 | // Use a smaller poll duration for faster test runs. Keep it below 31 | // 100ms (which value is used as common delays for tests) 32 | watch.POLL_DURATION = 5 * time.Millisecond 33 | os.Exit(m.Run()) 34 | } 35 | 36 | func TestMustExist(t *testing.T) { 37 | tail, err := TailFile("/no/such/file", Config{Follow: true, MustExist: true}) 38 | if err == nil { 39 | t.Error("MustExist:true is violated") 40 | tail.Stop() 41 | } 42 | tail, err = TailFile("/no/such/file", Config{Follow: true, MustExist: false}) 43 | if err != nil { 44 | t.Error("MustExist:false is violated") 45 | } 46 | tail.Stop() 47 | _, err = TailFile("README.md", Config{Follow: true, MustExist: true}) 48 | if err != nil { 49 | t.Error("MustExist:true on an existing file is violated") 50 | } 51 | tail.Cleanup() 52 | } 53 | 54 | func TestWaitsForFileToExist(t *testing.T) { 55 | tailTest := NewTailTest("waits-for-file-to-exist", t) 56 | tail := tailTest.StartTail("test.txt", Config{}) 57 | go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false) 58 | 59 | <-time.After(100 * time.Millisecond) 60 | tailTest.CreateFile("test.txt", "hello\nworld\n") 61 | tailTest.Cleanup(tail, true) 62 | } 63 | 64 | func TestWaitsForFileToExistRelativePath(t *testing.T) { 65 | tailTest := NewTailTest("waits-for-file-to-exist-relative", t) 66 | 67 | oldWD, err := os.Getwd() 68 | if err != nil { 69 | tailTest.Fatal(err) 70 | } 71 | os.Chdir(tailTest.path) 72 | defer os.Chdir(oldWD) 73 | 74 | tail, err := TailFile("test.txt", Config{}) 75 | if err != nil { 76 | tailTest.Fatal(err) 77 | } 78 | 79 | go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false) 80 | 81 | <-time.After(100 * time.Millisecond) 82 | if err := ioutil.WriteFile("test.txt", []byte("hello\nworld\n"), 0600); err != nil { 83 | tailTest.Fatal(err) 84 | } 85 | tailTest.Cleanup(tail, true) 86 | } 87 | 88 | func TestStop(t *testing.T) { 89 | tail, err := TailFile("_no_such_file", Config{Follow: true, MustExist: false}) 90 | if err != nil { 91 | t.Error("MustExist:false is violated") 92 | } 93 | if tail.Stop() != nil { 94 | t.Error("Should be stoped successfully") 95 | } 96 | tail.Cleanup() 97 | } 98 | 99 | func TestStopAtEOF(t *testing.T) { 100 | tailTest := NewTailTest("maxlinesize", t) 101 | tailTest.CreateFile("test.txt", "hello\nthere\nworld\n") 102 | tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil}) 103 | 104 | // read "hello" 105 | line := <-tail.Lines 106 | if line.Text != "hello" { 107 | t.Errorf("Expected to get 'hello', got '%s' instead", line.Text) 108 | } 109 | 110 | tailTest.VerifyTailOutput(tail, []string{"there", "world"}, false) 111 | tail.StopAtEOF() 112 | tailTest.Cleanup(tail, true) 113 | } 114 | 115 | func TestMaxLineSizeFollow(t *testing.T) { 116 | // As last file line does not end with newline, it will not be present in tail's output 117 | maxLineSize(t, true, "hello\nworld\nfin\nhe", []string{"hel", "lo", "wor", "ld", "fin"}) 118 | } 119 | 120 | func TestMaxLineSizeNoFollow(t *testing.T) { 121 | maxLineSize(t, false, "hello\nworld\nfin\nhe", []string{"hel", "lo", "wor", "ld", "fin", "he"}) 122 | } 123 | 124 | func TestOver4096ByteLine(t *testing.T) { 125 | tailTest := NewTailTest("Over4096ByteLine", t) 126 | testString := strings.Repeat("a", 4097) 127 | tailTest.CreateFile("test.txt", "test\n"+testString+"\nhello\nworld\n") 128 | tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil}) 129 | go tailTest.VerifyTailOutput(tail, []string{"test", testString, "hello", "world"}, false) 130 | 131 | // Delete after a reasonable delay, to give tail sufficient time 132 | // to read all lines. 133 | <-time.After(100 * time.Millisecond) 134 | tailTest.RemoveFile("test.txt") 135 | tailTest.Cleanup(tail, true) 136 | } 137 | func TestOver4096ByteLineWithSetMaxLineSize(t *testing.T) { 138 | tailTest := NewTailTest("Over4096ByteLineMaxLineSize", t) 139 | testString := strings.Repeat("a", 4097) 140 | tailTest.CreateFile("test.txt", "test\n"+testString+"\nhello\nworld\n") 141 | tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil, MaxLineSize: 4097}) 142 | go tailTest.VerifyTailOutput(tail, []string{"test", testString, "hello", "world"}, false) 143 | 144 | // Delete after a reasonable delay, to give tail sufficient time 145 | // to read all lines. 146 | <-time.After(100 * time.Millisecond) 147 | tailTest.RemoveFile("test.txt") 148 | tailTest.Cleanup(tail, true) 149 | } 150 | 151 | func TestLocationFull(t *testing.T) { 152 | tailTest := NewTailTest("location-full", t) 153 | tailTest.CreateFile("test.txt", "hello\nworld\n") 154 | tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil}) 155 | go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false) 156 | 157 | // Delete after a reasonable delay, to give tail sufficient time 158 | // to read all lines. 159 | <-time.After(100 * time.Millisecond) 160 | tailTest.RemoveFile("test.txt") 161 | tailTest.Cleanup(tail, true) 162 | } 163 | 164 | func TestLocationFullDontFollow(t *testing.T) { 165 | tailTest := NewTailTest("location-full-dontfollow", t) 166 | tailTest.CreateFile("test.txt", "hello\nworld\n") 167 | tail := tailTest.StartTail("test.txt", Config{Follow: false, Location: nil}) 168 | go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false) 169 | 170 | // Add more data only after reasonable delay. 171 | <-time.After(100 * time.Millisecond) 172 | tailTest.AppendFile("test.txt", "more\ndata\n") 173 | <-time.After(100 * time.Millisecond) 174 | 175 | tailTest.Cleanup(tail, true) 176 | } 177 | 178 | func TestLocationEnd(t *testing.T) { 179 | tailTest := NewTailTest("location-end", t) 180 | tailTest.CreateFile("test.txt", "hello\nworld\n") 181 | tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: &SeekInfo{0, os.SEEK_END}}) 182 | go tailTest.VerifyTailOutput(tail, []string{"more", "data"}, false) 183 | 184 | <-time.After(100 * time.Millisecond) 185 | tailTest.AppendFile("test.txt", "more\ndata\n") 186 | 187 | // Delete after a reasonable delay, to give tail sufficient time 188 | // to read all lines. 189 | <-time.After(100 * time.Millisecond) 190 | tailTest.RemoveFile("test.txt") 191 | tailTest.Cleanup(tail, true) 192 | } 193 | 194 | func TestLocationMiddle(t *testing.T) { 195 | // Test reading from middle. 196 | tailTest := NewTailTest("location-middle", t) 197 | tailTest.CreateFile("test.txt", "hello\nworld\n") 198 | tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: &SeekInfo{-6, os.SEEK_END}}) 199 | go tailTest.VerifyTailOutput(tail, []string{"world", "more", "data"}, false) 200 | 201 | <-time.After(100 * time.Millisecond) 202 | tailTest.AppendFile("test.txt", "more\ndata\n") 203 | 204 | // Delete after a reasonable delay, to give tail sufficient time 205 | // to read all lines. 206 | <-time.After(100 * time.Millisecond) 207 | tailTest.RemoveFile("test.txt") 208 | tailTest.Cleanup(tail, true) 209 | } 210 | 211 | // The use of polling file watcher could affect file rotation 212 | // (detected via renames), so test these explicitly. 213 | 214 | func TestReOpenInotify(t *testing.T) { 215 | reOpen(t, false) 216 | } 217 | 218 | func TestReOpenPolling(t *testing.T) { 219 | reOpen(t, true) 220 | } 221 | 222 | // The use of polling file watcher could affect file rotation 223 | // (detected via renames), so test these explicitly. 224 | 225 | func TestReSeekInotify(t *testing.T) { 226 | reSeek(t, false) 227 | } 228 | 229 | func TestReSeekPolling(t *testing.T) { 230 | reSeek(t, true) 231 | } 232 | 233 | func TestRateLimiting(t *testing.T) { 234 | tailTest := NewTailTest("rate-limiting", t) 235 | tailTest.CreateFile("test.txt", "hello\nworld\nagain\nextra\n") 236 | config := Config{ 237 | Follow: true, 238 | RateLimiter: ratelimiter.NewLeakyBucket(2, time.Second)} 239 | leakybucketFull := "Too much log activity; waiting a second before resuming tailing" 240 | tail := tailTest.StartTail("test.txt", config) 241 | 242 | // TODO: also verify that tail resumes after the cooloff period. 243 | go tailTest.VerifyTailOutput(tail, []string{ 244 | "hello", "world", "again", 245 | leakybucketFull, 246 | "more", "data", 247 | leakybucketFull}, false) 248 | 249 | // Add more data only after reasonable delay. 250 | <-time.After(1200 * time.Millisecond) 251 | tailTest.AppendFile("test.txt", "more\ndata\n") 252 | 253 | // Delete after a reasonable delay, to give tail sufficient time 254 | // to read all lines. 255 | <-time.After(100 * time.Millisecond) 256 | tailTest.RemoveFile("test.txt") 257 | 258 | tailTest.Cleanup(tail, true) 259 | } 260 | 261 | func TestTell(t *testing.T) { 262 | tailTest := NewTailTest("tell-position", t) 263 | tailTest.CreateFile("test.txt", "hello\nworld\nagain\nmore\n") 264 | config := Config{ 265 | Follow: false, 266 | Location: &SeekInfo{0, os.SEEK_SET}} 267 | tail := tailTest.StartTail("test.txt", config) 268 | // read noe line 269 | <-tail.Lines 270 | offset, err := tail.Tell() 271 | if err != nil { 272 | tailTest.Errorf("Tell return error: %s", err.Error()) 273 | } 274 | tail.Done() 275 | // tail.close() 276 | 277 | config = Config{ 278 | Follow: false, 279 | Location: &SeekInfo{offset, os.SEEK_SET}} 280 | tail = tailTest.StartTail("test.txt", config) 281 | for l := range tail.Lines { 282 | // it may readed one line in the chan(tail.Lines), 283 | // so it may lost one line. 284 | if l.Text != "world" && l.Text != "again" { 285 | tailTest.Fatalf("mismatch; expected world or again, but got %s", 286 | l.Text) 287 | } 288 | break 289 | } 290 | tailTest.RemoveFile("test.txt") 291 | tail.Done() 292 | tail.Cleanup() 293 | } 294 | 295 | func TestBlockUntilExists(t *testing.T) { 296 | tailTest := NewTailTest("block-until-file-exists", t) 297 | config := Config{ 298 | Follow: true, 299 | } 300 | tail := tailTest.StartTail("test.txt", config) 301 | go func() { 302 | time.Sleep(100 * time.Millisecond) 303 | tailTest.CreateFile("test.txt", "hello world\n") 304 | }() 305 | for l := range tail.Lines { 306 | if l.Text != "hello world" { 307 | tailTest.Fatalf("mismatch; expected hello world, but got %s", 308 | l.Text) 309 | } 310 | break 311 | } 312 | tailTest.RemoveFile("test.txt") 313 | tail.Stop() 314 | tail.Cleanup() 315 | } 316 | 317 | func maxLineSize(t *testing.T, follow bool, fileContent string, expected []string) { 318 | tailTest := NewTailTest("maxlinesize", t) 319 | tailTest.CreateFile("test.txt", fileContent) 320 | tail := tailTest.StartTail("test.txt", Config{Follow: follow, Location: nil, MaxLineSize: 3}) 321 | go tailTest.VerifyTailOutput(tail, expected, false) 322 | 323 | // Delete after a reasonable delay, to give tail sufficient time 324 | // to read all lines. 325 | <-time.After(100 * time.Millisecond) 326 | tailTest.RemoveFile("test.txt") 327 | tailTest.Cleanup(tail, true) 328 | } 329 | 330 | func reOpen(t *testing.T, poll bool) { 331 | var name string 332 | var delay time.Duration 333 | if poll { 334 | name = "reopen-polling" 335 | delay = 300 * time.Millisecond // account for POLL_DURATION 336 | } else { 337 | name = "reopen-inotify" 338 | delay = 100 * time.Millisecond 339 | } 340 | tailTest := NewTailTest(name, t) 341 | tailTest.CreateFile("test.txt", "hello\nworld\n") 342 | tail := tailTest.StartTail( 343 | "test.txt", 344 | Config{Follow: true, ReOpen: true, Poll: poll}) 345 | content := []string{"hello", "world", "more", "data", "endofworld"} 346 | go tailTest.VerifyTailOutput(tail, content, false) 347 | 348 | if poll { 349 | // deletion must trigger reopen 350 | <-time.After(delay) 351 | tailTest.RemoveFile("test.txt") 352 | <-time.After(delay) 353 | tailTest.CreateFile("test.txt", "more\ndata\n") 354 | } else { 355 | // In inotify mode, fsnotify is currently unable to deliver notifications 356 | // about deletion of open files, so we are not testing file deletion. 357 | // (see https://github.com/fsnotify/fsnotify/issues/194 for details). 358 | <-time.After(delay) 359 | tailTest.AppendToFile("test.txt", "more\ndata\n") 360 | } 361 | 362 | // rename must trigger reopen 363 | <-time.After(delay) 364 | tailTest.RenameFile("test.txt", "test.txt.rotated") 365 | <-time.After(delay) 366 | tailTest.CreateFile("test.txt", "endofworld\n") 367 | 368 | // Delete after a reasonable delay, to give tail sufficient time 369 | // to read all lines. 370 | <-time.After(delay) 371 | tailTest.RemoveFile("test.txt") 372 | <-time.After(delay) 373 | 374 | // Do not bother with stopping as it could kill the tomb during 375 | // the reading of data written above. Timings can vary based on 376 | // test environment. 377 | tailTest.Cleanup(tail, false) 378 | } 379 | 380 | func TestInotify_WaitForCreateThenMove(t *testing.T) { 381 | tailTest := NewTailTest("wait-for-create-then-reopen", t) 382 | os.Remove(tailTest.path + "/test.txt") // Make sure the file does NOT exist. 383 | 384 | tail := tailTest.StartTail( 385 | "test.txt", 386 | Config{Follow: true, ReOpen: true, Poll: false}) 387 | 388 | content := []string{"hello", "world", "endofworld"} 389 | go tailTest.VerifyTailOutput(tail, content, false) 390 | 391 | time.Sleep(50 * time.Millisecond) 392 | tailTest.CreateFile("test.txt", "hello\nworld\n") 393 | time.Sleep(50 * time.Millisecond) 394 | tailTest.RenameFile("test.txt", "test.txt.rotated") 395 | time.Sleep(50 * time.Millisecond) 396 | tailTest.CreateFile("test.txt", "endofworld\n") 397 | time.Sleep(50 * time.Millisecond) 398 | tailTest.RemoveFile("test.txt.rotated") 399 | tailTest.RemoveFile("test.txt") 400 | 401 | // Do not bother with stopping as it could kill the tomb during 402 | // the reading of data written above. Timings can vary based on 403 | // test environment. 404 | tailTest.Cleanup(tail, false) 405 | } 406 | 407 | func reSeek(t *testing.T, poll bool) { 408 | var name string 409 | if poll { 410 | name = "reseek-polling" 411 | } else { 412 | name = "reseek-inotify" 413 | } 414 | tailTest := NewTailTest(name, t) 415 | tailTest.CreateFile("test.txt", "a really long string goes here\nhello\nworld\n") 416 | tail := tailTest.StartTail( 417 | "test.txt", 418 | Config{Follow: true, ReOpen: false, Poll: poll}) 419 | 420 | go tailTest.VerifyTailOutput(tail, []string{ 421 | "a really long string goes here", "hello", "world", "h311o", "w0r1d", "endofworld"}, false) 422 | 423 | // truncate now 424 | <-time.After(100 * time.Millisecond) 425 | tailTest.TruncateFile("test.txt", "h311o\nw0r1d\nendofworld\n") 426 | 427 | // Delete after a reasonable delay, to give tail sufficient time 428 | // to read all lines. 429 | <-time.After(100 * time.Millisecond) 430 | tailTest.RemoveFile("test.txt") 431 | 432 | // Do not bother with stopping as it could kill the tomb during 433 | // the reading of data written above. Timings can vary based on 434 | // test environment. 435 | tailTest.Cleanup(tail, false) 436 | } 437 | 438 | // Test library 439 | 440 | type TailTest struct { 441 | Name string 442 | path string 443 | done chan struct{} 444 | *testing.T 445 | } 446 | 447 | func NewTailTest(name string, t *testing.T) TailTest { 448 | tt := TailTest{name, ".test/" + name, make(chan struct{}), t} 449 | err := os.MkdirAll(tt.path, os.ModeTemporary|0700) 450 | if err != nil { 451 | tt.Fatal(err) 452 | } 453 | 454 | return tt 455 | } 456 | 457 | func (t TailTest) CreateFile(name string, contents string) { 458 | err := ioutil.WriteFile(t.path+"/"+name, []byte(contents), 0600) 459 | if err != nil { 460 | t.Fatal(err) 461 | } 462 | } 463 | 464 | func (t TailTest) AppendToFile(name string, contents string) { 465 | err := ioutil.WriteFile(t.path+"/"+name, []byte(contents), 0600|os.ModeAppend) 466 | if err != nil { 467 | t.Fatal(err) 468 | } 469 | } 470 | 471 | func (t TailTest) RemoveFile(name string) { 472 | err := os.Remove(t.path + "/" + name) 473 | if err != nil { 474 | t.Fatal(err) 475 | } 476 | } 477 | 478 | func (t TailTest) RenameFile(oldname string, newname string) { 479 | oldname = t.path + "/" + oldname 480 | newname = t.path + "/" + newname 481 | err := os.Rename(oldname, newname) 482 | if err != nil { 483 | t.Fatal(err) 484 | } 485 | } 486 | 487 | func (t TailTest) AppendFile(name string, contents string) { 488 | f, err := os.OpenFile(t.path+"/"+name, os.O_APPEND|os.O_WRONLY, 0600) 489 | if err != nil { 490 | t.Fatal(err) 491 | } 492 | defer f.Close() 493 | _, err = f.WriteString(contents) 494 | if err != nil { 495 | t.Fatal(err) 496 | } 497 | } 498 | 499 | func (t TailTest) TruncateFile(name string, contents string) { 500 | f, err := os.OpenFile(t.path+"/"+name, os.O_TRUNC|os.O_WRONLY, 0600) 501 | if err != nil { 502 | t.Fatal(err) 503 | } 504 | defer f.Close() 505 | _, err = f.WriteString(contents) 506 | if err != nil { 507 | t.Fatal(err) 508 | } 509 | } 510 | 511 | func (t TailTest) StartTail(name string, config Config) *Tail { 512 | tail, err := TailFile(t.path+"/"+name, config) 513 | if err != nil { 514 | t.Fatal(err) 515 | } 516 | return tail 517 | } 518 | 519 | func (t TailTest) VerifyTailOutput(tail *Tail, lines []string, expectEOF bool) { 520 | defer close(t.done) 521 | t.ReadLines(tail, lines) 522 | // It is important to do this if only EOF is expected 523 | // otherwise we could block on <-tail.Lines 524 | if expectEOF { 525 | line, ok := <-tail.Lines 526 | if ok { 527 | t.Fatalf("more content from tail: %+v", line) 528 | } 529 | } 530 | } 531 | 532 | func (t TailTest) ReadLines(tail *Tail, lines []string) { 533 | for idx, line := range lines { 534 | tailedLine, ok := <-tail.Lines 535 | if !ok { 536 | // tail.Lines is closed and empty. 537 | err := tail.Err() 538 | if err != nil { 539 | t.Fatalf("tail ended with error: %v", err) 540 | } 541 | t.Fatalf("tail ended early; expecting more: %v", lines[idx:]) 542 | } 543 | if tailedLine == nil { 544 | t.Fatalf("tail.Lines returned nil; not possible") 545 | } 546 | // Note: not checking .Err as the `lines` argument is designed 547 | // to match error strings as well. 548 | if tailedLine.Text != line { 549 | t.Fatalf( 550 | "unexpected line/err from tail: "+ 551 | "expecting <<%s>>>, but got <<<%s>>>", 552 | line, tailedLine.Text) 553 | } 554 | } 555 | } 556 | 557 | func (t TailTest) Cleanup(tail *Tail, stop bool) { 558 | <-t.done 559 | if stop { 560 | tail.Stop() 561 | } 562 | tail.Cleanup() 563 | } 564 | -------------------------------------------------------------------------------- /tail_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package tail 4 | 5 | import ( 6 | "github.com/hpcloud/tail/winfile" 7 | "os" 8 | ) 9 | 10 | func OpenFile(name string) (file *os.File, err error) { 11 | return winfile.OpenFile(name, os.O_RDONLY, 0) 12 | } 13 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 | // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 | 4 | package util 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "os" 10 | "runtime/debug" 11 | ) 12 | 13 | type Logger struct { 14 | *log.Logger 15 | } 16 | 17 | var LOGGER = &Logger{log.New(os.Stderr, "", log.LstdFlags)} 18 | 19 | // fatal is like panic except it displays only the current goroutine's stack. 20 | func Fatal(format string, v ...interface{}) { 21 | // https://github.com/hpcloud/log/blob/master/log.go#L45 22 | LOGGER.Output(2, fmt.Sprintf("FATAL -- "+format, v...)+"\n"+string(debug.Stack())) 23 | os.Exit(1) 24 | } 25 | 26 | // partitionString partitions the string into chunks of given size, 27 | // with the last chunk of variable size. 28 | func PartitionString(s string, chunkSize int) []string { 29 | if chunkSize <= 0 { 30 | panic("invalid chunkSize") 31 | } 32 | length := len(s) 33 | chunks := 1 + length/chunkSize 34 | start := 0 35 | end := chunkSize 36 | parts := make([]string, 0, chunks) 37 | for { 38 | if end > length { 39 | end = length 40 | } 41 | parts = append(parts, s[start:end]) 42 | if end == length { 43 | break 44 | } 45 | start, end = end, end+chunkSize 46 | } 47 | return parts 48 | } 49 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/.gitignore: -------------------------------------------------------------------------------- 1 | # Setup a Global .gitignore for OS and editor generated files: 2 | # https://help.github.com/articles/ignoring-files 3 | # git config --global core.excludesfile ~/.gitignore_global 4 | 5 | .vagrant 6 | *.sublime-project 7 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | 4 | go: 5 | - 1.5.1 6 | 7 | before_script: 8 | - go get -u github.com/golang/lint/golint 9 | 10 | after_script: 11 | - test -z "$(gofmt -s -l -w . | tee /dev/stderr)" 12 | - test -z "$(golint ./... | tee /dev/stderr)" 13 | - go vet ./... 14 | 15 | os: 16 | - linux 17 | - osx 18 | 19 | notifications: 20 | email: false 21 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/AUTHORS: -------------------------------------------------------------------------------- 1 | # Names should be added to this file as 2 | # Name or Organization 3 | # The email address is not required for organizations. 4 | 5 | # You can update this list using the following command: 6 | # 7 | # $ git shortlog -se | awk '{print $2 " " $3 " " $4}' 8 | 9 | # Please keep the list sorted. 10 | 11 | Adrien Bustany 12 | Caleb Spare 13 | Case Nelson 14 | Chris Howey 15 | Christoffer Buchholz 16 | Dave Cheney 17 | Francisco Souza 18 | Hari haran 19 | John C Barstow 20 | Kelvin Fo 21 | Matt Layher 22 | Nathan Youngman 23 | Paul Hammond 24 | Pieter Droogendijk 25 | Pursuit92 26 | Rob Figueiredo 27 | Soge Zhang 28 | Tilak Sharma 29 | Travis Cline 30 | Tudor Golubenco 31 | Yukang 32 | bronze1man 33 | debrando 34 | henrikedwards 35 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.2.1 / 2015-10-14 4 | 5 | * kqueue: don't watch named pipes [#98](https://github.com/go-fsnotify/fsnotify/pull/98) (thanks @evanphx) 6 | 7 | ## v1.2.0 / 2015-02-08 8 | 9 | * inotify: use epoll to wake up readEvents [#66](https://github.com/go-fsnotify/fsnotify/pull/66) (thanks @PieterD) 10 | * inotify: closing watcher should now always shut down goroutine [#63](https://github.com/go-fsnotify/fsnotify/pull/63) (thanks @PieterD) 11 | * kqueue: close kqueue after removing watches, fixes [#59](https://github.com/go-fsnotify/fsnotify/issues/59) 12 | 13 | ## v1.1.1 / 2015-02-05 14 | 15 | * inotify: Retry read on EINTR [#61](https://github.com/go-fsnotify/fsnotify/issues/61) (thanks @PieterD) 16 | 17 | ## v1.1.0 / 2014-12-12 18 | 19 | * kqueue: rework internals [#43](https://github.com/go-fsnotify/fsnotify/pull/43) 20 | * add low-level functions 21 | * only need to store flags on directories 22 | * less mutexes [#13](https://github.com/go-fsnotify/fsnotify/issues/13) 23 | * done can be an unbuffered channel 24 | * remove calls to os.NewSyscallError 25 | * More efficient string concatenation for Event.String() [#52](https://github.com/go-fsnotify/fsnotify/pull/52) (thanks @mdlayher) 26 | * kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/go-fsnotify/fsnotify/issues/48) 27 | * kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51) 28 | 29 | ## v1.0.4 / 2014-09-07 30 | 31 | * kqueue: add dragonfly to the build tags. 32 | * Rename source code files, rearrange code so exported APIs are at the top. 33 | * Add done channel to example code. [#37](https://github.com/go-fsnotify/fsnotify/pull/37) (thanks @chenyukang) 34 | 35 | ## v1.0.3 / 2014-08-19 36 | 37 | * [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/go-fsnotify/fsnotify/issues/36) 38 | 39 | ## v1.0.2 / 2014-08-17 40 | 41 | * [Fix] Missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso) 42 | * [Fix] Make ./path and path equivalent. (thanks @zhsso) 43 | 44 | ## v1.0.0 / 2014-08-15 45 | 46 | * [API] Remove AddWatch on Windows, use Add. 47 | * Improve documentation for exported identifiers. [#30](https://github.com/go-fsnotify/fsnotify/issues/30) 48 | * Minor updates based on feedback from golint. 49 | 50 | ## dev / 2014-07-09 51 | 52 | * Moved to [github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify). 53 | * Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno) 54 | 55 | ## dev / 2014-07-04 56 | 57 | * kqueue: fix incorrect mutex used in Close() 58 | * Update example to demonstrate usage of Op. 59 | 60 | ## dev / 2014-06-28 61 | 62 | * [API] Don't set the Write Op for attribute notifications [#4](https://github.com/go-fsnotify/fsnotify/issues/4) 63 | * Fix for String() method on Event (thanks Alex Brainman) 64 | * Don't build on Plan 9 or Solaris (thanks @4ad) 65 | 66 | ## dev / 2014-06-21 67 | 68 | * Events channel of type Event rather than *Event. 69 | * [internal] use syscall constants directly for inotify and kqueue. 70 | * [internal] kqueue: rename events to kevents and fileEvent to event. 71 | 72 | ## dev / 2014-06-19 73 | 74 | * Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally). 75 | * [internal] remove cookie from Event struct (unused). 76 | * [internal] Event struct has the same definition across every OS. 77 | * [internal] remove internal watch and removeWatch methods. 78 | 79 | ## dev / 2014-06-12 80 | 81 | * [API] Renamed Watch() to Add() and RemoveWatch() to Remove(). 82 | * [API] Pluralized channel names: Events and Errors. 83 | * [API] Renamed FileEvent struct to Event. 84 | * [API] Op constants replace methods like IsCreate(). 85 | 86 | ## dev / 2014-06-12 87 | 88 | * Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) 89 | 90 | ## dev / 2014-05-23 91 | 92 | * [API] Remove current implementation of WatchFlags. 93 | * current implementation doesn't take advantage of OS for efficiency 94 | * provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes 95 | * no tests for the current implementation 96 | * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) 97 | 98 | ## v0.9.3 / 2014-12-31 99 | 100 | * kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51) 101 | 102 | ## v0.9.2 / 2014-08-17 103 | 104 | * [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso) 105 | 106 | ## v0.9.1 / 2014-06-12 107 | 108 | * Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) 109 | 110 | ## v0.9.0 / 2014-01-17 111 | 112 | * IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany) 113 | * [Fix] kqueue: fix deadlock [#77][] (thanks @cespare) 114 | * [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library. 115 | 116 | ## v0.8.12 / 2013-11-13 117 | 118 | * [API] Remove FD_SET and friends from Linux adapter 119 | 120 | ## v0.8.11 / 2013-11-02 121 | 122 | * [Doc] Add Changelog [#72][] (thanks @nathany) 123 | * [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond) 124 | 125 | ## v0.8.10 / 2013-10-19 126 | 127 | * [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott) 128 | * [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer) 129 | * [Doc] specify OS-specific limits in README (thanks @debrando) 130 | 131 | ## v0.8.9 / 2013-09-08 132 | 133 | * [Doc] Contributing (thanks @nathany) 134 | * [Doc] update package path in example code [#63][] (thanks @paulhammond) 135 | * [Doc] GoCI badge in README (Linux only) [#60][] 136 | * [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany) 137 | 138 | ## v0.8.8 / 2013-06-17 139 | 140 | * [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie) 141 | 142 | ## v0.8.7 / 2013-06-03 143 | 144 | * [API] Make syscall flags internal 145 | * [Fix] inotify: ignore event changes 146 | * [Fix] race in symlink test [#45][] (reported by @srid) 147 | * [Fix] tests on Windows 148 | * lower case error messages 149 | 150 | ## v0.8.6 / 2013-05-23 151 | 152 | * kqueue: Use EVT_ONLY flag on Darwin 153 | * [Doc] Update README with full example 154 | 155 | ## v0.8.5 / 2013-05-09 156 | 157 | * [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg) 158 | 159 | ## v0.8.4 / 2013-04-07 160 | 161 | * [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz) 162 | 163 | ## v0.8.3 / 2013-03-13 164 | 165 | * [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin) 166 | * [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin) 167 | 168 | ## v0.8.2 / 2013-02-07 169 | 170 | * [Doc] add Authors 171 | * [Fix] fix data races for map access [#29][] (thanks @fsouza) 172 | 173 | ## v0.8.1 / 2013-01-09 174 | 175 | * [Fix] Windows path separators 176 | * [Doc] BSD License 177 | 178 | ## v0.8.0 / 2012-11-09 179 | 180 | * kqueue: directory watching improvements (thanks @vmirage) 181 | * inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto) 182 | * [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr) 183 | 184 | ## v0.7.4 / 2012-10-09 185 | 186 | * [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji) 187 | * [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig) 188 | * [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig) 189 | * [Fix] kqueue: modify after recreation of file 190 | 191 | ## v0.7.3 / 2012-09-27 192 | 193 | * [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage) 194 | * [Fix] kqueue: no longer get duplicate CREATE events 195 | 196 | ## v0.7.2 / 2012-09-01 197 | 198 | * kqueue: events for created directories 199 | 200 | ## v0.7.1 / 2012-07-14 201 | 202 | * [Fix] for renaming files 203 | 204 | ## v0.7.0 / 2012-07-02 205 | 206 | * [Feature] FSNotify flags 207 | * [Fix] inotify: Added file name back to event path 208 | 209 | ## v0.6.0 / 2012-06-06 210 | 211 | * kqueue: watch files after directory created (thanks @tmc) 212 | 213 | ## v0.5.1 / 2012-05-22 214 | 215 | * [Fix] inotify: remove all watches before Close() 216 | 217 | ## v0.5.0 / 2012-05-03 218 | 219 | * [API] kqueue: return errors during watch instead of sending over channel 220 | * kqueue: match symlink behavior on Linux 221 | * inotify: add `DELETE_SELF` (requested by @taralx) 222 | * [Fix] kqueue: handle EINTR (reported by @robfig) 223 | * [Doc] Godoc example [#1][] (thanks @davecheney) 224 | 225 | ## v0.4.0 / 2012-03-30 226 | 227 | * Go 1 released: build with go tool 228 | * [Feature] Windows support using winfsnotify 229 | * Windows does not have attribute change notifications 230 | * Roll attribute notifications into IsModify 231 | 232 | ## v0.3.0 / 2012-02-19 233 | 234 | * kqueue: add files when watch directory 235 | 236 | ## v0.2.0 / 2011-12-30 237 | 238 | * update to latest Go weekly code 239 | 240 | ## v0.1.0 / 2011-10-19 241 | 242 | * kqueue: add watch on file creation to match inotify 243 | * kqueue: create file event 244 | * inotify: ignore `IN_IGNORED` events 245 | * event String() 246 | * linux: common FileEvent functions 247 | * initial commit 248 | 249 | [#79]: https://github.com/howeyc/fsnotify/pull/79 250 | [#77]: https://github.com/howeyc/fsnotify/pull/77 251 | [#72]: https://github.com/howeyc/fsnotify/issues/72 252 | [#71]: https://github.com/howeyc/fsnotify/issues/71 253 | [#70]: https://github.com/howeyc/fsnotify/issues/70 254 | [#63]: https://github.com/howeyc/fsnotify/issues/63 255 | [#62]: https://github.com/howeyc/fsnotify/issues/62 256 | [#60]: https://github.com/howeyc/fsnotify/issues/60 257 | [#59]: https://github.com/howeyc/fsnotify/issues/59 258 | [#49]: https://github.com/howeyc/fsnotify/issues/49 259 | [#45]: https://github.com/howeyc/fsnotify/issues/45 260 | [#40]: https://github.com/howeyc/fsnotify/issues/40 261 | [#36]: https://github.com/howeyc/fsnotify/issues/36 262 | [#33]: https://github.com/howeyc/fsnotify/issues/33 263 | [#29]: https://github.com/howeyc/fsnotify/issues/29 264 | [#25]: https://github.com/howeyc/fsnotify/issues/25 265 | [#24]: https://github.com/howeyc/fsnotify/issues/24 266 | [#21]: https://github.com/howeyc/fsnotify/issues/21 267 | 268 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Issues 4 | 5 | * Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). 6 | * Please indicate the platform you are using fsnotify on. 7 | * A code example to reproduce the problem is appreciated. 8 | 9 | ## Pull Requests 10 | 11 | ### Contributor License Agreement 12 | 13 | fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). 14 | 15 | Please indicate that you have signed the CLA in your pull request. 16 | 17 | ### How fsnotify is Developed 18 | 19 | * Development is done on feature branches. 20 | * Tests are run on BSD, Linux, OS X and Windows. 21 | * Pull requests are reviewed and [applied to master][am] using [hub][]. 22 | * Maintainers may modify or squash commits rather than asking contributors to. 23 | * To issue a new release, the maintainers will: 24 | * Update the CHANGELOG 25 | * Tag a version, which will become available through gopkg.in. 26 | 27 | ### How to Fork 28 | 29 | For smooth sailing, always use the original import path. Installing with `go get` makes this easy. 30 | 31 | 1. Install from GitHub (`go get -u github.com/go-fsnotify/fsnotify`) 32 | 2. Create your feature branch (`git checkout -b my-new-feature`) 33 | 3. Ensure everything works and the tests pass (see below) 34 | 4. Commit your changes (`git commit -am 'Add some feature'`) 35 | 36 | Contribute upstream: 37 | 38 | 1. Fork fsnotify on GitHub 39 | 2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`) 40 | 3. Push to the branch (`git push fork my-new-feature`) 41 | 4. Create a new Pull Request on GitHub 42 | 43 | This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/). 44 | 45 | ### Testing 46 | 47 | fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows. 48 | 49 | Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. 50 | 51 | To aid in cross-platform testing there is a Vagrantfile for Linux and BSD. 52 | 53 | * Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) 54 | * Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder. 55 | * Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password) 56 | * Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd go-fsnotify/fsnotify; go test'`. 57 | * When you're done, you will want to halt or destroy the Vagrant boxes. 58 | 59 | Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory. 60 | 61 | Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). 62 | 63 | ### Maintainers 64 | 65 | Help maintaining fsnotify is welcome. To be a maintainer: 66 | 67 | * Submit a pull request and sign the CLA as above. 68 | * You must be able to run the test suite on Mac, Windows, Linux and BSD. 69 | 70 | To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][]. 71 | 72 | All code changes should be internal pull requests. 73 | 74 | Releases are tagged using [Semantic Versioning](http://semver.org/). 75 | 76 | [hub]: https://github.com/github/hub 77 | [am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs 78 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | Copyright (c) 2012 fsnotify Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/README.md: -------------------------------------------------------------------------------- 1 | # File system notifications for Go 2 | 3 | [![GoDoc](https://godoc.org/gopkg.in/fsnotify.v1?status.svg)](https://godoc.org/gopkg.in/fsnotify.v1) [![Coverage](http://gocover.io/_badge/github.com/go-fsnotify/fsnotify)](http://gocover.io/github.com/go-fsnotify/fsnotify) 4 | 5 | Go 1.3+ required. 6 | 7 | Cross platform: Windows, Linux, BSD and OS X. 8 | 9 | |Adapter |OS |Status | 10 | |----------|----------|----------| 11 | |inotify |Linux, Android\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)| 12 | |kqueue |BSD, OS X, iOS\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)| 13 | |ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)| 14 | |FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)| 15 | |FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)| 16 | |fanotify |Linux 2.6.37+ | | 17 | |USN Journals |Windows |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/53)| 18 | |Polling |*All* |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)| 19 | 20 | \* Android and iOS are untested. 21 | 22 | Please see [the documentation](https://godoc.org/gopkg.in/fsnotify.v1) for usage. Consult the [Wiki](https://github.com/go-fsnotify/fsnotify/wiki) for the FAQ and further information. 23 | 24 | ## API stability 25 | 26 | Two major versions of fsnotify exist. 27 | 28 | **[fsnotify.v0](https://gopkg.in/fsnotify.v0)** is API-compatible with [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify). Bugfixes *may* be backported, but I recommend upgrading to v1. 29 | 30 | ```go 31 | import "gopkg.in/fsnotify.v0" 32 | ``` 33 | 34 | \* Refer to the package as fsnotify (without the .v0 suffix). 35 | 36 | **[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with: 37 | 38 | ```go 39 | import "gopkg.in/fsnotify.v1" 40 | ``` 41 | 42 | Further API changes are [planned](https://github.com/go-fsnotify/fsnotify/milestones), but a new major revision will be tagged, so you can depend on the v1 API. 43 | 44 | **Master** may have unreleased changes. Use it to test the very latest code or when [contributing][], but don't expect it to remain API-compatible: 45 | 46 | ```go 47 | import "github.com/go-fsnotify/fsnotify" 48 | ``` 49 | 50 | ## Contributing 51 | 52 | Please refer to [CONTRIBUTING][] before opening an issue or pull request. 53 | 54 | ## Example 55 | 56 | See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go). 57 | 58 | [contributing]: https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md 59 | 60 | ## Related Projects 61 | 62 | * [notify](https://github.com/rjeczalik/notify) 63 | * [fsevents](https://github.com/go-fsnotify/fsevents) 64 | 65 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/fsnotify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !plan9,!solaris 6 | 7 | // Package fsnotify provides a platform-independent interface for file system notifications. 8 | package fsnotify 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | ) 14 | 15 | // Event represents a single file system notification. 16 | type Event struct { 17 | Name string // Relative path to the file or directory. 18 | Op Op // File operation that triggered the event. 19 | } 20 | 21 | // Op describes a set of file operations. 22 | type Op uint32 23 | 24 | // These are the generalized file operations that can trigger a notification. 25 | const ( 26 | Create Op = 1 << iota 27 | Write 28 | Remove 29 | Rename 30 | Chmod 31 | ) 32 | 33 | // String returns a string representation of the event in the form 34 | // "file: REMOVE|WRITE|..." 35 | func (e Event) String() string { 36 | // Use a buffer for efficient string concatenation 37 | var buffer bytes.Buffer 38 | 39 | if e.Op&Create == Create { 40 | buffer.WriteString("|CREATE") 41 | } 42 | if e.Op&Remove == Remove { 43 | buffer.WriteString("|REMOVE") 44 | } 45 | if e.Op&Write == Write { 46 | buffer.WriteString("|WRITE") 47 | } 48 | if e.Op&Rename == Rename { 49 | buffer.WriteString("|RENAME") 50 | } 51 | if e.Op&Chmod == Chmod { 52 | buffer.WriteString("|CHMOD") 53 | } 54 | 55 | // If buffer remains empty, return no event names 56 | if buffer.Len() == 0 { 57 | return fmt.Sprintf("%q: ", e.Name) 58 | } 59 | 60 | // Return a list of event names, with leading pipe character stripped 61 | return fmt.Sprintf("%q: %s", e.Name, buffer.String()[1:]) 62 | } 63 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/inotify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build linux 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "io" 13 | "os" 14 | "path/filepath" 15 | "strings" 16 | "sync" 17 | "syscall" 18 | "unsafe" 19 | ) 20 | 21 | // Watcher watches a set of files, delivering events to a channel. 22 | type Watcher struct { 23 | Events chan Event 24 | Errors chan error 25 | mu sync.Mutex // Map access 26 | fd int 27 | poller *fdPoller 28 | watches map[string]*watch // Map of inotify watches (key: path) 29 | paths map[int]string // Map of watched paths (key: watch descriptor) 30 | done chan struct{} // Channel for sending a "quit message" to the reader goroutine 31 | doneResp chan struct{} // Channel to respond to Close 32 | } 33 | 34 | // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. 35 | func NewWatcher() (*Watcher, error) { 36 | // Create inotify fd 37 | fd, errno := syscall.InotifyInit() 38 | if fd == -1 { 39 | return nil, errno 40 | } 41 | // Create epoll 42 | poller, err := newFdPoller(fd) 43 | if err != nil { 44 | syscall.Close(fd) 45 | return nil, err 46 | } 47 | w := &Watcher{ 48 | fd: fd, 49 | poller: poller, 50 | watches: make(map[string]*watch), 51 | paths: make(map[int]string), 52 | Events: make(chan Event), 53 | Errors: make(chan error), 54 | done: make(chan struct{}), 55 | doneResp: make(chan struct{}), 56 | } 57 | 58 | go w.readEvents() 59 | return w, nil 60 | } 61 | 62 | func (w *Watcher) isClosed() bool { 63 | select { 64 | case <-w.done: 65 | return true 66 | default: 67 | return false 68 | } 69 | } 70 | 71 | // Close removes all watches and closes the events channel. 72 | func (w *Watcher) Close() error { 73 | if w.isClosed() { 74 | return nil 75 | } 76 | 77 | // Send 'close' signal to goroutine, and set the Watcher to closed. 78 | close(w.done) 79 | 80 | // Wake up goroutine 81 | w.poller.wake() 82 | 83 | // Wait for goroutine to close 84 | <-w.doneResp 85 | 86 | return nil 87 | } 88 | 89 | // Add starts watching the named file or directory (non-recursively). 90 | func (w *Watcher) Add(name string) error { 91 | name = filepath.Clean(name) 92 | if w.isClosed() { 93 | return errors.New("inotify instance already closed") 94 | } 95 | 96 | const agnosticEvents = syscall.IN_MOVED_TO | syscall.IN_MOVED_FROM | 97 | syscall.IN_CREATE | syscall.IN_ATTRIB | syscall.IN_MODIFY | 98 | syscall.IN_MOVE_SELF | syscall.IN_DELETE | syscall.IN_DELETE_SELF 99 | 100 | var flags uint32 = agnosticEvents 101 | 102 | w.mu.Lock() 103 | watchEntry, found := w.watches[name] 104 | w.mu.Unlock() 105 | if found { 106 | watchEntry.flags |= flags 107 | flags |= syscall.IN_MASK_ADD 108 | } 109 | wd, errno := syscall.InotifyAddWatch(w.fd, name, flags) 110 | if wd == -1 { 111 | return errno 112 | } 113 | 114 | w.mu.Lock() 115 | w.watches[name] = &watch{wd: uint32(wd), flags: flags} 116 | w.paths[wd] = name 117 | w.mu.Unlock() 118 | 119 | return nil 120 | } 121 | 122 | // Remove stops watching the named file or directory (non-recursively). 123 | func (w *Watcher) Remove(name string) error { 124 | name = filepath.Clean(name) 125 | 126 | // Fetch the watch. 127 | w.mu.Lock() 128 | defer w.mu.Unlock() 129 | watch, ok := w.watches[name] 130 | 131 | // Remove it from inotify. 132 | if !ok { 133 | return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) 134 | } 135 | // inotify_rm_watch will return EINVAL if the file has been deleted; 136 | // the inotify will already have been removed. 137 | // That means we can safely delete it from our watches, whatever inotify_rm_watch does. 138 | delete(w.watches, name) 139 | success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) 140 | if success == -1 { 141 | // TODO: Perhaps it's not helpful to return an error here in every case. 142 | // the only two possible errors are: 143 | // EBADF, which happens when w.fd is not a valid file descriptor of any kind. 144 | // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. 145 | // Watch descriptors are invalidated when they are removed explicitly or implicitly; 146 | // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. 147 | return errno 148 | } 149 | return nil 150 | } 151 | 152 | type watch struct { 153 | wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) 154 | flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) 155 | } 156 | 157 | // readEvents reads from the inotify file descriptor, converts the 158 | // received events into Event objects and sends them via the Events channel 159 | func (w *Watcher) readEvents() { 160 | var ( 161 | buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events 162 | n int // Number of bytes read with read() 163 | errno error // Syscall errno 164 | ok bool // For poller.wait 165 | ) 166 | 167 | defer close(w.doneResp) 168 | defer close(w.Errors) 169 | defer close(w.Events) 170 | defer syscall.Close(w.fd) 171 | defer w.poller.close() 172 | 173 | for { 174 | // See if we have been closed. 175 | if w.isClosed() { 176 | return 177 | } 178 | 179 | ok, errno = w.poller.wait() 180 | if errno != nil { 181 | select { 182 | case w.Errors <- errno: 183 | case <-w.done: 184 | return 185 | } 186 | continue 187 | } 188 | 189 | if !ok { 190 | continue 191 | } 192 | 193 | n, errno = syscall.Read(w.fd, buf[:]) 194 | // If a signal interrupted execution, see if we've been asked to close, and try again. 195 | // http://man7.org/linux/man-pages/man7/signal.7.html : 196 | // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" 197 | if errno == syscall.EINTR { 198 | continue 199 | } 200 | 201 | // syscall.Read might have been woken up by Close. If so, we're done. 202 | if w.isClosed() { 203 | return 204 | } 205 | 206 | if n < syscall.SizeofInotifyEvent { 207 | var err error 208 | if n == 0 { 209 | // If EOF is received. This should really never happen. 210 | err = io.EOF 211 | } else if n < 0 { 212 | // If an error occured while reading. 213 | err = errno 214 | } else { 215 | // Read was too short. 216 | err = errors.New("notify: short read in readEvents()") 217 | } 218 | select { 219 | case w.Errors <- err: 220 | case <-w.done: 221 | return 222 | } 223 | continue 224 | } 225 | 226 | var offset uint32 227 | // We don't know how many events we just read into the buffer 228 | // While the offset points to at least one whole event... 229 | for offset <= uint32(n-syscall.SizeofInotifyEvent) { 230 | // Point "raw" to the event in the buffer 231 | raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) 232 | 233 | mask := uint32(raw.Mask) 234 | nameLen := uint32(raw.Len) 235 | // If the event happened to the watched directory or the watched file, the kernel 236 | // doesn't append the filename to the event, but we would like to always fill the 237 | // the "Name" field with a valid filename. We retrieve the path of the watch from 238 | // the "paths" map. 239 | w.mu.Lock() 240 | name := w.paths[int(raw.Wd)] 241 | w.mu.Unlock() 242 | if nameLen > 0 { 243 | // Point "bytes" at the first byte of the filename 244 | bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent])) 245 | // The filename is padded with NULL bytes. TrimRight() gets rid of those. 246 | name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") 247 | } 248 | 249 | event := newEvent(name, mask) 250 | 251 | // Send the events that are not ignored on the events channel 252 | if !event.ignoreLinux(mask) { 253 | select { 254 | case w.Events <- event: 255 | case <-w.done: 256 | return 257 | } 258 | } 259 | 260 | // Move to the next event in the buffer 261 | offset += syscall.SizeofInotifyEvent + nameLen 262 | } 263 | } 264 | } 265 | 266 | // Certain types of events can be "ignored" and not sent over the Events 267 | // channel. Such as events marked ignore by the kernel, or MODIFY events 268 | // against files that do not exist. 269 | func (e *Event) ignoreLinux(mask uint32) bool { 270 | // Ignore anything the inotify API says to ignore 271 | if mask&syscall.IN_IGNORED == syscall.IN_IGNORED { 272 | return true 273 | } 274 | 275 | // If the event is not a DELETE or RENAME, the file must exist. 276 | // Otherwise the event is ignored. 277 | // *Note*: this was put in place because it was seen that a MODIFY 278 | // event was sent after the DELETE. This ignores that MODIFY and 279 | // assumes a DELETE will come or has come if the file doesn't exist. 280 | if !(e.Op&Remove == Remove || e.Op&Rename == Rename) { 281 | _, statErr := os.Lstat(e.Name) 282 | return os.IsNotExist(statErr) 283 | } 284 | return false 285 | } 286 | 287 | // newEvent returns an platform-independent Event based on an inotify mask. 288 | func newEvent(name string, mask uint32) Event { 289 | e := Event{Name: name} 290 | if mask&syscall.IN_CREATE == syscall.IN_CREATE || mask&syscall.IN_MOVED_TO == syscall.IN_MOVED_TO { 291 | e.Op |= Create 292 | } 293 | if mask&syscall.IN_DELETE_SELF == syscall.IN_DELETE_SELF || mask&syscall.IN_DELETE == syscall.IN_DELETE { 294 | e.Op |= Remove 295 | } 296 | if mask&syscall.IN_MODIFY == syscall.IN_MODIFY { 297 | e.Op |= Write 298 | } 299 | if mask&syscall.IN_MOVE_SELF == syscall.IN_MOVE_SELF || mask&syscall.IN_MOVED_FROM == syscall.IN_MOVED_FROM { 300 | e.Op |= Rename 301 | } 302 | if mask&syscall.IN_ATTRIB == syscall.IN_ATTRIB { 303 | e.Op |= Chmod 304 | } 305 | return e 306 | } 307 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/inotify_poller.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build linux 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "errors" 11 | "syscall" 12 | ) 13 | 14 | type fdPoller struct { 15 | fd int // File descriptor (as returned by the inotify_init() syscall) 16 | epfd int // Epoll file descriptor 17 | pipe [2]int // Pipe for waking up 18 | } 19 | 20 | func emptyPoller(fd int) *fdPoller { 21 | poller := new(fdPoller) 22 | poller.fd = fd 23 | poller.epfd = -1 24 | poller.pipe[0] = -1 25 | poller.pipe[1] = -1 26 | return poller 27 | } 28 | 29 | // Create a new inotify poller. 30 | // This creates an inotify handler, and an epoll handler. 31 | func newFdPoller(fd int) (*fdPoller, error) { 32 | var errno error 33 | poller := emptyPoller(fd) 34 | defer func() { 35 | if errno != nil { 36 | poller.close() 37 | } 38 | }() 39 | poller.fd = fd 40 | 41 | // Create epoll fd 42 | poller.epfd, errno = syscall.EpollCreate(1) 43 | if poller.epfd == -1 { 44 | return nil, errno 45 | } 46 | // Create pipe; pipe[0] is the read end, pipe[1] the write end. 47 | errno = syscall.Pipe2(poller.pipe[:], syscall.O_NONBLOCK) 48 | if errno != nil { 49 | return nil, errno 50 | } 51 | 52 | // Register inotify fd with epoll 53 | event := syscall.EpollEvent{ 54 | Fd: int32(poller.fd), 55 | Events: syscall.EPOLLIN, 56 | } 57 | errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.fd, &event) 58 | if errno != nil { 59 | return nil, errno 60 | } 61 | 62 | // Register pipe fd with epoll 63 | event = syscall.EpollEvent{ 64 | Fd: int32(poller.pipe[0]), 65 | Events: syscall.EPOLLIN, 66 | } 67 | errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.pipe[0], &event) 68 | if errno != nil { 69 | return nil, errno 70 | } 71 | 72 | return poller, nil 73 | } 74 | 75 | // Wait using epoll. 76 | // Returns true if something is ready to be read, 77 | // false if there is not. 78 | func (poller *fdPoller) wait() (bool, error) { 79 | // 3 possible events per fd, and 2 fds, makes a maximum of 6 events. 80 | // I don't know whether epoll_wait returns the number of events returned, 81 | // or the total number of events ready. 82 | // I decided to catch both by making the buffer one larger than the maximum. 83 | events := make([]syscall.EpollEvent, 7) 84 | for { 85 | n, errno := syscall.EpollWait(poller.epfd, events, -1) 86 | if n == -1 { 87 | if errno == syscall.EINTR { 88 | continue 89 | } 90 | return false, errno 91 | } 92 | if n == 0 { 93 | // If there are no events, try again. 94 | continue 95 | } 96 | if n > 6 { 97 | // This should never happen. More events were returned than should be possible. 98 | return false, errors.New("epoll_wait returned more events than I know what to do with") 99 | } 100 | ready := events[:n] 101 | epollhup := false 102 | epollerr := false 103 | epollin := false 104 | for _, event := range ready { 105 | if event.Fd == int32(poller.fd) { 106 | if event.Events&syscall.EPOLLHUP != 0 { 107 | // This should not happen, but if it does, treat it as a wakeup. 108 | epollhup = true 109 | } 110 | if event.Events&syscall.EPOLLERR != 0 { 111 | // If an error is waiting on the file descriptor, we should pretend 112 | // something is ready to read, and let syscall.Read pick up the error. 113 | epollerr = true 114 | } 115 | if event.Events&syscall.EPOLLIN != 0 { 116 | // There is data to read. 117 | epollin = true 118 | } 119 | } 120 | if event.Fd == int32(poller.pipe[0]) { 121 | if event.Events&syscall.EPOLLHUP != 0 { 122 | // Write pipe descriptor was closed, by us. This means we're closing down the 123 | // watcher, and we should wake up. 124 | } 125 | if event.Events&syscall.EPOLLERR != 0 { 126 | // If an error is waiting on the pipe file descriptor. 127 | // This is an absolute mystery, and should never ever happen. 128 | return false, errors.New("Error on the pipe descriptor.") 129 | } 130 | if event.Events&syscall.EPOLLIN != 0 { 131 | // This is a regular wakeup, so we have to clear the buffer. 132 | err := poller.clearWake() 133 | if err != nil { 134 | return false, err 135 | } 136 | } 137 | } 138 | } 139 | 140 | if epollhup || epollerr || epollin { 141 | return true, nil 142 | } 143 | return false, nil 144 | } 145 | } 146 | 147 | // Close the write end of the poller. 148 | func (poller *fdPoller) wake() error { 149 | buf := make([]byte, 1) 150 | n, errno := syscall.Write(poller.pipe[1], buf) 151 | if n == -1 { 152 | if errno == syscall.EAGAIN { 153 | // Buffer is full, poller will wake. 154 | return nil 155 | } 156 | return errno 157 | } 158 | return nil 159 | } 160 | 161 | func (poller *fdPoller) clearWake() error { 162 | // You have to be woken up a LOT in order to get to 100! 163 | buf := make([]byte, 100) 164 | n, errno := syscall.Read(poller.pipe[0], buf) 165 | if n == -1 { 166 | if errno == syscall.EAGAIN { 167 | // Buffer is empty, someone else cleared our wake. 168 | return nil 169 | } 170 | return errno 171 | } 172 | return nil 173 | } 174 | 175 | // Close all poller file descriptors, but not the one passed to it. 176 | func (poller *fdPoller) close() { 177 | if poller.pipe[1] != -1 { 178 | syscall.Close(poller.pipe[1]) 179 | } 180 | if poller.pipe[0] != -1 { 181 | syscall.Close(poller.pipe[0]) 182 | } 183 | if poller.epfd != -1 { 184 | syscall.Close(poller.epfd) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/kqueue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build freebsd openbsd netbsd dragonfly darwin 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | "sync" 16 | "syscall" 17 | "time" 18 | ) 19 | 20 | // Watcher watches a set of files, delivering events to a channel. 21 | type Watcher struct { 22 | Events chan Event 23 | Errors chan error 24 | done chan bool // Channel for sending a "quit message" to the reader goroutine 25 | 26 | kq int // File descriptor (as returned by the kqueue() syscall). 27 | 28 | mu sync.Mutex // Protects access to watcher data 29 | watches map[string]int // Map of watched file descriptors (key: path). 30 | externalWatches map[string]bool // Map of watches added by user of the library. 31 | dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue. 32 | paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events. 33 | fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). 34 | isClosed bool // Set to true when Close() is first called 35 | } 36 | 37 | type pathInfo struct { 38 | name string 39 | isDir bool 40 | } 41 | 42 | // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. 43 | func NewWatcher() (*Watcher, error) { 44 | kq, err := kqueue() 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | w := &Watcher{ 50 | kq: kq, 51 | watches: make(map[string]int), 52 | dirFlags: make(map[string]uint32), 53 | paths: make(map[int]pathInfo), 54 | fileExists: make(map[string]bool), 55 | externalWatches: make(map[string]bool), 56 | Events: make(chan Event), 57 | Errors: make(chan error), 58 | done: make(chan bool), 59 | } 60 | 61 | go w.readEvents() 62 | return w, nil 63 | } 64 | 65 | // Close removes all watches and closes the events channel. 66 | func (w *Watcher) Close() error { 67 | w.mu.Lock() 68 | if w.isClosed { 69 | w.mu.Unlock() 70 | return nil 71 | } 72 | w.isClosed = true 73 | w.mu.Unlock() 74 | 75 | w.mu.Lock() 76 | ws := w.watches 77 | w.mu.Unlock() 78 | 79 | var err error 80 | for name := range ws { 81 | if e := w.Remove(name); e != nil && err == nil { 82 | err = e 83 | } 84 | } 85 | 86 | // Send "quit" message to the reader goroutine: 87 | w.done <- true 88 | 89 | return nil 90 | } 91 | 92 | // Add starts watching the named file or directory (non-recursively). 93 | func (w *Watcher) Add(name string) error { 94 | w.mu.Lock() 95 | w.externalWatches[name] = true 96 | w.mu.Unlock() 97 | return w.addWatch(name, noteAllEvents) 98 | } 99 | 100 | // Remove stops watching the the named file or directory (non-recursively). 101 | func (w *Watcher) Remove(name string) error { 102 | name = filepath.Clean(name) 103 | w.mu.Lock() 104 | watchfd, ok := w.watches[name] 105 | w.mu.Unlock() 106 | if !ok { 107 | return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) 108 | } 109 | 110 | const registerRemove = syscall.EV_DELETE 111 | if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { 112 | return err 113 | } 114 | 115 | syscall.Close(watchfd) 116 | 117 | w.mu.Lock() 118 | isDir := w.paths[watchfd].isDir 119 | delete(w.watches, name) 120 | delete(w.paths, watchfd) 121 | delete(w.dirFlags, name) 122 | w.mu.Unlock() 123 | 124 | // Find all watched paths that are in this directory that are not external. 125 | if isDir { 126 | var pathsToRemove []string 127 | w.mu.Lock() 128 | for _, path := range w.paths { 129 | wdir, _ := filepath.Split(path.name) 130 | if filepath.Clean(wdir) == name { 131 | if !w.externalWatches[path.name] { 132 | pathsToRemove = append(pathsToRemove, path.name) 133 | } 134 | } 135 | } 136 | w.mu.Unlock() 137 | for _, name := range pathsToRemove { 138 | // Since these are internal, not much sense in propagating error 139 | // to the user, as that will just confuse them with an error about 140 | // a path they did not explicitly watch themselves. 141 | w.Remove(name) 142 | } 143 | } 144 | 145 | return nil 146 | } 147 | 148 | // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) 149 | const noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME 150 | 151 | // keventWaitTime to block on each read from kevent 152 | var keventWaitTime = durationToTimespec(100 * time.Millisecond) 153 | 154 | // addWatch adds name to the watched file set. 155 | // The flags are interpreted as described in kevent(2). 156 | func (w *Watcher) addWatch(name string, flags uint32) error { 157 | var isDir bool 158 | // Make ./name and name equivalent 159 | name = filepath.Clean(name) 160 | 161 | w.mu.Lock() 162 | if w.isClosed { 163 | w.mu.Unlock() 164 | return errors.New("kevent instance already closed") 165 | } 166 | watchfd, alreadyWatching := w.watches[name] 167 | // We already have a watch, but we can still override flags. 168 | if alreadyWatching { 169 | isDir = w.paths[watchfd].isDir 170 | } 171 | w.mu.Unlock() 172 | 173 | if !alreadyWatching { 174 | fi, err := os.Lstat(name) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | // Don't watch sockets. 180 | if fi.Mode()&os.ModeSocket == os.ModeSocket { 181 | return nil 182 | } 183 | 184 | // Don't watch named pipes. 185 | if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { 186 | return nil 187 | } 188 | 189 | // Follow Symlinks 190 | // Unfortunately, Linux can add bogus symlinks to watch list without 191 | // issue, and Windows can't do symlinks period (AFAIK). To maintain 192 | // consistency, we will act like everything is fine. There will simply 193 | // be no file events for broken symlinks. 194 | // Hence the returns of nil on errors. 195 | if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 196 | name, err = filepath.EvalSymlinks(name) 197 | if err != nil { 198 | return nil 199 | } 200 | 201 | fi, err = os.Lstat(name) 202 | if err != nil { 203 | return nil 204 | } 205 | } 206 | 207 | watchfd, err = syscall.Open(name, openMode, 0700) 208 | if watchfd == -1 { 209 | return err 210 | } 211 | 212 | isDir = fi.IsDir() 213 | } 214 | 215 | const registerAdd = syscall.EV_ADD | syscall.EV_CLEAR | syscall.EV_ENABLE 216 | if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { 217 | syscall.Close(watchfd) 218 | return err 219 | } 220 | 221 | if !alreadyWatching { 222 | w.mu.Lock() 223 | w.watches[name] = watchfd 224 | w.paths[watchfd] = pathInfo{name: name, isDir: isDir} 225 | w.mu.Unlock() 226 | } 227 | 228 | if isDir { 229 | // Watch the directory if it has not been watched before, 230 | // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) 231 | w.mu.Lock() 232 | watchDir := (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE && 233 | (!alreadyWatching || (w.dirFlags[name]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE) 234 | // Store flags so this watch can be updated later 235 | w.dirFlags[name] = flags 236 | w.mu.Unlock() 237 | 238 | if watchDir { 239 | if err := w.watchDirectoryFiles(name); err != nil { 240 | return err 241 | } 242 | } 243 | } 244 | return nil 245 | } 246 | 247 | // readEvents reads from kqueue and converts the received kevents into 248 | // Event values that it sends down the Events channel. 249 | func (w *Watcher) readEvents() { 250 | eventBuffer := make([]syscall.Kevent_t, 10) 251 | 252 | for { 253 | // See if there is a message on the "done" channel 254 | select { 255 | case <-w.done: 256 | err := syscall.Close(w.kq) 257 | if err != nil { 258 | w.Errors <- err 259 | } 260 | close(w.Events) 261 | close(w.Errors) 262 | return 263 | default: 264 | } 265 | 266 | // Get new events 267 | kevents, err := read(w.kq, eventBuffer, &keventWaitTime) 268 | // EINTR is okay, the syscall was interrupted before timeout expired. 269 | if err != nil && err != syscall.EINTR { 270 | w.Errors <- err 271 | continue 272 | } 273 | 274 | // Flush the events we received to the Events channel 275 | for len(kevents) > 0 { 276 | kevent := &kevents[0] 277 | watchfd := int(kevent.Ident) 278 | mask := uint32(kevent.Fflags) 279 | w.mu.Lock() 280 | path := w.paths[watchfd] 281 | w.mu.Unlock() 282 | event := newEvent(path.name, mask) 283 | 284 | if path.isDir && !(event.Op&Remove == Remove) { 285 | // Double check to make sure the directory exists. This can happen when 286 | // we do a rm -fr on a recursively watched folders and we receive a 287 | // modification event first but the folder has been deleted and later 288 | // receive the delete event 289 | if _, err := os.Lstat(event.Name); os.IsNotExist(err) { 290 | // mark is as delete event 291 | event.Op |= Remove 292 | } 293 | } 294 | 295 | if event.Op&Rename == Rename || event.Op&Remove == Remove { 296 | w.Remove(event.Name) 297 | w.mu.Lock() 298 | delete(w.fileExists, event.Name) 299 | w.mu.Unlock() 300 | } 301 | 302 | if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { 303 | w.sendDirectoryChangeEvents(event.Name) 304 | } else { 305 | // Send the event on the Events channel 306 | w.Events <- event 307 | } 308 | 309 | if event.Op&Remove == Remove { 310 | // Look for a file that may have overwritten this. 311 | // For example, mv f1 f2 will delete f2, then create f2. 312 | fileDir, _ := filepath.Split(event.Name) 313 | fileDir = filepath.Clean(fileDir) 314 | w.mu.Lock() 315 | _, found := w.watches[fileDir] 316 | w.mu.Unlock() 317 | if found { 318 | // make sure the directory exists before we watch for changes. When we 319 | // do a recursive watch and perform rm -fr, the parent directory might 320 | // have gone missing, ignore the missing directory and let the 321 | // upcoming delete event remove the watch from the parent directory. 322 | if _, err := os.Lstat(fileDir); os.IsExist(err) { 323 | w.sendDirectoryChangeEvents(fileDir) 324 | // FIXME: should this be for events on files or just isDir? 325 | } 326 | } 327 | } 328 | 329 | // Move to next event 330 | kevents = kevents[1:] 331 | } 332 | } 333 | } 334 | 335 | // newEvent returns an platform-independent Event based on kqueue Fflags. 336 | func newEvent(name string, mask uint32) Event { 337 | e := Event{Name: name} 338 | if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE { 339 | e.Op |= Remove 340 | } 341 | if mask&syscall.NOTE_WRITE == syscall.NOTE_WRITE { 342 | e.Op |= Write 343 | } 344 | if mask&syscall.NOTE_RENAME == syscall.NOTE_RENAME { 345 | e.Op |= Rename 346 | } 347 | if mask&syscall.NOTE_ATTRIB == syscall.NOTE_ATTRIB { 348 | e.Op |= Chmod 349 | } 350 | return e 351 | } 352 | 353 | func newCreateEvent(name string) Event { 354 | return Event{Name: name, Op: Create} 355 | } 356 | 357 | // watchDirectoryFiles to mimic inotify when adding a watch on a directory 358 | func (w *Watcher) watchDirectoryFiles(dirPath string) error { 359 | // Get all files 360 | files, err := ioutil.ReadDir(dirPath) 361 | if err != nil { 362 | return err 363 | } 364 | 365 | for _, fileInfo := range files { 366 | filePath := filepath.Join(dirPath, fileInfo.Name()) 367 | if err := w.internalWatch(filePath, fileInfo); err != nil { 368 | return err 369 | } 370 | 371 | w.mu.Lock() 372 | w.fileExists[filePath] = true 373 | w.mu.Unlock() 374 | } 375 | 376 | return nil 377 | } 378 | 379 | // sendDirectoryEvents searches the directory for newly created files 380 | // and sends them over the event channel. This functionality is to have 381 | // the BSD version of fsnotify match Linux inotify which provides a 382 | // create event for files created in a watched directory. 383 | func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { 384 | // Get all files 385 | files, err := ioutil.ReadDir(dirPath) 386 | if err != nil { 387 | w.Errors <- err 388 | } 389 | 390 | // Search for new files 391 | for _, fileInfo := range files { 392 | filePath := filepath.Join(dirPath, fileInfo.Name()) 393 | w.mu.Lock() 394 | _, doesExist := w.fileExists[filePath] 395 | w.mu.Unlock() 396 | if !doesExist { 397 | // Send create event 398 | w.Events <- newCreateEvent(filePath) 399 | } 400 | 401 | // like watchDirectoryFiles (but without doing another ReadDir) 402 | if err := w.internalWatch(filePath, fileInfo); err != nil { 403 | return 404 | } 405 | 406 | w.mu.Lock() 407 | w.fileExists[filePath] = true 408 | w.mu.Unlock() 409 | } 410 | } 411 | 412 | func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) error { 413 | if fileInfo.IsDir() { 414 | // mimic Linux providing delete events for subdirectories 415 | // but preserve the flags used if currently watching subdirectory 416 | w.mu.Lock() 417 | flags := w.dirFlags[name] 418 | w.mu.Unlock() 419 | 420 | flags |= syscall.NOTE_DELETE 421 | return w.addWatch(name, flags) 422 | } 423 | 424 | // watch file to mimic Linux inotify 425 | return w.addWatch(name, noteAllEvents) 426 | } 427 | 428 | // kqueue creates a new kernel event queue and returns a descriptor. 429 | func kqueue() (kq int, err error) { 430 | kq, err = syscall.Kqueue() 431 | if kq == -1 { 432 | return kq, err 433 | } 434 | return kq, nil 435 | } 436 | 437 | // register events with the queue 438 | func register(kq int, fds []int, flags int, fflags uint32) error { 439 | changes := make([]syscall.Kevent_t, len(fds)) 440 | 441 | for i, fd := range fds { 442 | // SetKevent converts int to the platform-specific types: 443 | syscall.SetKevent(&changes[i], fd, syscall.EVFILT_VNODE, flags) 444 | changes[i].Fflags = fflags 445 | } 446 | 447 | // register the events 448 | success, err := syscall.Kevent(kq, changes, nil, nil) 449 | if success == -1 { 450 | return err 451 | } 452 | return nil 453 | } 454 | 455 | // read retrieves pending events, or waits until an event occurs. 456 | // A timeout of nil blocks indefinitely, while 0 polls the queue. 457 | func read(kq int, events []syscall.Kevent_t, timeout *syscall.Timespec) ([]syscall.Kevent_t, error) { 458 | n, err := syscall.Kevent(kq, nil, events, timeout) 459 | if err != nil { 460 | return nil, err 461 | } 462 | return events[0:n], nil 463 | } 464 | 465 | // durationToTimespec prepares a timeout value 466 | func durationToTimespec(d time.Duration) syscall.Timespec { 467 | return syscall.NsecToTimespec(d.Nanoseconds()) 468 | } 469 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/open_mode_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build freebsd openbsd netbsd dragonfly 6 | 7 | package fsnotify 8 | 9 | import "syscall" 10 | 11 | const openMode = syscall.O_NONBLOCK | syscall.O_RDONLY 12 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/open_mode_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin 6 | 7 | package fsnotify 8 | 9 | import "syscall" 10 | 11 | // note: this constant is not defined on BSD 12 | const openMode = syscall.O_EVTONLY 13 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fsnotify/fsnotify.v1/windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build windows 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "os" 13 | "path/filepath" 14 | "runtime" 15 | "sync" 16 | "syscall" 17 | "unsafe" 18 | ) 19 | 20 | // Watcher watches a set of files, delivering events to a channel. 21 | type Watcher struct { 22 | Events chan Event 23 | Errors chan error 24 | isClosed bool // Set to true when Close() is first called 25 | mu sync.Mutex // Map access 26 | port syscall.Handle // Handle to completion port 27 | watches watchMap // Map of watches (key: i-number) 28 | input chan *input // Inputs to the reader are sent on this channel 29 | quit chan chan<- error 30 | } 31 | 32 | // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. 33 | func NewWatcher() (*Watcher, error) { 34 | port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) 35 | if e != nil { 36 | return nil, os.NewSyscallError("CreateIoCompletionPort", e) 37 | } 38 | w := &Watcher{ 39 | port: port, 40 | watches: make(watchMap), 41 | input: make(chan *input, 1), 42 | Events: make(chan Event, 50), 43 | Errors: make(chan error), 44 | quit: make(chan chan<- error, 1), 45 | } 46 | go w.readEvents() 47 | return w, nil 48 | } 49 | 50 | // Close removes all watches and closes the events channel. 51 | func (w *Watcher) Close() error { 52 | if w.isClosed { 53 | return nil 54 | } 55 | w.isClosed = true 56 | 57 | // Send "quit" message to the reader goroutine 58 | ch := make(chan error) 59 | w.quit <- ch 60 | if err := w.wakeupReader(); err != nil { 61 | return err 62 | } 63 | return <-ch 64 | } 65 | 66 | // Add starts watching the named file or directory (non-recursively). 67 | func (w *Watcher) Add(name string) error { 68 | if w.isClosed { 69 | return errors.New("watcher already closed") 70 | } 71 | in := &input{ 72 | op: opAddWatch, 73 | path: filepath.Clean(name), 74 | flags: sys_FS_ALL_EVENTS, 75 | reply: make(chan error), 76 | } 77 | w.input <- in 78 | if err := w.wakeupReader(); err != nil { 79 | return err 80 | } 81 | return <-in.reply 82 | } 83 | 84 | // Remove stops watching the the named file or directory (non-recursively). 85 | func (w *Watcher) Remove(name string) error { 86 | in := &input{ 87 | op: opRemoveWatch, 88 | path: filepath.Clean(name), 89 | reply: make(chan error), 90 | } 91 | w.input <- in 92 | if err := w.wakeupReader(); err != nil { 93 | return err 94 | } 95 | return <-in.reply 96 | } 97 | 98 | const ( 99 | // Options for AddWatch 100 | sys_FS_ONESHOT = 0x80000000 101 | sys_FS_ONLYDIR = 0x1000000 102 | 103 | // Events 104 | sys_FS_ACCESS = 0x1 105 | sys_FS_ALL_EVENTS = 0xfff 106 | sys_FS_ATTRIB = 0x4 107 | sys_FS_CLOSE = 0x18 108 | sys_FS_CREATE = 0x100 109 | sys_FS_DELETE = 0x200 110 | sys_FS_DELETE_SELF = 0x400 111 | sys_FS_MODIFY = 0x2 112 | sys_FS_MOVE = 0xc0 113 | sys_FS_MOVED_FROM = 0x40 114 | sys_FS_MOVED_TO = 0x80 115 | sys_FS_MOVE_SELF = 0x800 116 | 117 | // Special events 118 | sys_FS_IGNORED = 0x8000 119 | sys_FS_Q_OVERFLOW = 0x4000 120 | ) 121 | 122 | func newEvent(name string, mask uint32) Event { 123 | e := Event{Name: name} 124 | if mask&sys_FS_CREATE == sys_FS_CREATE || mask&sys_FS_MOVED_TO == sys_FS_MOVED_TO { 125 | e.Op |= Create 126 | } 127 | if mask&sys_FS_DELETE == sys_FS_DELETE || mask&sys_FS_DELETE_SELF == sys_FS_DELETE_SELF { 128 | e.Op |= Remove 129 | } 130 | if mask&sys_FS_MODIFY == sys_FS_MODIFY { 131 | e.Op |= Write 132 | } 133 | if mask&sys_FS_MOVE == sys_FS_MOVE || mask&sys_FS_MOVE_SELF == sys_FS_MOVE_SELF || mask&sys_FS_MOVED_FROM == sys_FS_MOVED_FROM { 134 | e.Op |= Rename 135 | } 136 | if mask&sys_FS_ATTRIB == sys_FS_ATTRIB { 137 | e.Op |= Chmod 138 | } 139 | return e 140 | } 141 | 142 | const ( 143 | opAddWatch = iota 144 | opRemoveWatch 145 | ) 146 | 147 | const ( 148 | provisional uint64 = 1 << (32 + iota) 149 | ) 150 | 151 | type input struct { 152 | op int 153 | path string 154 | flags uint32 155 | reply chan error 156 | } 157 | 158 | type inode struct { 159 | handle syscall.Handle 160 | volume uint32 161 | index uint64 162 | } 163 | 164 | type watch struct { 165 | ov syscall.Overlapped 166 | ino *inode // i-number 167 | path string // Directory path 168 | mask uint64 // Directory itself is being watched with these notify flags 169 | names map[string]uint64 // Map of names being watched and their notify flags 170 | rename string // Remembers the old name while renaming a file 171 | buf [4096]byte 172 | } 173 | 174 | type indexMap map[uint64]*watch 175 | type watchMap map[uint32]indexMap 176 | 177 | func (w *Watcher) wakeupReader() error { 178 | e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) 179 | if e != nil { 180 | return os.NewSyscallError("PostQueuedCompletionStatus", e) 181 | } 182 | return nil 183 | } 184 | 185 | func getDir(pathname string) (dir string, err error) { 186 | attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) 187 | if e != nil { 188 | return "", os.NewSyscallError("GetFileAttributes", e) 189 | } 190 | if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 191 | dir = pathname 192 | } else { 193 | dir, _ = filepath.Split(pathname) 194 | dir = filepath.Clean(dir) 195 | } 196 | return 197 | } 198 | 199 | func getIno(path string) (ino *inode, err error) { 200 | h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), 201 | syscall.FILE_LIST_DIRECTORY, 202 | syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 203 | nil, syscall.OPEN_EXISTING, 204 | syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) 205 | if e != nil { 206 | return nil, os.NewSyscallError("CreateFile", e) 207 | } 208 | var fi syscall.ByHandleFileInformation 209 | if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { 210 | syscall.CloseHandle(h) 211 | return nil, os.NewSyscallError("GetFileInformationByHandle", e) 212 | } 213 | ino = &inode{ 214 | handle: h, 215 | volume: fi.VolumeSerialNumber, 216 | index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), 217 | } 218 | return ino, nil 219 | } 220 | 221 | // Must run within the I/O thread. 222 | func (m watchMap) get(ino *inode) *watch { 223 | if i := m[ino.volume]; i != nil { 224 | return i[ino.index] 225 | } 226 | return nil 227 | } 228 | 229 | // Must run within the I/O thread. 230 | func (m watchMap) set(ino *inode, watch *watch) { 231 | i := m[ino.volume] 232 | if i == nil { 233 | i = make(indexMap) 234 | m[ino.volume] = i 235 | } 236 | i[ino.index] = watch 237 | } 238 | 239 | // Must run within the I/O thread. 240 | func (w *Watcher) addWatch(pathname string, flags uint64) error { 241 | dir, err := getDir(pathname) 242 | if err != nil { 243 | return err 244 | } 245 | if flags&sys_FS_ONLYDIR != 0 && pathname != dir { 246 | return nil 247 | } 248 | ino, err := getIno(dir) 249 | if err != nil { 250 | return err 251 | } 252 | w.mu.Lock() 253 | watchEntry := w.watches.get(ino) 254 | w.mu.Unlock() 255 | if watchEntry == nil { 256 | if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { 257 | syscall.CloseHandle(ino.handle) 258 | return os.NewSyscallError("CreateIoCompletionPort", e) 259 | } 260 | watchEntry = &watch{ 261 | ino: ino, 262 | path: dir, 263 | names: make(map[string]uint64), 264 | } 265 | w.mu.Lock() 266 | w.watches.set(ino, watchEntry) 267 | w.mu.Unlock() 268 | flags |= provisional 269 | } else { 270 | syscall.CloseHandle(ino.handle) 271 | } 272 | if pathname == dir { 273 | watchEntry.mask |= flags 274 | } else { 275 | watchEntry.names[filepath.Base(pathname)] |= flags 276 | } 277 | if err = w.startRead(watchEntry); err != nil { 278 | return err 279 | } 280 | if pathname == dir { 281 | watchEntry.mask &= ^provisional 282 | } else { 283 | watchEntry.names[filepath.Base(pathname)] &= ^provisional 284 | } 285 | return nil 286 | } 287 | 288 | // Must run within the I/O thread. 289 | func (w *Watcher) remWatch(pathname string) error { 290 | dir, err := getDir(pathname) 291 | if err != nil { 292 | return err 293 | } 294 | ino, err := getIno(dir) 295 | if err != nil { 296 | return err 297 | } 298 | w.mu.Lock() 299 | watch := w.watches.get(ino) 300 | w.mu.Unlock() 301 | if watch == nil { 302 | return fmt.Errorf("can't remove non-existent watch for: %s", pathname) 303 | } 304 | if pathname == dir { 305 | w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED) 306 | watch.mask = 0 307 | } else { 308 | name := filepath.Base(pathname) 309 | w.sendEvent(watch.path+"\\"+name, watch.names[name]&sys_FS_IGNORED) 310 | delete(watch.names, name) 311 | } 312 | return w.startRead(watch) 313 | } 314 | 315 | // Must run within the I/O thread. 316 | func (w *Watcher) deleteWatch(watch *watch) { 317 | for name, mask := range watch.names { 318 | if mask&provisional == 0 { 319 | w.sendEvent(watch.path+"\\"+name, mask&sys_FS_IGNORED) 320 | } 321 | delete(watch.names, name) 322 | } 323 | if watch.mask != 0 { 324 | if watch.mask&provisional == 0 { 325 | w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED) 326 | } 327 | watch.mask = 0 328 | } 329 | } 330 | 331 | // Must run within the I/O thread. 332 | func (w *Watcher) startRead(watch *watch) error { 333 | if e := syscall.CancelIo(watch.ino.handle); e != nil { 334 | w.Errors <- os.NewSyscallError("CancelIo", e) 335 | w.deleteWatch(watch) 336 | } 337 | mask := toWindowsFlags(watch.mask) 338 | for _, m := range watch.names { 339 | mask |= toWindowsFlags(m) 340 | } 341 | if mask == 0 { 342 | if e := syscall.CloseHandle(watch.ino.handle); e != nil { 343 | w.Errors <- os.NewSyscallError("CloseHandle", e) 344 | } 345 | w.mu.Lock() 346 | delete(w.watches[watch.ino.volume], watch.ino.index) 347 | w.mu.Unlock() 348 | return nil 349 | } 350 | e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], 351 | uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) 352 | if e != nil { 353 | err := os.NewSyscallError("ReadDirectoryChanges", e) 354 | if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { 355 | // Watched directory was probably removed 356 | if w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) { 357 | if watch.mask&sys_FS_ONESHOT != 0 { 358 | watch.mask = 0 359 | } 360 | } 361 | err = nil 362 | } 363 | w.deleteWatch(watch) 364 | w.startRead(watch) 365 | return err 366 | } 367 | return nil 368 | } 369 | 370 | // readEvents reads from the I/O completion port, converts the 371 | // received events into Event objects and sends them via the Events channel. 372 | // Entry point to the I/O thread. 373 | func (w *Watcher) readEvents() { 374 | var ( 375 | n, key uint32 376 | ov *syscall.Overlapped 377 | ) 378 | runtime.LockOSThread() 379 | 380 | for { 381 | e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) 382 | watch := (*watch)(unsafe.Pointer(ov)) 383 | 384 | if watch == nil { 385 | select { 386 | case ch := <-w.quit: 387 | w.mu.Lock() 388 | var indexes []indexMap 389 | for _, index := range w.watches { 390 | indexes = append(indexes, index) 391 | } 392 | w.mu.Unlock() 393 | for _, index := range indexes { 394 | for _, watch := range index { 395 | w.deleteWatch(watch) 396 | w.startRead(watch) 397 | } 398 | } 399 | var err error 400 | if e := syscall.CloseHandle(w.port); e != nil { 401 | err = os.NewSyscallError("CloseHandle", e) 402 | } 403 | close(w.Events) 404 | close(w.Errors) 405 | ch <- err 406 | return 407 | case in := <-w.input: 408 | switch in.op { 409 | case opAddWatch: 410 | in.reply <- w.addWatch(in.path, uint64(in.flags)) 411 | case opRemoveWatch: 412 | in.reply <- w.remWatch(in.path) 413 | } 414 | default: 415 | } 416 | continue 417 | } 418 | 419 | switch e { 420 | case syscall.ERROR_MORE_DATA: 421 | if watch == nil { 422 | w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer") 423 | } else { 424 | // The i/o succeeded but the buffer is full. 425 | // In theory we should be building up a full packet. 426 | // In practice we can get away with just carrying on. 427 | n = uint32(unsafe.Sizeof(watch.buf)) 428 | } 429 | case syscall.ERROR_ACCESS_DENIED: 430 | // Watched directory was probably removed 431 | w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) 432 | w.deleteWatch(watch) 433 | w.startRead(watch) 434 | continue 435 | case syscall.ERROR_OPERATION_ABORTED: 436 | // CancelIo was called on this handle 437 | continue 438 | default: 439 | w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e) 440 | continue 441 | case nil: 442 | } 443 | 444 | var offset uint32 445 | for { 446 | if n == 0 { 447 | w.Events <- newEvent("", sys_FS_Q_OVERFLOW) 448 | w.Errors <- errors.New("short read in readEvents()") 449 | break 450 | } 451 | 452 | // Point "raw" to the event in the buffer 453 | raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) 454 | buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) 455 | name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) 456 | fullname := watch.path + "\\" + name 457 | 458 | var mask uint64 459 | switch raw.Action { 460 | case syscall.FILE_ACTION_REMOVED: 461 | mask = sys_FS_DELETE_SELF 462 | case syscall.FILE_ACTION_MODIFIED: 463 | mask = sys_FS_MODIFY 464 | case syscall.FILE_ACTION_RENAMED_OLD_NAME: 465 | watch.rename = name 466 | case syscall.FILE_ACTION_RENAMED_NEW_NAME: 467 | if watch.names[watch.rename] != 0 { 468 | watch.names[name] |= watch.names[watch.rename] 469 | delete(watch.names, watch.rename) 470 | mask = sys_FS_MOVE_SELF 471 | } 472 | } 473 | 474 | sendNameEvent := func() { 475 | if w.sendEvent(fullname, watch.names[name]&mask) { 476 | if watch.names[name]&sys_FS_ONESHOT != 0 { 477 | delete(watch.names, name) 478 | } 479 | } 480 | } 481 | if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { 482 | sendNameEvent() 483 | } 484 | if raw.Action == syscall.FILE_ACTION_REMOVED { 485 | w.sendEvent(fullname, watch.names[name]&sys_FS_IGNORED) 486 | delete(watch.names, name) 487 | } 488 | if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { 489 | if watch.mask&sys_FS_ONESHOT != 0 { 490 | watch.mask = 0 491 | } 492 | } 493 | if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { 494 | fullname = watch.path + "\\" + watch.rename 495 | sendNameEvent() 496 | } 497 | 498 | // Move to the next event in the buffer 499 | if raw.NextEntryOffset == 0 { 500 | break 501 | } 502 | offset += raw.NextEntryOffset 503 | 504 | // Error! 505 | if offset >= n { 506 | w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.") 507 | break 508 | } 509 | } 510 | 511 | if err := w.startRead(watch); err != nil { 512 | w.Errors <- err 513 | } 514 | } 515 | } 516 | 517 | func (w *Watcher) sendEvent(name string, mask uint64) bool { 518 | if mask == 0 { 519 | return false 520 | } 521 | event := newEvent(name, uint32(mask)) 522 | select { 523 | case ch := <-w.quit: 524 | w.quit <- ch 525 | case w.Events <- event: 526 | } 527 | return true 528 | } 529 | 530 | func toWindowsFlags(mask uint64) uint32 { 531 | var m uint32 532 | if mask&sys_FS_ACCESS != 0 { 533 | m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS 534 | } 535 | if mask&sys_FS_MODIFY != 0 { 536 | m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE 537 | } 538 | if mask&sys_FS_ATTRIB != 0 { 539 | m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES 540 | } 541 | if mask&(sys_FS_MOVE|sys_FS_CREATE|sys_FS_DELETE) != 0 { 542 | m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME 543 | } 544 | return m 545 | } 546 | 547 | func toFSnotifyFlags(action uint32) uint64 { 548 | switch action { 549 | case syscall.FILE_ACTION_ADDED: 550 | return sys_FS_CREATE 551 | case syscall.FILE_ACTION_REMOVED: 552 | return sys_FS_DELETE 553 | case syscall.FILE_ACTION_MODIFIED: 554 | return sys_FS_MODIFY 555 | case syscall.FILE_ACTION_RENAMED_OLD_NAME: 556 | return sys_FS_MOVED_FROM 557 | case syscall.FILE_ACTION_RENAMED_NEW_NAME: 558 | return sys_FS_MOVED_TO 559 | } 560 | return 0 561 | } 562 | -------------------------------------------------------------------------------- /vendor/gopkg.in/tomb.v1/LICENSE: -------------------------------------------------------------------------------- 1 | tomb - support for clean goroutine termination in Go. 2 | 3 | Copyright (c) 2010-2011 - Gustavo Niemeyer 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /vendor/gopkg.in/tomb.v1/README.md: -------------------------------------------------------------------------------- 1 | Installation and usage 2 | ---------------------- 3 | 4 | See [gopkg.in/tomb.v1](https://gopkg.in/tomb.v1) for documentation and usage details. 5 | -------------------------------------------------------------------------------- /vendor/gopkg.in/tomb.v1/tomb.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 - Gustavo Niemeyer 2 | // 3 | // All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, 9 | // this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above copyright notice, 11 | // this list of conditions and the following disclaimer in the documentation 12 | // and/or other materials provided with the distribution. 13 | // * Neither the name of the copyright holder nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | // The tomb package offers a conventional API for clean goroutine termination. 30 | // 31 | // A Tomb tracks the lifecycle of a goroutine as alive, dying or dead, 32 | // and the reason for its death. 33 | // 34 | // The zero value of a Tomb assumes that a goroutine is about to be 35 | // created or already alive. Once Kill or Killf is called with an 36 | // argument that informs the reason for death, the goroutine is in 37 | // a dying state and is expected to terminate soon. Right before the 38 | // goroutine function or method returns, Done must be called to inform 39 | // that the goroutine is indeed dead and about to stop running. 40 | // 41 | // A Tomb exposes Dying and Dead channels. These channels are closed 42 | // when the Tomb state changes in the respective way. They enable 43 | // explicit blocking until the state changes, and also to selectively 44 | // unblock select statements accordingly. 45 | // 46 | // When the tomb state changes to dying and there's still logic going 47 | // on within the goroutine, nested functions and methods may choose to 48 | // return ErrDying as their error value, as this error won't alter the 49 | // tomb state if provied to the Kill method. This is a convenient way to 50 | // follow standard Go practices in the context of a dying tomb. 51 | // 52 | // For background and a detailed example, see the following blog post: 53 | // 54 | // http://blog.labix.org/2011/10/09/death-of-goroutines-under-control 55 | // 56 | // For a more complex code snippet demonstrating the use of multiple 57 | // goroutines with a single Tomb, see: 58 | // 59 | // http://play.golang.org/p/Xh7qWsDPZP 60 | // 61 | package tomb 62 | 63 | import ( 64 | "errors" 65 | "fmt" 66 | "sync" 67 | ) 68 | 69 | // A Tomb tracks the lifecycle of a goroutine as alive, dying or dead, 70 | // and the reason for its death. 71 | // 72 | // See the package documentation for details. 73 | type Tomb struct { 74 | m sync.Mutex 75 | dying chan struct{} 76 | dead chan struct{} 77 | reason error 78 | } 79 | 80 | var ( 81 | ErrStillAlive = errors.New("tomb: still alive") 82 | ErrDying = errors.New("tomb: dying") 83 | ) 84 | 85 | func (t *Tomb) init() { 86 | t.m.Lock() 87 | if t.dead == nil { 88 | t.dead = make(chan struct{}) 89 | t.dying = make(chan struct{}) 90 | t.reason = ErrStillAlive 91 | } 92 | t.m.Unlock() 93 | } 94 | 95 | // Dead returns the channel that can be used to wait 96 | // until t.Done has been called. 97 | func (t *Tomb) Dead() <-chan struct{} { 98 | t.init() 99 | return t.dead 100 | } 101 | 102 | // Dying returns the channel that can be used to wait 103 | // until t.Kill or t.Done has been called. 104 | func (t *Tomb) Dying() <-chan struct{} { 105 | t.init() 106 | return t.dying 107 | } 108 | 109 | // Wait blocks until the goroutine is in a dead state and returns the 110 | // reason for its death. 111 | func (t *Tomb) Wait() error { 112 | t.init() 113 | <-t.dead 114 | t.m.Lock() 115 | reason := t.reason 116 | t.m.Unlock() 117 | return reason 118 | } 119 | 120 | // Done flags the goroutine as dead, and should be called a single time 121 | // right before the goroutine function or method returns. 122 | // If the goroutine was not already in a dying state before Done is 123 | // called, it will be flagged as dying and dead at once with no 124 | // error. 125 | func (t *Tomb) Done() { 126 | t.Kill(nil) 127 | close(t.dead) 128 | } 129 | 130 | // Kill flags the goroutine as dying for the given reason. 131 | // Kill may be called multiple times, but only the first 132 | // non-nil error is recorded as the reason for termination. 133 | // 134 | // If reason is ErrDying, the previous reason isn't replaced 135 | // even if it is nil. It's a runtime error to call Kill with 136 | // ErrDying if t is not in a dying state. 137 | func (t *Tomb) Kill(reason error) { 138 | t.init() 139 | t.m.Lock() 140 | defer t.m.Unlock() 141 | if reason == ErrDying { 142 | if t.reason == ErrStillAlive { 143 | panic("tomb: Kill with ErrDying while still alive") 144 | } 145 | return 146 | } 147 | if t.reason == nil || t.reason == ErrStillAlive { 148 | t.reason = reason 149 | } 150 | // If the receive on t.dying succeeds, then 151 | // it can only be because we have already closed it. 152 | // If it blocks, then we know that it needs to be closed. 153 | select { 154 | case <-t.dying: 155 | default: 156 | close(t.dying) 157 | } 158 | } 159 | 160 | // Killf works like Kill, but builds the reason providing the received 161 | // arguments to fmt.Errorf. The generated error is also returned. 162 | func (t *Tomb) Killf(f string, a ...interface{}) error { 163 | err := fmt.Errorf(f, a...) 164 | t.Kill(err) 165 | return err 166 | } 167 | 168 | // Err returns the reason for the goroutine death provided via Kill 169 | // or Killf, or ErrStillAlive when the goroutine is still alive. 170 | func (t *Tomb) Err() (reason error) { 171 | t.init() 172 | t.m.Lock() 173 | reason = t.reason 174 | t.m.Unlock() 175 | return 176 | } 177 | -------------------------------------------------------------------------------- /watch/filechanges.go: -------------------------------------------------------------------------------- 1 | package watch 2 | 3 | type FileChanges struct { 4 | Modified chan bool // Channel to get notified of modifications 5 | Truncated chan bool // Channel to get notified of truncations 6 | Deleted chan bool // Channel to get notified of deletions/renames 7 | } 8 | 9 | func NewFileChanges() *FileChanges { 10 | return &FileChanges{ 11 | make(chan bool, 1), make(chan bool, 1), make(chan bool, 1)} 12 | } 13 | 14 | func (fc *FileChanges) NotifyModified() { 15 | sendOnlyIfEmpty(fc.Modified) 16 | } 17 | 18 | func (fc *FileChanges) NotifyTruncated() { 19 | sendOnlyIfEmpty(fc.Truncated) 20 | } 21 | 22 | func (fc *FileChanges) NotifyDeleted() { 23 | sendOnlyIfEmpty(fc.Deleted) 24 | } 25 | 26 | // sendOnlyIfEmpty sends on a bool channel only if the channel has no 27 | // backlog to be read by other goroutines. This concurrency pattern 28 | // can be used to notify other goroutines if and only if they are 29 | // looking for it (i.e., subsequent notifications can be compressed 30 | // into one). 31 | func sendOnlyIfEmpty(ch chan bool) { 32 | select { 33 | case ch <- true: 34 | default: 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /watch/inotify.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 | // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 | 4 | package watch 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/hpcloud/tail/util" 12 | 13 | "gopkg.in/fsnotify/fsnotify.v1" 14 | "gopkg.in/tomb.v1" 15 | ) 16 | 17 | // InotifyFileWatcher uses inotify to monitor file changes. 18 | type InotifyFileWatcher struct { 19 | Filename string 20 | Size int64 21 | } 22 | 23 | func NewInotifyFileWatcher(filename string) *InotifyFileWatcher { 24 | fw := &InotifyFileWatcher{filepath.Clean(filename), 0} 25 | return fw 26 | } 27 | 28 | func (fw *InotifyFileWatcher) BlockUntilExists(t *tomb.Tomb) error { 29 | err := WatchCreate(fw.Filename) 30 | if err != nil { 31 | return err 32 | } 33 | defer RemoveWatchCreate(fw.Filename) 34 | 35 | // Do a real check now as the file might have been created before 36 | // calling `WatchFlags` above. 37 | if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) { 38 | // file exists, or stat returned an error. 39 | return err 40 | } 41 | 42 | events := Events(fw.Filename) 43 | 44 | for { 45 | select { 46 | case evt, ok := <-events: 47 | if !ok { 48 | return fmt.Errorf("inotify watcher has been closed") 49 | } 50 | evtName, err := filepath.Abs(evt.Name) 51 | if err != nil { 52 | return err 53 | } 54 | fwFilename, err := filepath.Abs(fw.Filename) 55 | if err != nil { 56 | return err 57 | } 58 | if evtName == fwFilename { 59 | return nil 60 | } 61 | case <-t.Dying(): 62 | return tomb.ErrDying 63 | } 64 | } 65 | panic("unreachable") 66 | } 67 | 68 | func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) { 69 | err := Watch(fw.Filename) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | changes := NewFileChanges() 75 | fw.Size = pos 76 | 77 | go func() { 78 | 79 | events := Events(fw.Filename) 80 | 81 | for { 82 | prevSize := fw.Size 83 | 84 | var evt fsnotify.Event 85 | var ok bool 86 | 87 | select { 88 | case evt, ok = <-events: 89 | if !ok { 90 | RemoveWatch(fw.Filename) 91 | return 92 | } 93 | case <-t.Dying(): 94 | RemoveWatch(fw.Filename) 95 | return 96 | } 97 | 98 | switch { 99 | case evt.Op&fsnotify.Remove == fsnotify.Remove: 100 | fallthrough 101 | 102 | case evt.Op&fsnotify.Rename == fsnotify.Rename: 103 | RemoveWatch(fw.Filename) 104 | changes.NotifyDeleted() 105 | return 106 | 107 | //With an open fd, unlink(fd) - inotify returns IN_ATTRIB (==fsnotify.Chmod) 108 | case evt.Op&fsnotify.Chmod == fsnotify.Chmod: 109 | fallthrough 110 | 111 | case evt.Op&fsnotify.Write == fsnotify.Write: 112 | fi, err := os.Stat(fw.Filename) 113 | if err != nil { 114 | if os.IsNotExist(err) { 115 | RemoveWatch(fw.Filename) 116 | changes.NotifyDeleted() 117 | return 118 | } 119 | // XXX: report this error back to the user 120 | util.Fatal("Failed to stat file %v: %v", fw.Filename, err) 121 | } 122 | fw.Size = fi.Size() 123 | 124 | if prevSize > 0 && prevSize > fw.Size { 125 | changes.NotifyTruncated() 126 | } else { 127 | changes.NotifyModified() 128 | } 129 | prevSize = fw.Size 130 | } 131 | } 132 | }() 133 | 134 | return changes, nil 135 | } 136 | -------------------------------------------------------------------------------- /watch/inotify_tracker.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 | // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 | 4 | package watch 5 | 6 | import ( 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "sync" 11 | "syscall" 12 | 13 | "github.com/hpcloud/tail/util" 14 | 15 | "gopkg.in/fsnotify/fsnotify.v1" 16 | ) 17 | 18 | type InotifyTracker struct { 19 | mux sync.Mutex 20 | watcher *fsnotify.Watcher 21 | chans map[string]chan fsnotify.Event 22 | done map[string]chan bool 23 | watchNums map[string]int 24 | watch chan *watchInfo 25 | remove chan *watchInfo 26 | error chan error 27 | } 28 | 29 | type watchInfo struct { 30 | op fsnotify.Op 31 | fname string 32 | } 33 | 34 | func (this *watchInfo) isCreate() bool { 35 | return this.op == fsnotify.Create 36 | } 37 | 38 | var ( 39 | // globally shared InotifyTracker; ensures only one fsnotify.Watcher is used 40 | shared *InotifyTracker 41 | 42 | // these are used to ensure the shared InotifyTracker is run exactly once 43 | once = sync.Once{} 44 | goRun = func() { 45 | shared = &InotifyTracker{ 46 | mux: sync.Mutex{}, 47 | chans: make(map[string]chan fsnotify.Event), 48 | done: make(map[string]chan bool), 49 | watchNums: make(map[string]int), 50 | watch: make(chan *watchInfo), 51 | remove: make(chan *watchInfo), 52 | error: make(chan error), 53 | } 54 | go shared.run() 55 | } 56 | 57 | logger = log.New(os.Stderr, "", log.LstdFlags) 58 | ) 59 | 60 | // Watch signals the run goroutine to begin watching the input filename 61 | func Watch(fname string) error { 62 | return watch(&watchInfo{ 63 | fname: fname, 64 | }) 65 | } 66 | 67 | // Watch create signals the run goroutine to begin watching the input filename 68 | // if call the WatchCreate function, don't call the Cleanup, call the RemoveWatchCreate 69 | func WatchCreate(fname string) error { 70 | return watch(&watchInfo{ 71 | op: fsnotify.Create, 72 | fname: fname, 73 | }) 74 | } 75 | 76 | func watch(winfo *watchInfo) error { 77 | // start running the shared InotifyTracker if not already running 78 | once.Do(goRun) 79 | 80 | winfo.fname = filepath.Clean(winfo.fname) 81 | shared.watch <- winfo 82 | return <-shared.error 83 | } 84 | 85 | // RemoveWatch signals the run goroutine to remove the watch for the input filename 86 | func RemoveWatch(fname string) error { 87 | return remove(&watchInfo{ 88 | fname: fname, 89 | }) 90 | } 91 | 92 | // RemoveWatch create signals the run goroutine to remove the watch for the input filename 93 | func RemoveWatchCreate(fname string) error { 94 | return remove(&watchInfo{ 95 | op: fsnotify.Create, 96 | fname: fname, 97 | }) 98 | } 99 | 100 | func remove(winfo *watchInfo) error { 101 | // start running the shared InotifyTracker if not already running 102 | once.Do(goRun) 103 | 104 | winfo.fname = filepath.Clean(winfo.fname) 105 | shared.mux.Lock() 106 | done := shared.done[winfo.fname] 107 | if done != nil { 108 | delete(shared.done, winfo.fname) 109 | close(done) 110 | } 111 | shared.mux.Unlock() 112 | 113 | shared.remove <- winfo 114 | return <-shared.error 115 | } 116 | 117 | // Events returns a channel to which FileEvents corresponding to the input filename 118 | // will be sent. This channel will be closed when removeWatch is called on this 119 | // filename. 120 | func Events(fname string) <-chan fsnotify.Event { 121 | shared.mux.Lock() 122 | defer shared.mux.Unlock() 123 | 124 | return shared.chans[fname] 125 | } 126 | 127 | // Cleanup removes the watch for the input filename if necessary. 128 | func Cleanup(fname string) error { 129 | return RemoveWatch(fname) 130 | } 131 | 132 | // watchFlags calls fsnotify.WatchFlags for the input filename and flags, creating 133 | // a new Watcher if the previous Watcher was closed. 134 | func (shared *InotifyTracker) addWatch(winfo *watchInfo) error { 135 | shared.mux.Lock() 136 | defer shared.mux.Unlock() 137 | 138 | if shared.chans[winfo.fname] == nil { 139 | shared.chans[winfo.fname] = make(chan fsnotify.Event) 140 | } 141 | if shared.done[winfo.fname] == nil { 142 | shared.done[winfo.fname] = make(chan bool) 143 | } 144 | 145 | fname := winfo.fname 146 | if winfo.isCreate() { 147 | // Watch for new files to be created in the parent directory. 148 | fname = filepath.Dir(fname) 149 | } 150 | 151 | var err error 152 | // already in inotify watch 153 | if shared.watchNums[fname] == 0 { 154 | err = shared.watcher.Add(fname) 155 | } 156 | if err == nil { 157 | shared.watchNums[fname]++ 158 | } 159 | return err 160 | } 161 | 162 | // removeWatch calls fsnotify.RemoveWatch for the input filename and closes the 163 | // corresponding events channel. 164 | func (shared *InotifyTracker) removeWatch(winfo *watchInfo) error { 165 | shared.mux.Lock() 166 | 167 | ch := shared.chans[winfo.fname] 168 | if ch != nil { 169 | delete(shared.chans, winfo.fname) 170 | close(ch) 171 | } 172 | 173 | fname := winfo.fname 174 | if winfo.isCreate() { 175 | // Watch for new files to be created in the parent directory. 176 | fname = filepath.Dir(fname) 177 | } 178 | shared.watchNums[fname]-- 179 | watchNum := shared.watchNums[fname] 180 | if watchNum == 0 { 181 | delete(shared.watchNums, fname) 182 | } 183 | shared.mux.Unlock() 184 | 185 | var err error 186 | // If we were the last ones to watch this file, unsubscribe from inotify. 187 | // This needs to happen after releasing the lock because fsnotify waits 188 | // synchronously for the kernel to acknowledge the removal of the watch 189 | // for this file, which causes us to deadlock if we still held the lock. 190 | if watchNum == 0 { 191 | err = shared.watcher.Remove(fname) 192 | } 193 | 194 | return err 195 | } 196 | 197 | // sendEvent sends the input event to the appropriate Tail. 198 | func (shared *InotifyTracker) sendEvent(event fsnotify.Event) { 199 | name := filepath.Clean(event.Name) 200 | 201 | shared.mux.Lock() 202 | ch := shared.chans[name] 203 | done := shared.done[name] 204 | shared.mux.Unlock() 205 | 206 | if ch != nil && done != nil { 207 | select { 208 | case ch <- event: 209 | case <-done: 210 | } 211 | } 212 | } 213 | 214 | // run starts the goroutine in which the shared struct reads events from its 215 | // Watcher's Event channel and sends the events to the appropriate Tail. 216 | func (shared *InotifyTracker) run() { 217 | watcher, err := fsnotify.NewWatcher() 218 | if err != nil { 219 | util.Fatal("failed to create Watcher") 220 | } 221 | shared.watcher = watcher 222 | 223 | for { 224 | select { 225 | case winfo := <-shared.watch: 226 | shared.error <- shared.addWatch(winfo) 227 | 228 | case winfo := <-shared.remove: 229 | shared.error <- shared.removeWatch(winfo) 230 | 231 | case event, open := <-shared.watcher.Events: 232 | if !open { 233 | return 234 | } 235 | shared.sendEvent(event) 236 | 237 | case err, open := <-shared.watcher.Errors: 238 | if !open { 239 | return 240 | } else if err != nil { 241 | sysErr, ok := err.(*os.SyscallError) 242 | if !ok || sysErr.Err != syscall.EINTR { 243 | logger.Printf("Error in Watcher Error channel: %s", err) 244 | } 245 | } 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /watch/polling.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 | // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 | 4 | package watch 5 | 6 | import ( 7 | "os" 8 | "runtime" 9 | "time" 10 | 11 | "github.com/hpcloud/tail/util" 12 | "gopkg.in/tomb.v1" 13 | ) 14 | 15 | // PollingFileWatcher polls the file for changes. 16 | type PollingFileWatcher struct { 17 | Filename string 18 | Size int64 19 | } 20 | 21 | func NewPollingFileWatcher(filename string) *PollingFileWatcher { 22 | fw := &PollingFileWatcher{filename, 0} 23 | return fw 24 | } 25 | 26 | var POLL_DURATION time.Duration 27 | 28 | func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error { 29 | for { 30 | if _, err := os.Stat(fw.Filename); err == nil { 31 | return nil 32 | } else if !os.IsNotExist(err) { 33 | return err 34 | } 35 | select { 36 | case <-time.After(POLL_DURATION): 37 | continue 38 | case <-t.Dying(): 39 | return tomb.ErrDying 40 | } 41 | } 42 | panic("unreachable") 43 | } 44 | 45 | func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) { 46 | origFi, err := os.Stat(fw.Filename) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | changes := NewFileChanges() 52 | var prevModTime time.Time 53 | 54 | // XXX: use tomb.Tomb to cleanly manage these goroutines. replace 55 | // the fatal (below) with tomb's Kill. 56 | 57 | fw.Size = pos 58 | 59 | go func() { 60 | prevSize := fw.Size 61 | for { 62 | select { 63 | case <-t.Dying(): 64 | return 65 | default: 66 | } 67 | 68 | time.Sleep(POLL_DURATION) 69 | fi, err := os.Stat(fw.Filename) 70 | if err != nil { 71 | // Windows cannot delete a file if a handle is still open (tail keeps one open) 72 | // so it gives access denied to anything trying to read it until all handles are released. 73 | if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) { 74 | // File does not exist (has been deleted). 75 | changes.NotifyDeleted() 76 | return 77 | } 78 | 79 | // XXX: report this error back to the user 80 | util.Fatal("Failed to stat file %v: %v", fw.Filename, err) 81 | } 82 | 83 | // File got moved/renamed? 84 | if !os.SameFile(origFi, fi) { 85 | changes.NotifyDeleted() 86 | return 87 | } 88 | 89 | // File got truncated? 90 | fw.Size = fi.Size() 91 | if prevSize > 0 && prevSize > fw.Size { 92 | changes.NotifyTruncated() 93 | prevSize = fw.Size 94 | continue 95 | } 96 | // File got bigger? 97 | if prevSize > 0 && prevSize < fw.Size { 98 | changes.NotifyModified() 99 | prevSize = fw.Size 100 | continue 101 | } 102 | prevSize = fw.Size 103 | 104 | // File was appended to (changed)? 105 | modTime := fi.ModTime() 106 | if modTime != prevModTime { 107 | prevModTime = modTime 108 | changes.NotifyModified() 109 | } 110 | } 111 | }() 112 | 113 | return changes, nil 114 | } 115 | 116 | func init() { 117 | POLL_DURATION = 250 * time.Millisecond 118 | } 119 | -------------------------------------------------------------------------------- /watch/watch.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 | // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 | 4 | package watch 5 | 6 | import "gopkg.in/tomb.v1" 7 | 8 | // FileWatcher monitors file-level events. 9 | type FileWatcher interface { 10 | // BlockUntilExists blocks until the file comes into existence. 11 | BlockUntilExists(*tomb.Tomb) error 12 | 13 | // ChangeEvents reports on changes to a file, be it modification, 14 | // deletion, renames or truncations. Returned FileChanges group of 15 | // channels will be closed, thus become unusable, after a deletion 16 | // or truncation event. 17 | // In order to properly report truncations, ChangeEvents requires 18 | // the caller to pass their current offset in the file. 19 | ChangeEvents(*tomb.Tomb, int64) (*FileChanges, error) 20 | } 21 | -------------------------------------------------------------------------------- /winfile/winfile.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package winfile 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | // issue also described here 12 | //https://codereview.appspot.com/8203043/ 13 | 14 | // https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L218 15 | func Open(path string, mode int, perm uint32) (fd syscall.Handle, err error) { 16 | if len(path) == 0 { 17 | return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND 18 | } 19 | pathp, err := syscall.UTF16PtrFromString(path) 20 | if err != nil { 21 | return syscall.InvalidHandle, err 22 | } 23 | var access uint32 24 | switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) { 25 | case syscall.O_RDONLY: 26 | access = syscall.GENERIC_READ 27 | case syscall.O_WRONLY: 28 | access = syscall.GENERIC_WRITE 29 | case syscall.O_RDWR: 30 | access = syscall.GENERIC_READ | syscall.GENERIC_WRITE 31 | } 32 | if mode&syscall.O_CREAT != 0 { 33 | access |= syscall.GENERIC_WRITE 34 | } 35 | if mode&syscall.O_APPEND != 0 { 36 | access &^= syscall.GENERIC_WRITE 37 | access |= syscall.FILE_APPEND_DATA 38 | } 39 | sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE) 40 | var sa *syscall.SecurityAttributes 41 | if mode&syscall.O_CLOEXEC == 0 { 42 | sa = makeInheritSa() 43 | } 44 | var createmode uint32 45 | switch { 46 | case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL): 47 | createmode = syscall.CREATE_NEW 48 | case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC): 49 | createmode = syscall.CREATE_ALWAYS 50 | case mode&syscall.O_CREAT == syscall.O_CREAT: 51 | createmode = syscall.OPEN_ALWAYS 52 | case mode&syscall.O_TRUNC == syscall.O_TRUNC: 53 | createmode = syscall.TRUNCATE_EXISTING 54 | default: 55 | createmode = syscall.OPEN_EXISTING 56 | } 57 | h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL, 0) 58 | return h, e 59 | } 60 | 61 | // https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L211 62 | func makeInheritSa() *syscall.SecurityAttributes { 63 | var sa syscall.SecurityAttributes 64 | sa.Length = uint32(unsafe.Sizeof(sa)) 65 | sa.InheritHandle = 1 66 | return &sa 67 | } 68 | 69 | // https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_windows.go#L133 70 | func OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) { 71 | r, e := Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm)) 72 | if e != nil { 73 | return nil, e 74 | } 75 | return os.NewFile(uintptr(r), name), nil 76 | } 77 | 78 | // https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_posix.go#L61 79 | func syscallMode(i os.FileMode) (o uint32) { 80 | o |= uint32(i.Perm()) 81 | if i&os.ModeSetuid != 0 { 82 | o |= syscall.S_ISUID 83 | } 84 | if i&os.ModeSetgid != 0 { 85 | o |= syscall.S_ISGID 86 | } 87 | if i&os.ModeSticky != 0 { 88 | o |= syscall.S_ISVTX 89 | } 90 | // No mapping for Go's ModeTemporary (plan9 only). 91 | return 92 | } 93 | --------------------------------------------------------------------------------