This commit is contained in:
lovezsh 2020-05-26 10:47:00 +08:00
commit 2a86751b60
7 changed files with 625 additions and 0 deletions

84
README.md Normal file
View File

@ -0,0 +1,84 @@
# 简介
Linux Inotify API的封装。
# 使用示例
```go
package main
import (
"fmt"
"inotify"
"log"
"os"
"path/filepath"
"time"
)
func main() {
watcher, err := inotify.NewWatcher()
if err != nil {
log.Println(err)
}
name := os.Args[1]
paths := make([]string, 0)
if !isExistDir(name) {
paths = append(paths, name)
} else {
var err error
paths, err = dirs(name)
if err != nil {
log.Fatal(err)
}
}
for _, v := range paths {
err = watcher.AddWatch(v)
if err != nil {
log.Fatalf("failed to add watch %s, error: %v\n", os.Args[1], err)
}
}
ticker := time.NewTicker(3 * time.Second)
for {
select {
case err := <-watcher.Errors:
log.Println(err)
os.Exit(1)
case event := <-watcher.Events:
if event.IsDir {
if event.Name == inotify.EventCreate {
err = watcher.AddWatch(event.Path)
if err != nil {
log.Fatalf("failed to add watch %s, error: %v\n", os.Args[1], err)
}
}
fmt.Println("Directory:", event.Path, "event:", event.Name)
} else {
fmt.Println("File:", event.Path, "event:", event.Name)
}
case <-ticker.C:
fmt.Println(watcher.Path())
}
}
}
func isExistDir(name string) bool {
f, err := os.Stat(name)
return err == nil && f.IsDir()
}
func dirs(name string) ([]string, error) {
matchs := make([]string, 0)
err := filepath.Walk(name, func(root string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
matchs = append(matchs, root)
}
return nil
})
return matchs, err
}
```

76
example/main.go Normal file
View File

@ -0,0 +1,76 @@
package main
import (
"fmt"
"inotify"
"log"
"os"
"path/filepath"
"time"
)
func main() {
watcher, err := inotify.NewWatcher()
if err != nil {
log.Println(err)
}
name := os.Args[1]
paths := make([]string, 0)
if !isExistDir(name) {
paths = append(paths, name)
} else {
var err error
paths, err = dirs(name)
if err != nil {
log.Fatal(err)
}
}
for _, v := range paths {
err = watcher.AddWatch(v)
if err != nil {
log.Fatalf("failed to add watch %s, error: %v\n", os.Args[1], err)
}
}
ticker := time.NewTicker(3 * time.Second)
for {
select {
case err := <-watcher.Errors:
log.Println(err)
os.Exit(1)
case event := <-watcher.Events:
if event.IsDir {
if event.Name == inotify.EventCreate {
err = watcher.AddWatch(event.Path)
if err != nil {
log.Fatalf("failed to add watch %s, error: %v\n", os.Args[1], err)
}
}
fmt.Println("Directory:", event.Path, "event:", event.Name)
} else {
fmt.Println("File:", event.Path, "event:", event.Name)
}
case <-ticker.C:
fmt.Println(watcher.Path())
}
}
}
func isExistDir(name string) bool {
f, err := os.Stat(name)
return err == nil && f.IsDir()
}
func dirs(name string) ([]string, error) {
matchs := make([]string, 0)
err := filepath.Walk(name, func(root string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
matchs = append(matchs, root)
}
return nil
})
return matchs, err
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module inotify
go 1.13
require golang.org/x/sys v0.0.0-20200519105757-fe76b779f299

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

300
inotify.go Normal file
View File

@ -0,0 +1,300 @@
package inotify
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
var ErrEventOverflow = fmt.Errorf("inotify event queue overflowed")
type Event struct {
Name string
Path string
IsDir bool
}
type Watcher struct {
Events chan Event
Errors chan error
fd int
poller *poller
pW map[int32]watch
wP map[string]int
wPLock *sync.Mutex
pWLock *sync.Mutex
done chan bool
isClosed bool
}
func (w *Watcher) Path() []string {
paths := make([]string, 0)
if w.isClosed {
return paths
}
w.wPLock.Lock()
defer w.wPLock.Unlock()
for path := range w.wP {
paths = append(paths, path)
}
return paths
}
func NewWatcher() (*Watcher, error) {
fd, err := unix.InotifyInit1(unix.IN_NONBLOCK)
if err != nil {
return nil, err
}
poller, err := newPoller(fd)
if err != nil {
return nil, err
}
w := &Watcher{
fd: fd,
poller: poller,
Events: make(chan Event),
Errors: make(chan error),
pW: make(map[int32]watch),
pWLock: new(sync.Mutex),
wP: make(map[string]int),
wPLock: new(sync.Mutex),
done: make(chan bool),
}
go w.eventLoop()
return w, nil
}
const bufsize = unix.SizeofInotifyEvent + syscall.NAME_MAX + 1
type eventBuf [bufsize]byte
func (w *Watcher) readEvent(buf eventBuf, n int) {
var offset uint32
for offset <= uint32(n-unix.SizeofInotifyEvent) {
event := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
if event.Mask&unix.IN_Q_OVERFLOW == unix.IN_Q_OVERFLOW {
w.Errors <- ErrEventOverflow
break
}
var name string
w.pWLock.Lock()
path := w.pW[event.Wd]
w.pWLock.Unlock()
if event.Len > 0 {
rawName := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:event.Len:event.Len]
name = strings.TrimRight(string(rawName[0:event.Len]), "\000")
}
var isDir bool
if isDeleteSelf(event.Mask) {
isDir = path.isDir
w.remove(event.Wd)
} else {
isDir = isDirectory(event.Mask)
}
if !isIgnore(event.Mask) {
w.Events <- Event{
Name: maskString(event.Mask),
Path: filepath.Join(path.name, name),
IsDir: isDir,
}
}
offset += event.Len + unix.SizeofInotifyEvent
}
}
func (w *Watcher) Close() {
if w.isClosed {
return
}
w.poller.close()
w.done <- true
close(w.Errors)
close(w.Events)
close(w.done)
}
func (w *Watcher) readEventLoop() {
var (
n int
err error
buf eventBuf
)
for {
select {
case <-w.done:
return
default:
}
n, err = unix.Read(w.fd, buf[:])
if err != nil {
if err == unix.EINTR {
break
}
if err == unix.EAGAIN {
break
}
w.Errors <- err
continue
}
if n < unix.SizeofInotifyEvent {
w.Errors <- fmt.Errorf("event is too short")
break
}
w.readEvent(buf, n)
}
}
func (w *Watcher) eventLoop() {
var (
ok bool
err error
)
for {
ok, err = w.poller.wait()
if err != nil {
w.Errors <- err
continue
}
if !ok {
continue
}
w.readEventLoop()
}
}
func (w *Watcher) remove(wd int32) {
w.pWLock.Lock()
defer w.pWLock.Unlock()
name, ok := w.pW[wd]
if !ok {
return
}
delete(w.pW, wd)
w.wPLock.Lock()
defer w.wPLock.Unlock()
delete(w.wP, name.name)
}
type watch struct {
name string
isDir bool
}
func (w *Watcher) AddWatch(name string) error {
name = filepath.Clean(name)
wd, err := unix.InotifyAddWatch(w.fd, name, unix.IN_ALL_EVENTS)
if err != nil {
return err
}
w.pWLock.Lock()
w.pW[int32(wd)] = watch{name: name, isDir: !isExistFile(name)}
w.pWLock.Unlock()
w.wPLock.Lock()
w.wP[name] = wd
w.wPLock.Unlock()
return nil
}
func (w *Watcher) RemoveWatch(name string) error {
name = filepath.Clean(name)
w.wPLock.Lock()
defer w.wPLock.Unlock()
wd, ok := w.wP[name]
if !ok {
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
}
delete(w.wP, name)
w.pWLock.Lock()
defer w.pWLock.Unlock()
delete(w.pW, int32(wd))
_, err := unix.InotifyRmWatch(w.fd, uint32(wd))
return err
}
func maskString(mask uint32) string {
for key, value := range eventName {
if key&mask != 0 {
return value
}
}
return EventUnknown
}
const (
EventAccess = "IN_ACCESS"
EventAttrib = "IN_ATTRIB"
EventCloseWrite = "IN_CLOSE_WRITE"
EventCloseNoWrite = "IN_CLOSE_NOWRITE"
EventCreate = "IN_CREATE"
EventDelete = "IN_DELETE"
EventDeleteSelf = "IN_DELETE_SELF"
EventModify = "IN_MODIFY"
EventMoveSelf = "IN_MOVE_SELF"
EventMovedFrom = "IN_MOVED_FROM"
EventMovedTo = "IN_MOVED_TO"
EventOpen = "IN_OPEN"
EventIgnored = "IN_IGNORED"
EventUnmount = "IN_UNMOUNT"
EventUnknown = "UNKNOWN"
)
var eventName = map[uint32]string{
unix.IN_ACCESS: EventAccess,
unix.IN_ATTRIB: EventAttrib,
unix.IN_CLOSE_WRITE: EventCloseWrite,
unix.IN_CLOSE_NOWRITE: EventCloseNoWrite,
unix.IN_CREATE: EventCreate,
unix.IN_DELETE: EventDelete,
unix.IN_DELETE_SELF: EventDeleteSelf,
unix.IN_MODIFY: EventModify,
unix.IN_MOVE_SELF: EventMoveSelf,
unix.IN_MOVED_FROM: EventMovedFrom,
unix.IN_MOVED_TO: EventMovedTo,
unix.IN_OPEN: EventOpen,
unix.IN_IGNORED: EventIgnored,
unix.IN_UNMOUNT: EventUnmount,
}
func isDirectory(mask uint32) bool {
return mask&unix.IN_ISDIR == unix.IN_ISDIR
}
func isIgnore(mask uint32) bool {
return mask&unix.IN_IGNORED == unix.IN_IGNORED
}
func isDeleteSelf(mask uint32) bool {
return mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF
}
func isExistFile(name string) bool {
f, err := os.Stat(name)
return err == nil && !f.IsDir()
}

73
poller.go Normal file
View File

@ -0,0 +1,73 @@
package inotify
import (
"errors"
"golang.org/x/sys/unix"
)
type poller struct {
fd int
epfd int
}
func newPoller(fd int) (*poller, error) {
var err error
p := &poller{fd: -1, epfd: -1}
p.fd = fd
p.epfd, err = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
if err != nil {
return nil, err
}
event := unix.EpollEvent{
Fd: int32(p.fd),
Events: unix.EPOLLIN | unix.EPOLLET | unix.EPOLLERR | unix.EPOLLHUP,
}
err = unix.EpollCtl(p.epfd, unix.EPOLL_CTL_ADD, p.fd, &event)
if err != nil {
return nil, err
}
return p, nil
}
func (p *poller) wait() (bool, error) {
for {
events := make([]unix.EpollEvent, 1)
n, err := unix.EpollWait(p.epfd, events, -1)
if err != nil {
if err == unix.EINTR {
continue
}
return false, err
}
if n == 0 {
continue
}
event := events[0]
if event.Events&unix.EPOLLHUP != 0 {
return false, errors.New("epoll hup")
}
if event.Events&unix.EPOLLERR != 0 {
return false, errors.New("epoll error")
}
if event.Events&unix.EPOLLIN != 0 {
return true, nil
}
return false, errors.New("unkown epoll event")
}
}
func (p *poller) close() {
if p.fd != -1 {
unix.Close(p.fd)
}
if p.epfd != -1 {
unix.Close(p.epfd)
}
}

85
poller_test.go Normal file
View File

@ -0,0 +1,85 @@
package inotify
import (
"testing"
"time"
"golang.org/x/sys/unix"
)
func TestPollerWithBadFd(t *testing.T) {
_, err := newPoller(-1)
if err != unix.EBADF {
t.Fatalf("Expected EBADF, got: %v", err)
}
}
func TestPollerWithData(t *testing.T) {
var tfd [2]int
err := unix.Pipe(tfd[:])
if err != nil {
t.Fatalf("failed to create pipe: %v", err)
}
defer func() {
unix.Close(tfd[0])
unix.Close(tfd[1])
}()
poller, err := newPoller(tfd[0])
if err != nil {
t.Fatalf("Failed to create poller: %v", err)
}
oks := make(chan bool)
go func() {
ok, err := poller.wait()
if err != nil {
t.Fatalf("poller failed: %v", err)
}
oks <- ok
}()
select {
case <-time.After(1000 * time.Millisecond):
case <-oks:
t.Fatalf("poller did not wait")
}
msg := "poller"
buf := []byte(msg)
_, err = unix.Write(tfd[1], buf)
if err != nil {
t.Fatalf("Failed to write to pipe: %v", err)
}
ok := <-oks
if !ok {
t.Fatalf("expected true")
}
buf2 := make([]byte, 2048)
n, err := unix.Read(tfd[0], buf2)
if string(buf2[:n]) != msg {
t.Fatalf("read data error")
}
}
func TestPollerWithClose(t *testing.T) {
var tfd [2]int
err := unix.Pipe(tfd[:])
if err != nil {
t.Fatalf("failed to create pipe: %v", err)
}
defer func() {
unix.Close(tfd[0])
}()
poller, err := newPoller(tfd[0])
if err != nil {
t.Fatalf("Failed to create poller: %v", err)
}
unix.Close(tfd[1])
ok, err := poller.wait()
if err == nil {
t.Fatalf("except poller")
}
if ok {
t.Fatalf("expected poller to return true")
}
}