Using epoll to monitor inotify events

Viewed 354

I want to use inotify to watch some file changes, and use epoll to monitor if any inotify event occurs. However, I was having some trouble receiving inotify events. Say, how do I know if I got every events that occured.

If inotify fd sets to NON_BLOCK mode, EAGAIN would get returned when read() indicating that there's nothing else left to read, which is nice. But If inotify fd sets to BLOCKING mode, read() would block infinitely.

For example, if you call read(2) by asking to read a certain amount of data and read(2) returns a lower number of bytes, you can be sure of having exhausted the read I/O space for the file descriptor.

Ref: epoll(7)

According to epoll(7), I can assure a thorough read if read() returns a lower number of bytes. But how should I deal with read() returning the same number of bytes ?

Here's the code I've tried.

func main() {
    fd, _ := unix.InotifyInit()
    epollfd, _ := unix.EpollCreate(1)
    unix.EpollCtl(epollfd, unix.EPOLL_CTL_ADD, fd, &unix.EpollEvent{
        Fd:     int32(fd),
        Events: unix.EPOLLIN,
    })
    unix.InotifyAddWatch(fd, os.Args[1], unix.IN_ALL_EVENTS)
    epollevents := make([]unix.EpollEvent, 8)
    for {
        // epoll wait
        var nepoll int
        if nepoll, err = unix.EpollWait(epollfd, epollevents, -1); err == nil {
            // nothing happens
        } else if errors.Is(err, unix.EINTR) {
            continue // ignore interrupt
        } else {
            log.Fatal(err.Error())
        }
        // process inotify events
        for i := 0; i < nepoll; i += 1 {
            var nr int
            // buff for receiving
            // eventBuf for processing
            // two buff to prevent a large amount of data
            eventBuff := make([]byte, (unix.SizeofInotifyEvent+unix.PathMax)*2)
            buff := make([]byte, unix.SizeofInotifyEvent+unix.PathMax)
            offset, nr, lastUnread := 0, 0, 0
            for {
                // read loop in case of a large amount of data
                if nr, err = unix.Read(int(epollevents[i].Fd), buff); err == nil {
                    // nothing happens
                } else if errors.Is(err, unix.EAGAIN) {
                    println("exhaust")
                    break
                } else {
                    log.Fatal(err.Error())
                }
                copy(eventBuff[lastUnread:lastUnread+nr], buff[:nr])
                lastUnread += nr
                for offset < lastUnread &&
                    (nr < len(buff) || lastUnread-offset >= unix.PathMax+unix.SizeofInotifyEvent) {
                    event := (*unix.InotifyEvent)(unsafe.Pointer(&eventBuff[offset]))
                    switch event.Mask {
                    case unix.IN_ACCESS:
                        // ...
                    case unix.IN_OPEN:
                        // ...
                    case unix.IN_CLOSE_WRITE:
                        // ...
                    case unix.IN_CLOSE_NOWRITE:
                        // ...
                    }
                    offset += unix.SizeofInotifyEvent + int(event.Len)
                }
                copy(eventBuff[offset:lastUnread], eventBuff[:lastUnread-offset])
                lastUnread -= offset
                offset = 0
            }
        }
        fmt.Println("Over") // NEVER got print out in BLOCKING mode
    }
}

0 Answers
Related