summaryrefslogtreecommitdiff
path: root/vendor/github.com/hpcloud
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hpcloud')
-rw-r--r--vendor/github.com/hpcloud/tail/LICENSE.txt21
-rw-r--r--vendor/github.com/hpcloud/tail/README.md28
-rw-r--r--vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go97
-rw-r--r--vendor/github.com/hpcloud/tail/ratelimiter/memory.go58
-rw-r--r--vendor/github.com/hpcloud/tail/ratelimiter/storage.go6
-rw-r--r--vendor/github.com/hpcloud/tail/tail.go438
-rw-r--r--vendor/github.com/hpcloud/tail/tail_posix.go11
-rw-r--r--vendor/github.com/hpcloud/tail/tail_windows.go12
-rw-r--r--vendor/github.com/hpcloud/tail/util/util.go48
-rw-r--r--vendor/github.com/hpcloud/tail/watch/filechanges.go36
-rw-r--r--vendor/github.com/hpcloud/tail/watch/inotify.go128
-rw-r--r--vendor/github.com/hpcloud/tail/watch/inotify_tracker.go260
-rw-r--r--vendor/github.com/hpcloud/tail/watch/polling.go118
-rw-r--r--vendor/github.com/hpcloud/tail/watch/watch.go20
-rw-r--r--vendor/github.com/hpcloud/tail/winfile/winfile.go92
15 files changed, 1373 insertions, 0 deletions
diff --git a/vendor/github.com/hpcloud/tail/LICENSE.txt b/vendor/github.com/hpcloud/tail/LICENSE.txt
new file mode 100644
index 000000000..818d802a5
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/LICENSE.txt
@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+# © Copyright 2015 Hewlett Packard Enterprise Development LP
+Copyright (c) 2014 ActiveState
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/hpcloud/tail/README.md b/vendor/github.com/hpcloud/tail/README.md
new file mode 100644
index 000000000..fb7fbc26c
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/README.md
@@ -0,0 +1,28 @@
+[![Build Status](https://travis-ci.org/hpcloud/tail.svg)](https://travis-ci.org/hpcloud/tail)
+[![Build status](https://ci.appveyor.com/api/projects/status/kohpsf3rvhjhrox6?svg=true)](https://ci.appveyor.com/project/HelionCloudFoundry/tail)
+
+# Go package for tail-ing files
+
+A Go package striving to emulate the features of the BSD `tail` program.
+
+```Go
+t, err := tail.TailFile("/var/log/nginx.log", tail.Config{Follow: true})
+for line := range t.Lines {
+ fmt.Println(line.Text)
+}
+```
+
+See [API documentation](http://godoc.org/github.com/hpcloud/tail).
+
+## Log rotation
+
+Tail comes with full support for truncation/move detection as it is
+designed to work with log rotation tools.
+
+## Installing
+
+ go get github.com/hpcloud/tail/...
+
+## Windows support
+
+This package [needs assistance](https://github.com/hpcloud/tail/labels/Windows) for full Windows support.
diff --git a/vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go b/vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go
new file mode 100644
index 000000000..358b69e7f
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/ratelimiter/leakybucket.go
@@ -0,0 +1,97 @@
+// Package ratelimiter implements the Leaky Bucket ratelimiting algorithm with memcached and in-memory backends.
+package ratelimiter
+
+import (
+ "time"
+)
+
+type LeakyBucket struct {
+ Size uint16
+ Fill float64
+ LeakInterval time.Duration // time.Duration for 1 unit of size to leak
+ Lastupdate time.Time
+ Now func() time.Time
+}
+
+func NewLeakyBucket(size uint16, leakInterval time.Duration) *LeakyBucket {
+ bucket := LeakyBucket{
+ Size: size,
+ Fill: 0,
+ LeakInterval: leakInterval,
+ Now: time.Now,
+ Lastupdate: time.Now(),
+ }
+
+ return &bucket
+}
+
+func (b *LeakyBucket) updateFill() {
+ now := b.Now()
+ if b.Fill > 0 {
+ elapsed := now.Sub(b.Lastupdate)
+
+ b.Fill -= float64(elapsed) / float64(b.LeakInterval)
+ if b.Fill < 0 {
+ b.Fill = 0
+ }
+ }
+ b.Lastupdate = now
+}
+
+func (b *LeakyBucket) Pour(amount uint16) bool {
+ b.updateFill()
+
+ var newfill float64 = b.Fill + float64(amount)
+
+ if newfill > float64(b.Size) {
+ return false
+ }
+
+ b.Fill = newfill
+
+ return true
+}
+
+// The time at which this bucket will be completely drained
+func (b *LeakyBucket) DrainedAt() time.Time {
+ return b.Lastupdate.Add(time.Duration(b.Fill * float64(b.LeakInterval)))
+}
+
+// The duration until this bucket is completely drained
+func (b *LeakyBucket) TimeToDrain() time.Duration {
+ return b.DrainedAt().Sub(b.Now())
+}
+
+func (b *LeakyBucket) TimeSinceLastUpdate() time.Duration {
+ return b.Now().Sub(b.Lastupdate)
+}
+
+type LeakyBucketSer struct {
+ Size uint16
+ Fill float64
+ LeakInterval time.Duration // time.Duration for 1 unit of size to leak
+ Lastupdate time.Time
+}
+
+func (b *LeakyBucket) Serialise() *LeakyBucketSer {
+ bucket := LeakyBucketSer{
+ Size: b.Size,
+ Fill: b.Fill,
+ LeakInterval: b.LeakInterval,
+ Lastupdate: b.Lastupdate,
+ }
+
+ return &bucket
+}
+
+func (b *LeakyBucketSer) DeSerialise() *LeakyBucket {
+ bucket := LeakyBucket{
+ Size: b.Size,
+ Fill: b.Fill,
+ LeakInterval: b.LeakInterval,
+ Lastupdate: b.Lastupdate,
+ Now: time.Now,
+ }
+
+ return &bucket
+}
diff --git a/vendor/github.com/hpcloud/tail/ratelimiter/memory.go b/vendor/github.com/hpcloud/tail/ratelimiter/memory.go
new file mode 100644
index 000000000..8f6a5784a
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/ratelimiter/memory.go
@@ -0,0 +1,58 @@
+package ratelimiter
+
+import (
+ "errors"
+ "time"
+)
+
+const GC_SIZE int = 100
+
+type Memory struct {
+ store map[string]LeakyBucket
+ lastGCCollected time.Time
+}
+
+func NewMemory() *Memory {
+ m := new(Memory)
+ m.store = make(map[string]LeakyBucket)
+ m.lastGCCollected = time.Now()
+ return m
+}
+
+func (m *Memory) GetBucketFor(key string) (*LeakyBucket, error) {
+
+ bucket, ok := m.store[key]
+ if !ok {
+ return nil, errors.New("miss")
+ }
+
+ return &bucket, nil
+}
+
+func (m *Memory) SetBucketFor(key string, bucket LeakyBucket) error {
+
+ if len(m.store) > GC_SIZE {
+ m.GarbageCollect()
+ }
+
+ m.store[key] = bucket
+
+ return nil
+}
+
+func (m *Memory) GarbageCollect() {
+ now := time.Now()
+
+ // rate limit GC to once per minute
+ if now.Add(60*time.Second).Unix() > m.lastGCCollected.Unix() {
+
+ for key, bucket := range m.store {
+ // if the bucket is drained, then GC
+ if bucket.DrainedAt().Unix() > now.Unix() {
+ delete(m.store, key)
+ }
+ }
+
+ m.lastGCCollected = now
+ }
+}
diff --git a/vendor/github.com/hpcloud/tail/ratelimiter/storage.go b/vendor/github.com/hpcloud/tail/ratelimiter/storage.go
new file mode 100644
index 000000000..89b2fe882
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/ratelimiter/storage.go
@@ -0,0 +1,6 @@
+package ratelimiter
+
+type Storage interface {
+ GetBucketFor(string) (*LeakyBucket, error)
+ SetBucketFor(string, LeakyBucket) error
+}
diff --git a/vendor/github.com/hpcloud/tail/tail.go b/vendor/github.com/hpcloud/tail/tail.go
new file mode 100644
index 000000000..2d252d605
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/tail.go
@@ -0,0 +1,438 @@
+// Copyright (c) 2015 HPE Software Inc. All rights reserved.
+// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
+
+package tail
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/hpcloud/tail/ratelimiter"
+ "github.com/hpcloud/tail/util"
+ "github.com/hpcloud/tail/watch"
+ "gopkg.in/tomb.v1"
+)
+
+var (
+ ErrStop = fmt.Errorf("tail should now stop")
+)
+
+type Line struct {
+ Text string
+ Time time.Time
+ Err error // Error from tail
+}
+
+// NewLine returns a Line with present time.
+func NewLine(text string) *Line {
+ return &Line{text, time.Now(), nil}
+}
+
+// SeekInfo represents arguments to `os.Seek`
+type SeekInfo struct {
+ Offset int64
+ Whence int // os.SEEK_*
+}
+
+type logger interface {
+ Fatal(v ...interface{})
+ Fatalf(format string, v ...interface{})
+ Fatalln(v ...interface{})
+ Panic(v ...interface{})
+ Panicf(format string, v ...interface{})
+ Panicln(v ...interface{})
+ Print(v ...interface{})
+ Printf(format string, v ...interface{})
+ Println(v ...interface{})
+}
+
+// Config is used to specify how a file must be tailed.
+type Config struct {
+ // File-specifc
+ Location *SeekInfo // Seek to this location before tailing
+ ReOpen bool // Reopen recreated files (tail -F)
+ MustExist bool // Fail early if the file does not exist
+ Poll bool // Poll for file changes instead of using inotify
+ Pipe bool // Is a named pipe (mkfifo)
+ RateLimiter *ratelimiter.LeakyBucket
+
+ // Generic IO
+ Follow bool // Continue looking for new lines (tail -f)
+ MaxLineSize int // If non-zero, split longer lines into multiple lines
+
+ // Logger, when nil, is set to tail.DefaultLogger
+ // To disable logging: set field to tail.DiscardingLogger
+ Logger logger
+}
+
+type Tail struct {
+ Filename string
+ Lines chan *Line
+ Config
+
+ file *os.File
+ reader *bufio.Reader
+
+ watcher watch.FileWatcher
+ changes *watch.FileChanges
+
+ tomb.Tomb // provides: Done, Kill, Dying
+
+ lk sync.Mutex
+}
+
+var (
+ // DefaultLogger is used when Config.Logger == nil
+ DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
+ // DiscardingLogger can be used to disable logging output
+ DiscardingLogger = log.New(ioutil.Discard, "", 0)
+)
+
+// TailFile begins tailing the file. Output stream is made available
+// via the `Tail.Lines` channel. To handle errors during tailing,
+// invoke the `Wait` or `Err` method after finishing reading from the
+// `Lines` channel.
+func TailFile(filename string, config Config) (*Tail, error) {
+ if config.ReOpen && !config.Follow {
+ util.Fatal("cannot set ReOpen without Follow.")
+ }
+
+ t := &Tail{
+ Filename: filename,
+ Lines: make(chan *Line),
+ Config: config,
+ }
+
+ // when Logger was not specified in config, use default logger
+ if t.Logger == nil {
+ t.Logger = log.New(os.Stderr, "", log.LstdFlags)
+ }
+
+ if t.Poll {
+ t.watcher = watch.NewPollingFileWatcher(filename)
+ } else {
+ t.watcher = watch.NewInotifyFileWatcher(filename)
+ }
+
+ if t.MustExist {
+ var err error
+ t.file, err = OpenFile(t.Filename)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ go t.tailFileSync()
+
+ return t, nil
+}
+
+// Return the file's current position, like stdio's ftell().
+// But this value is not very accurate.
+// it may readed one line in the chan(tail.Lines),
+// so it may lost one line.
+func (tail *Tail) Tell() (offset int64, err error) {
+ if tail.file == nil {
+ return
+ }
+ offset, err = tail.file.Seek(0, os.SEEK_CUR)
+ if err != nil {
+ return
+ }
+
+ tail.lk.Lock()
+ defer tail.lk.Unlock()
+ if tail.reader == nil {
+ return
+ }
+
+ offset -= int64(tail.reader.Buffered())
+ return
+}
+
+// Stop stops the tailing activity.
+func (tail *Tail) Stop() error {
+ tail.Kill(nil)
+ return tail.Wait()
+}
+
+// StopAtEOF stops tailing as soon as the end of the file is reached.
+func (tail *Tail) StopAtEOF() error {
+ tail.Kill(errStopAtEOF)
+ return tail.Wait()
+}
+
+var errStopAtEOF = errors.New("tail: stop at eof")
+
+func (tail *Tail) close() {
+ close(tail.Lines)
+ tail.closeFile()
+}
+
+func (tail *Tail) closeFile() {
+ if tail.file != nil {
+ tail.file.Close()
+ tail.file = nil
+ }
+}
+
+func (tail *Tail) reopen() error {
+ tail.closeFile()
+ for {
+ var err error
+ tail.file, err = OpenFile(tail.Filename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
+ if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
+ if err == tomb.ErrDying {
+ return err
+ }
+ return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
+ }
+ continue
+ }
+ return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
+ }
+ break
+ }
+ return nil
+}
+
+func (tail *Tail) readLine() (string, error) {
+ tail.lk.Lock()
+ line, err := tail.reader.ReadString('\n')
+ tail.lk.Unlock()
+ if err != nil {
+ // Note ReadString "returns the data read before the error" in
+ // case of an error, including EOF, so we return it as is. The
+ // caller is expected to process it if err is EOF.
+ return line, err
+ }
+
+ line = strings.TrimRight(line, "\n")
+
+ return line, err
+}
+
+func (tail *Tail) tailFileSync() {
+ defer tail.Done()
+ defer tail.close()
+
+ if !tail.MustExist {
+ // deferred first open.
+ err := tail.reopen()
+ if err != nil {
+ if err != tomb.ErrDying {
+ tail.Kill(err)
+ }
+ return
+ }
+ }
+
+ // Seek to requested location on first open of the file.
+ if tail.Location != nil {
+ _, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
+ tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
+ if err != nil {
+ tail.Killf("Seek error on %s: %s", tail.Filename, err)
+ return
+ }
+ }
+
+ tail.openReader()
+
+ var offset int64 = 0
+ var err error
+
+ // Read line by line.
+ for {
+ // do not seek in named pipes
+ if !tail.Pipe {
+ // grab the position in case we need to back up in the event of a half-line
+ offset, err = tail.Tell()
+ if err != nil {
+ tail.Kill(err)
+ return
+ }
+ }
+
+ line, err := tail.readLine()
+
+ // Process `line` even if err is EOF.
+ if err == nil {
+ cooloff := !tail.sendLine(line)
+ if cooloff {
+ // Wait a second before seeking till the end of
+ // file when rate limit is reached.
+ msg := fmt.Sprintf(
+ "Too much log activity; waiting a second " +
+ "before resuming tailing")
+ tail.Lines <- &Line{msg, time.Now(), fmt.Errorf(msg)}
+ select {
+ case <-time.After(time.Second):
+ case <-tail.Dying():
+ return
+ }
+ if err := tail.seekEnd(); err != nil {
+ tail.Kill(err)
+ return
+ }
+ }
+ } else if err == io.EOF {
+ if !tail.Follow {
+ if line != "" {
+ tail.sendLine(line)
+ }
+ return
+ }
+
+ if tail.Follow && line != "" {
+ // this has the potential to never return the last line if
+ // it's not followed by a newline; seems a fair trade here
+ err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0})
+ if err != nil {
+ tail.Kill(err)
+ return
+ }
+ }
+
+ // When EOF is reached, wait for more data to become
+ // available. Wait strategy is based on the `tail.watcher`
+ // implementation (inotify or polling).
+ err := tail.waitForChanges()
+ if err != nil {
+ if err != ErrStop {
+ tail.Kill(err)
+ }
+ return
+ }
+ } else {
+ // non-EOF error
+ tail.Killf("Error reading %s: %s", tail.Filename, err)
+ return
+ }
+
+ select {
+ case <-tail.Dying():
+ if tail.Err() == errStopAtEOF {
+ continue
+ }
+ return
+ default:
+ }
+ }
+}
+
+// waitForChanges waits until the file has been appended, deleted,
+// moved or truncated. When moved or deleted - the file will be
+// reopened if ReOpen is true. Truncated files are always reopened.
+func (tail *Tail) waitForChanges() error {
+ if tail.changes == nil {
+ pos, err := tail.file.Seek(0, os.SEEK_CUR)
+ if err != nil {
+ return err
+ }
+ tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos)
+ if err != nil {
+ return err
+ }
+ }
+
+ select {
+ case <-tail.changes.Modified:
+ return nil
+ case <-tail.changes.Deleted:
+ tail.changes = nil
+ if tail.ReOpen {
+ // XXX: we must not log from a library.
+ tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
+ if err := tail.reopen(); err != nil {
+ return err
+ }
+ tail.Logger.Printf("Successfully reopened %s", tail.Filename)
+ tail.openReader()
+ return nil
+ } else {
+ tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
+ return ErrStop
+ }
+ case <-tail.changes.Truncated:
+ // Always reopen truncated files (Follow is true)
+ tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
+ if err := tail.reopen(); err != nil {
+ return err
+ }
+ tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
+ tail.openReader()
+ return nil
+ case <-tail.Dying():
+ return ErrStop
+ }
+ panic("unreachable")
+}
+
+func (tail *Tail) openReader() {
+ if tail.MaxLineSize > 0 {
+ // add 2 to account for newline characters
+ tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
+ } else {
+ tail.reader = bufio.NewReader(tail.file)
+ }
+}
+
+func (tail *Tail) seekEnd() error {
+ return tail.seekTo(SeekInfo{Offset: 0, Whence: os.SEEK_END})
+}
+
+func (tail *Tail) seekTo(pos SeekInfo) error {
+ _, err := tail.file.Seek(pos.Offset, pos.Whence)
+ if err != nil {
+ return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
+ }
+ // Reset the read buffer whenever the file is re-seek'ed
+ tail.reader.Reset(tail.file)
+ return nil
+}
+
+// sendLine sends the line(s) to Lines channel, splitting longer lines
+// if necessary. Return false if rate limit is reached.
+func (tail *Tail) sendLine(line string) bool {
+ now := time.Now()
+ lines := []string{line}
+
+ // Split longer lines
+ if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
+ lines = util.PartitionString(line, tail.MaxLineSize)
+ }
+
+ for _, line := range lines {
+ tail.Lines <- &Line{line, now, nil}
+ }
+
+ if tail.Config.RateLimiter != nil {
+ ok := tail.Config.RateLimiter.Pour(uint16(len(lines)))
+ if !ok {
+ tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n",
+ tail.Filename)
+ return false
+ }
+ }
+
+ return true
+}
+
+// Cleanup removes inotify watches added by the tail package. This function is
+// meant to be invoked from a process's exit handler. Linux kernel may not
+// automatically remove inotify watches after the process exits.
+func (tail *Tail) Cleanup() {
+ watch.Cleanup(tail.Filename)
+}
diff --git a/vendor/github.com/hpcloud/tail/tail_posix.go b/vendor/github.com/hpcloud/tail/tail_posix.go
new file mode 100644
index 000000000..bc4dc3357
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/tail_posix.go
@@ -0,0 +1,11 @@
+// +build linux darwin freebsd netbsd openbsd
+
+package tail
+
+import (
+ "os"
+)
+
+func OpenFile(name string) (file *os.File, err error) {
+ return os.Open(name)
+}
diff --git a/vendor/github.com/hpcloud/tail/tail_windows.go b/vendor/github.com/hpcloud/tail/tail_windows.go
new file mode 100644
index 000000000..ef2cfca1b
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/tail_windows.go
@@ -0,0 +1,12 @@
+// +build windows
+
+package tail
+
+import (
+ "github.com/hpcloud/tail/winfile"
+ "os"
+)
+
+func OpenFile(name string) (file *os.File, err error) {
+ return winfile.OpenFile(name, os.O_RDONLY, 0)
+}
diff --git a/vendor/github.com/hpcloud/tail/util/util.go b/vendor/github.com/hpcloud/tail/util/util.go
new file mode 100644
index 000000000..54151fe39
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/util/util.go
@@ -0,0 +1,48 @@
+// Copyright (c) 2015 HPE Software Inc. All rights reserved.
+// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
+
+package util
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "runtime/debug"
+)
+
+type Logger struct {
+ *log.Logger
+}
+
+var LOGGER = &Logger{log.New(os.Stderr, "", log.LstdFlags)}
+
+// fatal is like panic except it displays only the current goroutine's stack.
+func Fatal(format string, v ...interface{}) {
+ // https://github.com/hpcloud/log/blob/master/log.go#L45
+ LOGGER.Output(2, fmt.Sprintf("FATAL -- "+format, v...)+"\n"+string(debug.Stack()))
+ os.Exit(1)
+}
+
+// partitionString partitions the string into chunks of given size,
+// with the last chunk of variable size.
+func PartitionString(s string, chunkSize int) []string {
+ if chunkSize <= 0 {
+ panic("invalid chunkSize")
+ }
+ length := len(s)
+ chunks := 1 + length/chunkSize
+ start := 0
+ end := chunkSize
+ parts := make([]string, 0, chunks)
+ for {
+ if end > length {
+ end = length
+ }
+ parts = append(parts, s[start:end])
+ if end == length {
+ break
+ }
+ start, end = end, end+chunkSize
+ }
+ return parts
+}
diff --git a/vendor/github.com/hpcloud/tail/watch/filechanges.go b/vendor/github.com/hpcloud/tail/watch/filechanges.go
new file mode 100644
index 000000000..3ce5dcecb
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/watch/filechanges.go
@@ -0,0 +1,36 @@
+package watch
+
+type FileChanges struct {
+ Modified chan bool // Channel to get notified of modifications
+ Truncated chan bool // Channel to get notified of truncations
+ Deleted chan bool // Channel to get notified of deletions/renames
+}
+
+func NewFileChanges() *FileChanges {
+ return &FileChanges{
+ make(chan bool), make(chan bool), make(chan bool)}
+}
+
+func (fc *FileChanges) NotifyModified() {
+ sendOnlyIfEmpty(fc.Modified)
+}
+
+func (fc *FileChanges) NotifyTruncated() {
+ sendOnlyIfEmpty(fc.Truncated)
+}
+
+func (fc *FileChanges) NotifyDeleted() {
+ sendOnlyIfEmpty(fc.Deleted)
+}
+
+// sendOnlyIfEmpty sends on a bool channel only if the channel has no
+// backlog to be read by other goroutines. This concurrency pattern
+// can be used to notify other goroutines if and only if they are
+// looking for it (i.e., subsequent notifications can be compressed
+// into one).
+func sendOnlyIfEmpty(ch chan bool) {
+ select {
+ case ch <- true:
+ default:
+ }
+}
diff --git a/vendor/github.com/hpcloud/tail/watch/inotify.go b/vendor/github.com/hpcloud/tail/watch/inotify.go
new file mode 100644
index 000000000..4478f1e1a
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/watch/inotify.go
@@ -0,0 +1,128 @@
+// Copyright (c) 2015 HPE Software Inc. All rights reserved.
+// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
+
+package watch
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/hpcloud/tail/util"
+
+ "gopkg.in/fsnotify.v1"
+ "gopkg.in/tomb.v1"
+)
+
+// InotifyFileWatcher uses inotify to monitor file changes.
+type InotifyFileWatcher struct {
+ Filename string
+ Size int64
+}
+
+func NewInotifyFileWatcher(filename string) *InotifyFileWatcher {
+ fw := &InotifyFileWatcher{filepath.Clean(filename), 0}
+ return fw
+}
+
+func (fw *InotifyFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
+ err := WatchCreate(fw.Filename)
+ if err != nil {
+ return err
+ }
+ defer RemoveWatchCreate(fw.Filename)
+
+ // Do a real check now as the file might have been created before
+ // calling `WatchFlags` above.
+ if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) {
+ // file exists, or stat returned an error.
+ return err
+ }
+
+ events := Events(fw.Filename)
+
+ for {
+ select {
+ case evt, ok := <-events:
+ if !ok {
+ return fmt.Errorf("inotify watcher has been closed")
+ }
+ evtName, err := filepath.Abs(evt.Name)
+ if err != nil {
+ return err
+ }
+ fwFilename, err := filepath.Abs(fw.Filename)
+ if err != nil {
+ return err
+ }
+ if evtName == fwFilename {
+ return nil
+ }
+ case <-t.Dying():
+ return tomb.ErrDying
+ }
+ }
+ panic("unreachable")
+}
+
+func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
+ err := Watch(fw.Filename)
+ if err != nil {
+ return nil, err
+ }
+
+ changes := NewFileChanges()
+ fw.Size = pos
+
+ go func() {
+ defer RemoveWatch(fw.Filename)
+
+ events := Events(fw.Filename)
+
+ for {
+ prevSize := fw.Size
+
+ var evt fsnotify.Event
+ var ok bool
+
+ select {
+ case evt, ok = <-events:
+ if !ok {
+ return
+ }
+ case <-t.Dying():
+ return
+ }
+
+ switch {
+ case evt.Op&fsnotify.Remove == fsnotify.Remove:
+ fallthrough
+
+ case evt.Op&fsnotify.Rename == fsnotify.Rename:
+ changes.NotifyDeleted()
+ return
+
+ case evt.Op&fsnotify.Write == fsnotify.Write:
+ fi, err := os.Stat(fw.Filename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ changes.NotifyDeleted()
+ return
+ }
+ // XXX: report this error back to the user
+ util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
+ }
+ fw.Size = fi.Size()
+
+ if prevSize > 0 && prevSize > fw.Size {
+ changes.NotifyTruncated()
+ } else {
+ changes.NotifyModified()
+ }
+ prevSize = fw.Size
+ }
+ }
+ }()
+
+ return changes, nil
+}
diff --git a/vendor/github.com/hpcloud/tail/watch/inotify_tracker.go b/vendor/github.com/hpcloud/tail/watch/inotify_tracker.go
new file mode 100644
index 000000000..03be4275c
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/watch/inotify_tracker.go
@@ -0,0 +1,260 @@
+// Copyright (c) 2015 HPE Software Inc. All rights reserved.
+// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
+
+package watch
+
+import (
+ "log"
+ "os"
+ "path/filepath"
+ "sync"
+ "syscall"
+
+ "github.com/hpcloud/tail/util"
+
+ "gopkg.in/fsnotify.v1"
+)
+
+type InotifyTracker struct {
+ mux sync.Mutex
+ watcher *fsnotify.Watcher
+ chans map[string]chan fsnotify.Event
+ done map[string]chan bool
+ watchNums map[string]int
+ watch chan *watchInfo
+ remove chan *watchInfo
+ error chan error
+}
+
+type watchInfo struct {
+ op fsnotify.Op
+ fname string
+}
+
+func (this *watchInfo) isCreate() bool {
+ return this.op == fsnotify.Create
+}
+
+var (
+ // globally shared InotifyTracker; ensures only one fsnotify.Watcher is used
+ shared *InotifyTracker
+
+ // these are used to ensure the shared InotifyTracker is run exactly once
+ once = sync.Once{}
+ goRun = func() {
+ shared = &InotifyTracker{
+ mux: sync.Mutex{},
+ chans: make(map[string]chan fsnotify.Event),
+ done: make(map[string]chan bool),
+ watchNums: make(map[string]int),
+ watch: make(chan *watchInfo),
+ remove: make(chan *watchInfo),
+ error: make(chan error),
+ }
+ go shared.run()
+ }
+
+ logger = log.New(os.Stderr, "", log.LstdFlags)
+)
+
+// Watch signals the run goroutine to begin watching the input filename
+func Watch(fname string) error {
+ return watch(&watchInfo{
+ fname: fname,
+ })
+}
+
+// Watch create signals the run goroutine to begin watching the input filename
+// if call the WatchCreate function, don't call the Cleanup, call the RemoveWatchCreate
+func WatchCreate(fname string) error {
+ return watch(&watchInfo{
+ op: fsnotify.Create,
+ fname: fname,
+ })
+}
+
+func watch(winfo *watchInfo) error {
+ // start running the shared InotifyTracker if not already running
+ once.Do(goRun)
+
+ winfo.fname = filepath.Clean(winfo.fname)
+ shared.watch <- winfo
+ return <-shared.error
+}
+
+// RemoveWatch signals the run goroutine to remove the watch for the input filename
+func RemoveWatch(fname string) {
+ remove(&watchInfo{
+ fname: fname,
+ })
+}
+
+// RemoveWatch create signals the run goroutine to remove the watch for the input filename
+func RemoveWatchCreate(fname string) {
+ remove(&watchInfo{
+ op: fsnotify.Create,
+ fname: fname,
+ })
+}
+
+func remove(winfo *watchInfo) {
+ // start running the shared InotifyTracker if not already running
+ once.Do(goRun)
+
+ winfo.fname = filepath.Clean(winfo.fname)
+ shared.mux.Lock()
+ done := shared.done[winfo.fname]
+ if done != nil {
+ delete(shared.done, winfo.fname)
+ close(done)
+ }
+
+ fname := winfo.fname
+ if winfo.isCreate() {
+ // Watch for new files to be created in the parent directory.
+ fname = filepath.Dir(fname)
+ }
+ shared.watchNums[fname]--
+ watchNum := shared.watchNums[fname]
+ if watchNum == 0 {
+ delete(shared.watchNums, fname)
+ }
+ shared.mux.Unlock()
+
+ // If we were the last ones to watch this file, unsubscribe from inotify.
+ // This needs to happen after releasing the lock because fsnotify waits
+ // synchronously for the kernel to acknowledge the removal of the watch
+ // for this file, which causes us to deadlock if we still held the lock.
+ if watchNum == 0 {
+ shared.watcher.Remove(fname)
+ }
+ shared.remove <- winfo
+}
+
+// Events returns a channel to which FileEvents corresponding to the input filename
+// will be sent. This channel will be closed when removeWatch is called on this
+// filename.
+func Events(fname string) <-chan fsnotify.Event {
+ shared.mux.Lock()
+ defer shared.mux.Unlock()
+
+ return shared.chans[fname]
+}
+
+// Cleanup removes the watch for the input filename if necessary.
+func Cleanup(fname string) {
+ RemoveWatch(fname)
+}
+
+// watchFlags calls fsnotify.WatchFlags for the input filename and flags, creating
+// a new Watcher if the previous Watcher was closed.
+func (shared *InotifyTracker) addWatch(winfo *watchInfo) error {
+ shared.mux.Lock()
+ defer shared.mux.Unlock()
+
+ if shared.chans[winfo.fname] == nil {
+ shared.chans[winfo.fname] = make(chan fsnotify.Event)
+ shared.done[winfo.fname] = make(chan bool)
+ }
+
+ fname := winfo.fname
+ if winfo.isCreate() {
+ // Watch for new files to be created in the parent directory.
+ fname = filepath.Dir(fname)
+ }
+
+ // already in inotify watch
+ if shared.watchNums[fname] > 0 {
+ shared.watchNums[fname]++
+ if winfo.isCreate() {
+ shared.watchNums[winfo.fname]++
+ }
+ return nil
+ }
+
+ err := shared.watcher.Add(fname)
+ if err == nil {
+ shared.watchNums[fname]++
+ if winfo.isCreate() {
+ shared.watchNums[winfo.fname]++
+ }
+ }
+ return err
+}
+
+// removeWatch calls fsnotify.RemoveWatch for the input filename and closes the
+// corresponding events channel.
+func (shared *InotifyTracker) removeWatch(winfo *watchInfo) {
+ shared.mux.Lock()
+ defer shared.mux.Unlock()
+
+ ch := shared.chans[winfo.fname]
+ if ch == nil {
+ return
+ }
+
+ delete(shared.chans, winfo.fname)
+ close(ch)
+
+ if !winfo.isCreate() {
+ return
+ }
+
+ shared.watchNums[winfo.fname]--
+ if shared.watchNums[winfo.fname] == 0 {
+ delete(shared.watchNums, winfo.fname)
+ }
+}
+
+// sendEvent sends the input event to the appropriate Tail.
+func (shared *InotifyTracker) sendEvent(event fsnotify.Event) {
+ name := filepath.Clean(event.Name)
+
+ shared.mux.Lock()
+ ch := shared.chans[name]
+ done := shared.done[name]
+ shared.mux.Unlock()
+
+ if ch != nil && done != nil {
+ select {
+ case ch <- event:
+ case <-done:
+ }
+ }
+}
+
+// run starts the goroutine in which the shared struct reads events from its
+// Watcher's Event channel and sends the events to the appropriate Tail.
+func (shared *InotifyTracker) run() {
+ watcher, err := fsnotify.NewWatcher()
+ if err != nil {
+ util.Fatal("failed to create Watcher")
+ }
+ shared.watcher = watcher
+
+ for {
+ select {
+ case winfo := <-shared.watch:
+ shared.error <- shared.addWatch(winfo)
+
+ case winfo := <-shared.remove:
+ shared.removeWatch(winfo)
+
+ case event, open := <-shared.watcher.Events:
+ if !open {
+ return
+ }
+ shared.sendEvent(event)
+
+ case err, open := <-shared.watcher.Errors:
+ if !open {
+ return
+ } else if err != nil {
+ sysErr, ok := err.(*os.SyscallError)
+ if !ok || sysErr.Err != syscall.EINTR {
+ logger.Printf("Error in Watcher Error channel: %s", err)
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/hpcloud/tail/watch/polling.go b/vendor/github.com/hpcloud/tail/watch/polling.go
new file mode 100644
index 000000000..49491f21d
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/watch/polling.go
@@ -0,0 +1,118 @@
+// Copyright (c) 2015 HPE Software Inc. All rights reserved.
+// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
+
+package watch
+
+import (
+ "os"
+ "runtime"
+ "time"
+
+ "github.com/hpcloud/tail/util"
+ "gopkg.in/tomb.v1"
+)
+
+// PollingFileWatcher polls the file for changes.
+type PollingFileWatcher struct {
+ Filename string
+ Size int64
+}
+
+func NewPollingFileWatcher(filename string) *PollingFileWatcher {
+ fw := &PollingFileWatcher{filename, 0}
+ return fw
+}
+
+var POLL_DURATION time.Duration
+
+func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
+ for {
+ if _, err := os.Stat(fw.Filename); err == nil {
+ return nil
+ } else if !os.IsNotExist(err) {
+ return err
+ }
+ select {
+ case <-time.After(POLL_DURATION):
+ continue
+ case <-t.Dying():
+ return tomb.ErrDying
+ }
+ }
+ panic("unreachable")
+}
+
+func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
+ origFi, err := os.Stat(fw.Filename)
+ if err != nil {
+ return nil, err
+ }
+
+ changes := NewFileChanges()
+ var prevModTime time.Time
+
+ // XXX: use tomb.Tomb to cleanly manage these goroutines. replace
+ // the fatal (below) with tomb's Kill.
+
+ fw.Size = pos
+
+ go func() {
+ prevSize := fw.Size
+ for {
+ select {
+ case <-t.Dying():
+ return
+ default:
+ }
+
+ time.Sleep(POLL_DURATION)
+ fi, err := os.Stat(fw.Filename)
+ if err != nil {
+ // Windows cannot delete a file if a handle is still open (tail keeps one open)
+ // so it gives access denied to anything trying to read it until all handles are released.
+ if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) {
+ // File does not exist (has been deleted).
+ changes.NotifyDeleted()
+ return
+ }
+
+ // XXX: report this error back to the user
+ util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
+ }
+
+ // File got moved/renamed?
+ if !os.SameFile(origFi, fi) {
+ changes.NotifyDeleted()
+ return
+ }
+
+ // File got truncated?
+ fw.Size = fi.Size()
+ if prevSize > 0 && prevSize > fw.Size {
+ changes.NotifyTruncated()
+ prevSize = fw.Size
+ continue
+ }
+ // File got bigger?
+ if prevSize > 0 && prevSize < fw.Size {
+ changes.NotifyModified()
+ prevSize = fw.Size
+ continue
+ }
+ prevSize = fw.Size
+
+ // File was appended to (changed)?
+ modTime := fi.ModTime()
+ if modTime != prevModTime {
+ prevModTime = modTime
+ changes.NotifyModified()
+ }
+ }
+ }()
+
+ return changes, nil
+}
+
+func init() {
+ POLL_DURATION = 250 * time.Millisecond
+}
diff --git a/vendor/github.com/hpcloud/tail/watch/watch.go b/vendor/github.com/hpcloud/tail/watch/watch.go
new file mode 100644
index 000000000..2e1783ef0
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/watch/watch.go
@@ -0,0 +1,20 @@
+// Copyright (c) 2015 HPE Software Inc. All rights reserved.
+// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
+
+package watch
+
+import "gopkg.in/tomb.v1"
+
+// FileWatcher monitors file-level events.
+type FileWatcher interface {
+ // BlockUntilExists blocks until the file comes into existence.
+ BlockUntilExists(*tomb.Tomb) error
+
+ // ChangeEvents reports on changes to a file, be it modification,
+ // deletion, renames or truncations. Returned FileChanges group of
+ // channels will be closed, thus become unusable, after a deletion
+ // or truncation event.
+ // In order to properly report truncations, ChangeEvents requires
+ // the caller to pass their current offset in the file.
+ ChangeEvents(*tomb.Tomb, int64) (*FileChanges, error)
+}
diff --git a/vendor/github.com/hpcloud/tail/winfile/winfile.go b/vendor/github.com/hpcloud/tail/winfile/winfile.go
new file mode 100644
index 000000000..aa7e7bc5d
--- /dev/null
+++ b/vendor/github.com/hpcloud/tail/winfile/winfile.go
@@ -0,0 +1,92 @@
+// +build windows
+
+package winfile
+
+import (
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+// issue also described here
+//https://codereview.appspot.com/8203043/
+
+// https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L218
+func Open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
+ if len(path) == 0 {
+ return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
+ }
+ pathp, err := syscall.UTF16PtrFromString(path)
+ if err != nil {
+ return syscall.InvalidHandle, err
+ }
+ var access uint32
+ switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
+ case syscall.O_RDONLY:
+ access = syscall.GENERIC_READ
+ case syscall.O_WRONLY:
+ access = syscall.GENERIC_WRITE
+ case syscall.O_RDWR:
+ access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
+ }
+ if mode&syscall.O_CREAT != 0 {
+ access |= syscall.GENERIC_WRITE
+ }
+ if mode&syscall.O_APPEND != 0 {
+ access &^= syscall.GENERIC_WRITE
+ access |= syscall.FILE_APPEND_DATA
+ }
+ sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
+ var sa *syscall.SecurityAttributes
+ if mode&syscall.O_CLOEXEC == 0 {
+ sa = makeInheritSa()
+ }
+ var createmode uint32
+ switch {
+ case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
+ createmode = syscall.CREATE_NEW
+ case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
+ createmode = syscall.CREATE_ALWAYS
+ case mode&syscall.O_CREAT == syscall.O_CREAT:
+ createmode = syscall.OPEN_ALWAYS
+ case mode&syscall.O_TRUNC == syscall.O_TRUNC:
+ createmode = syscall.TRUNCATE_EXISTING
+ default:
+ createmode = syscall.OPEN_EXISTING
+ }
+ h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL, 0)
+ return h, e
+}
+
+// https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L211
+func makeInheritSa() *syscall.SecurityAttributes {
+ var sa syscall.SecurityAttributes
+ sa.Length = uint32(unsafe.Sizeof(sa))
+ sa.InheritHandle = 1
+ return &sa
+}
+
+// https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_windows.go#L133
+func OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) {
+ r, e := Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
+ if e != nil {
+ return nil, e
+ }
+ return os.NewFile(uintptr(r), name), nil
+}
+
+// https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_posix.go#L61
+func syscallMode(i os.FileMode) (o uint32) {
+ o |= uint32(i.Perm())
+ if i&os.ModeSetuid != 0 {
+ o |= syscall.S_ISUID
+ }
+ if i&os.ModeSetgid != 0 {
+ o |= syscall.S_ISGID
+ }
+ if i&os.ModeSticky != 0 {
+ o |= syscall.S_ISVTX
+ }
+ // No mapping for Go's ModeTemporary (plan9 only).
+ return
+}