init
This commit is contained in:
commit
2a86751b60
|
@ -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
|
||||
}
|
||||
```
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module inotify
|
||||
|
||||
go 1.13
|
||||
|
||||
require golang.org/x/sys v0.0.0-20200519105757-fe76b779f299
|
|
@ -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=
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue