-
Notifications
You must be signed in to change notification settings - Fork 1
/
aliases.go
203 lines (184 loc) · 6.1 KB
/
aliases.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Copyright © 2016 Pennock Tech, LLC.
// All rights reserved, except as granted under license.
// Licensed per file LICENSE.txt
package main
import (
"bufio"
"io"
"os"
"path/filepath"
"strings"
"sync"
"github.com/sirupsen/logrus"
"gopkg.in/fsnotify.v1"
)
// opts.aliasfile defaults to /etc/finger.conf
// The format is man-page specified on BSD; "#" for comments, nothing about blank lines;
// Unix tradition, expect a final newline
// all real lines are:
// alias:(user|alias)
// but aliases are only permitted to be _forward_ references (which avoids loops, but is
// inverted from the usual sense). Since we're implementing this file for compatibility,
// stick to that constraint.
// Also aliases can be fully-qualified filenames (start with a `/`) to point elsewhere.
var aliases struct {
sync.RWMutex
to map[string]string
}
func init() {
aliases.to = make(map[string]string)
}
func currentAliases() map[string]string {
aliases.RLock()
defer aliases.RUnlock()
return aliases.to
}
func loadMappingData(log logrus.FieldLogger) {
log = log.WithField("file", opts.aliasfile)
var err error
fh, err := os.Open(opts.aliasfile)
if err != nil {
log.WithError(err).Info("unable to load aliases")
return
}
defer fh.Close()
concrete := make(map[string]string)
unresolved := make([][2]string, 0, 100)
// No size limit on the alias file, we "trust" it
r := bufio.NewReader(fh)
err = nil
var line string
for lineNum := 0; err != io.EOF; {
line, err = r.ReadString('\n')
if err != nil && err != io.EOF {
log.WithError(err).Warn("problem reading config, aborting")
return
}
lineNum++
line = strings.TrimSpace(line)
if len(line) == 0 || line[0] == '#' {
continue
}
fields := strings.SplitN(line, ":", 2)
if len(fields) != 2 || len(fields[0]) == 0 || len(fields[1]) == 0 || strings.ContainsRune(fields[0], '/') {
log.WithField("line", lineNum).Warn("malformed line, skipping")
continue
}
unresolved = append(unresolved, [2]string{fields[0], fields[1]})
}
for i := len(unresolved) - 1; i >= 0; i-- {
from := strings.ToLower(unresolved[i][0])
to := unresolved[i][1]
if to[0] != '/' {
to = strings.ToLower(to)
}
if _, ok := concrete[from]; ok {
log.Warn("alias %q defined more than once, last one wins", from)
continue
}
if chain, ok := concrete[to]; ok {
concrete[from] = chain
} else {
concrete[from] = to
}
}
aliases.Lock()
aliases.to = concrete
aliases.Unlock()
log.WithField("alias-count", len(concrete)).Info("parsed aliases")
}
// as long as the _directory_ exists, we'll detect a late file creation and handle it fine.
func scheduleAutoMappingDataReload(log logrus.FieldLogger) {
log = log.WithField("subsystem", "fs-watcher")
// originally mostly ripped straight from fsnotify.v1's NewWatcher example in the docs
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.WithError(err).Error("unable to start FS watcher, will not detect changes")
// We continue on without aborting
return
}
logrus.RegisterExitHandler(func() { _ = watcher.Close() })
basename := filepath.Base(opts.aliasfile)
dirname := filepath.Dir(opts.aliasfile)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
log.Warn("terminating config-watcher event dispatcher")
return
}
l := log.WithField("event", event)
switch filepath.Base(event.Name) {
case basename:
if event.Op&fsnotify.Write == fsnotify.Write {
l.Info("modification detected")
// The actual work! (also in some other edge-cases just below)
loadMappingData(log)
} else if event.Op&fsnotify.Create == fsnotify.Create {
// better late than never
l.Info("creation detected (adding watch)")
loadMappingData(log)
watcher.Add(opts.aliasfile)
} else if event.Op&fsnotify.Chmod == fsnotify.Chmod {
// assume file created with 0 permissions then chmod'd more open, so our initial read might
// have failed. Should be harmless to re-read the file. If it was chmod'd unreadable, we'll
// error out cleanly.
l.Info("chmod detected")
loadMappingData(log)
} else if event.Op&fsnotify.Remove == fsnotify.Remove || event.Op&fsnotify.Rename == fsnotify.Rename {
// I have seen this fire when the watched config file
// is an entry in a K8S configmap and the entry was
// modified, not deleted.
_, err := os.Stat(event.Name)
if err != nil && os.IsNotExist(err) {
l.Info("file gone, confirmed, WATCH GONE")
} else {
l.Info("file gone, false positive, file still exists, re-watching")
// The kernel will have removed the watch and
// fsnotify will have removed its copy, to match.
// We can't just ignore this, we have to add the
// watch back.
watcher.Add(opts.aliasfile)
}
// TODO: should we also remove all aliases?
// We currently continue running with the last aliases seen, which lets us
// steady-state on the assumption that the file is being replaced. Perhaps
// we should start a timer and after 5 seconds, nuke the loaded aliases?
}
// no other scenarios known
case dirname:
// usually ...
// nothing to do; file creation will create an event named for the file, which we detect above for the file
// which we care about; chmod ... we care less about.
if event.Op&fsnotify.Remove == fsnotify.Remove || event.Op&fsnotify.Rename == fsnotify.Rename {
l.Warn("directory of config file gone, nuking watcher, no notifications anymore")
// duplicate close on shutdown is safe
_ = watcher.Close()
return
}
}
case err, ok := <-watcher.Errors:
if !ok {
log.Warn("terminating config-watcher event dispatcher")
return
}
log.WithError(err).Warn("error")
}
}
}()
count := 0
for _, p := range []string{opts.aliasfile, dirname} {
err = watcher.Add(p)
if err != nil {
log.WithError(err).WithField("file", p).Info("unable to start watching")
// do not error out
} else {
count++
}
}
if count == 0 {
log.Warn("unable to set up any watches, terminating FS watcher, auto-reloading gone")
_ = watcher.Close()
}
}