From b190157b8c568922f7f9b4039b67a34862fa9f54 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 12 Sep 2015 09:14:40 -0600 Subject: Add an inotify watcher utility using channels; use it. The interface of inotify/inutil.Watcher more resembles golang.org/x/exp/inotify; with it using channels instead of repeated calls to Read(). In my use-case, this is useful because it allows implementing a "read" (select, really) that doesn't block Close(); which is required to handle the TERM signal correctly. --- src/inotify/inutil/inotify_util.go | 78 ++++++++++++++++++++++++ src/nshd/hackers_git/hackers.go | 9 +-- src/nshd/hackers_git/hackers_watch.go | 110 ++++++++++++++++++++-------------- 3 files changed, 146 insertions(+), 51 deletions(-) create mode 100644 src/inotify/inutil/inotify_util.go (limited to 'src') diff --git a/src/inotify/inutil/inotify_util.go b/src/inotify/inutil/inotify_util.go new file mode 100644 index 0000000..46146f5 --- /dev/null +++ b/src/inotify/inutil/inotify_util.go @@ -0,0 +1,78 @@ +package inutil + +import ( + "inotify" + "os" + "syscall" +) + +const ( + // Flags for the parameter of InotifyInit1(). + // These, oddly, appear to be 24-bit numbers. + IN_CLOEXEC = inotify.IN_CLOEXEC +) + +type Watcher struct { + Events <-chan inotify.Event + events chan<- inotify.Event + Errors <-chan error + errors chan<- error + in *inotify.Inotify +} + +func WatcherInit() (*Watcher, error) { + in, err := inotify.InotifyInit() + return newWatcher(in, err) +} + +func WatcherInit1(flags int) (*Watcher, error) { + in, err := inotify.InotifyInit1(flags&^inotify.IN_NONBLOCK) + return newWatcher(in, err) +} + +func newWatcher(in *inotify.Inotify, err error) (*Watcher, error) { + events := make(chan inotify.Event, 1) + errors := make(chan error, 1) + o := &Watcher{ + Events: events, + events: events, + Errors: errors, + errors: errors, + in: in, + } + go o.worker() + return o, err +} + +func (o *Watcher) AddWatch(path string, mask inotify.Mask) (inotify.Wd, error) { + return o.in.AddWatch(path, mask); +} + +func (o *Watcher) RmWatch(wd inotify.Wd) error { + return o.in.RmWatch(wd); +} + +func (o *Watcher) Close() { + func() { + defer recover() + close(o.events) + close(o.errors) + }() + go o.in.Close() +} + +func (o *Watcher) worker() { + defer recover() + for { + ev, err := o.in.Read(); + if ev.Wd >= 0 { + o.events <- ev + } + if err != nil { + if err.(*os.SyscallError).Err == syscall.EBADF { + o.Close() + } + o.errors <- err + } + } +} diff --git a/src/nshd/hackers_git/hackers.go b/src/nshd/hackers_git/hackers.go index 446351d..f47868f 100644 --- a/src/nshd/hackers_git/hackers.go +++ b/src/nshd/hackers_git/hackers.go @@ -2,11 +2,11 @@ package hackers_git import ( "inotify" + "inotify/inutil" "nslcd_proto" "nslcd_proto/util" "nslcd_systemd" "sd_daemon/logger" - "sd_daemon/lsb" "sync" ) @@ -24,11 +24,12 @@ type Hackers struct { util.NullBackend Cfg Config lock sync.RWMutex + workers sync.WaitGroup users map[int32]user groups map[string]map[string]bool - in_fd *inotify.Inotify + in_fd *inutil.Watcher in_wd_home inotify.Wd in_wd_yaml inotify.Wd in_uid2wd map[int32]inotify.Wd @@ -44,10 +45,6 @@ func (o *Hackers) Init() error { logger.Err("Could not initialize hackers.git: %v", err) return err } - go func() { - defer lsb.Recover() - o.worker() - }() return nil } diff --git a/src/nshd/hackers_git/hackers_watch.go b/src/nshd/hackers_git/hackers_watch.go index c10ec78..666237b 100644 --- a/src/nshd/hackers_git/hackers_watch.go +++ b/src/nshd/hackers_git/hackers_watch.go @@ -2,6 +2,7 @@ package hackers_git import ( "inotify" + "inotify/inutil" "os" "path/filepath" "sd_daemon/logger" @@ -49,6 +50,7 @@ func (o *Hackers) unwatchHomedir(wd inotify.Wd) { func (o *Hackers) close() { if o.in_fd != nil { o.in_fd.Close() + defer o.workers.Wait() } o.in_wd_home = -1 o.in_wd_yaml = -1 @@ -60,7 +62,7 @@ func (o *Hackers) close() { func (o *Hackers) reload() (err error) { o.close() - o.in_fd, err = inotify.InotifyInit() ; if err != nil { return } + o.in_fd, err = inutil.WatcherInit() ; if err != nil { return } o.in_wd_home, err = o.in_fd.AddWatch("/home" , in_DIR|in_CHILD_ADD); if err != nil { return } o.in_wd_yaml, err = o.in_fd.AddWatch(o.Cfg.Yamldir, in_DIR|in_CHILD_ANY); if err != nil { return } @@ -73,6 +75,12 @@ func (o *Hackers) reload() (err error) { o.load_yaml_file(filename, false) } + go func() { + defer lsb.Recover() + defer o.workers.Done() + o.worker() + }() + err = nil return } @@ -160,55 +168,67 @@ func (o *Hackers) worker() { if err != nil { worker_error("failed to load %q: %v", o.Cfg.Yamldir, err) } - for event, err := o.in_fd.Read(); err == nil; event, err = o.in_fd.Read() { - switch event.Wd { - case o.in_wd_yaml: - // handle updates to yaml files - if event.Mask&in_DIR_INVALID != 0 { - err := o.Reload() - if err != nil { - worker_error("failed to reload hackers.git yaml directory: %v", err) - - } - err = os.Chdir(o.Cfg.Yamldir) - if err != nil { - worker_error("failed to load %q: %v", o.Cfg.Yamldir, err) +Loop: + for { + select { + case event, ok := <-o.in_fd.Events: + if !ok { + break Loop + } + switch event.Wd { + case o.in_wd_yaml: + // handle updates to yaml files + if event.Mask&in_DIR_INVALID != 0 { + err := o.Reload() + if err != nil { + worker_error("failed to reload hackers.git yaml directory: %v", err) + + } + err = os.Chdir(o.Cfg.Yamldir) + if err != nil { + worker_error("failed to load %q: %v", o.Cfg.Yamldir, err) + } + } else if event.Mask&in_CHILD_ANY != 0 { + if event.Name == nil { + panic("recieved child event from inotify, but no child name") + } + o.load_yaml_file(o.Cfg.Yamldir + "/" + *event.Name, true) + } else { + panic("recieved non-subscribed inotify event from kernel") } - } else if event.Mask&in_CHILD_ANY != 0 { - if event.Name == nil { - panic("recieved child event from inotify, but no child name") + case o.in_wd_home: + if event.Mask&in_DIR_INVALID != 0 { + err := o.Reload() + if err != nil { + panic(err) + } + } else if event.Mask&inotify.IN_ISDIR != 0 { + // handle added home directory + o.worker_watch_homedirs() } - o.load_yaml_file(o.Cfg.Yamldir + "/" + *event.Name, true) - } else { - panic("recieved non-subscribed inotify event from kernel") - } - case o.in_wd_home: - if event.Mask&in_DIR_INVALID != 0 { - err := o.Reload() - if err != nil { - panic(err) + default: + // handle a change to someone's password + if event.Mask&in_DIR_INVALID != 0 { + o.unwatchHomedir(event.Wd) + o.worker_watch_homedirs() + } else if event.Name != nil { + if *event.Name == ".password" { + func() { + o.lock.Lock() + defer o.lock.Unlock() + o.load_user_password(o.in_wd2uid[event.Wd]) + }() + } + } else { + logger.Debug("hackers.git: event didn't match: %#v", event) } - } else if event.Mask&inotify.IN_ISDIR != 0 { - // handle added home directory - o.worker_watch_homedirs() } - default: - // handle a change to someone's password - if event.Mask&in_DIR_INVALID != 0 { - o.unwatchHomedir(event.Wd) - o.worker_watch_homedirs() - } else if event.Name != nil { - if *event.Name == ".password" { - func() { - o.lock.Lock() - defer o.lock.Unlock() - o.load_user_password(o.in_wd2uid[event.Wd]) - }() - } - } else { - logger.Debug("hackers.git: event didn't match: %#v", event) + case err, ok := <-o.in_fd.Errors: + if !ok { + break Loop } + logger.Warning("hackers.git: inotify error: %v", err) } } - logger.Info("Stopped hackers.git inotify watcher") + logger.Info("hackers.git: Stopped inotify watcher") } -- cgit v1.2.2