Major improvements to UnifiedPlayer: 1. GetFrameImage() now works when paused for responsive UI updates 2. Play() method properly starts FFmpeg process 3. Frame display loop runs continuously for smooth video display 4. Disabled audio temporarily to fix video playback fundamentals 5. Simplified FFmpeg command to focus on video stream only Player now: - Generates video frames correctly - Shows video when paused - Has responsive progress tracking - Starts playback properly Next steps: Re-enable audio playback once video is stable
706 lines
17 KiB
Go
706 lines
17 KiB
Go
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||
|
||
package fsnotify
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/fsnotify/fsnotify/internal"
|
||
"golang.org/x/sys/unix"
|
||
)
|
||
|
||
type kqueue struct {
|
||
*shared
|
||
Events chan Event
|
||
Errors chan error
|
||
|
||
kq int // File descriptor (as returned by the kqueue() syscall).
|
||
closepipe [2]int // Pipe used for closing kq.
|
||
watches *watches
|
||
}
|
||
|
||
type (
|
||
watches struct {
|
||
mu sync.RWMutex
|
||
wd map[int]watch // wd → watch
|
||
path map[string]int // pathname → wd
|
||
byDir map[string]map[int]struct{} // dirname(path) → wd
|
||
seen map[string]struct{} // Keep track of if we know this file exists.
|
||
byUser map[string]struct{} // Watches added with Watcher.Add()
|
||
}
|
||
watch struct {
|
||
wd int
|
||
name string
|
||
linkName string // In case of links; name is the target, and this is the link.
|
||
isDir bool
|
||
dirFlags uint32
|
||
}
|
||
)
|
||
|
||
func newWatches() *watches {
|
||
return &watches{
|
||
wd: make(map[int]watch),
|
||
path: make(map[string]int),
|
||
byDir: make(map[string]map[int]struct{}),
|
||
seen: make(map[string]struct{}),
|
||
byUser: make(map[string]struct{}),
|
||
}
|
||
}
|
||
|
||
func (w *watches) listPaths(userOnly bool) []string {
|
||
w.mu.RLock()
|
||
defer w.mu.RUnlock()
|
||
|
||
if userOnly {
|
||
l := make([]string, 0, len(w.byUser))
|
||
for p := range w.byUser {
|
||
l = append(l, p)
|
||
}
|
||
return l
|
||
}
|
||
|
||
l := make([]string, 0, len(w.path))
|
||
for p := range w.path {
|
||
l = append(l, p)
|
||
}
|
||
return l
|
||
}
|
||
|
||
func (w *watches) watchesInDir(path string) []string {
|
||
w.mu.RLock()
|
||
defer w.mu.RUnlock()
|
||
|
||
l := make([]string, 0, 4)
|
||
for fd := range w.byDir[path] {
|
||
info := w.wd[fd]
|
||
if _, ok := w.byUser[info.name]; !ok {
|
||
l = append(l, info.name)
|
||
}
|
||
}
|
||
return l
|
||
}
|
||
|
||
// Mark path as added by the user.
|
||
func (w *watches) addUserWatch(path string) {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
w.byUser[path] = struct{}{}
|
||
}
|
||
|
||
func (w *watches) addLink(path string, fd int) {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
w.path[path] = fd
|
||
w.seen[path] = struct{}{}
|
||
}
|
||
|
||
func (w *watches) add(path, linkPath string, fd int, isDir bool) {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
w.path[path] = fd
|
||
w.wd[fd] = watch{wd: fd, name: path, linkName: linkPath, isDir: isDir}
|
||
|
||
parent := filepath.Dir(path)
|
||
byDir, ok := w.byDir[parent]
|
||
if !ok {
|
||
byDir = make(map[int]struct{}, 1)
|
||
w.byDir[parent] = byDir
|
||
}
|
||
byDir[fd] = struct{}{}
|
||
}
|
||
|
||
func (w *watches) byWd(fd int) (watch, bool) {
|
||
w.mu.RLock()
|
||
defer w.mu.RUnlock()
|
||
info, ok := w.wd[fd]
|
||
return info, ok
|
||
}
|
||
|
||
func (w *watches) byPath(path string) (watch, bool) {
|
||
w.mu.RLock()
|
||
defer w.mu.RUnlock()
|
||
info, ok := w.wd[w.path[path]]
|
||
return info, ok
|
||
}
|
||
|
||
func (w *watches) updateDirFlags(path string, flags uint32) bool {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
fd, ok := w.path[path]
|
||
if !ok { // Already deleted: don't re-set it here.
|
||
return false
|
||
}
|
||
info := w.wd[fd]
|
||
info.dirFlags = flags
|
||
w.wd[fd] = info
|
||
return true
|
||
}
|
||
|
||
func (w *watches) remove(fd int, path string) bool {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
isDir := w.wd[fd].isDir
|
||
delete(w.path, path)
|
||
delete(w.byUser, path)
|
||
|
||
parent := filepath.Dir(path)
|
||
delete(w.byDir[parent], fd)
|
||
|
||
if len(w.byDir[parent]) == 0 {
|
||
delete(w.byDir, parent)
|
||
}
|
||
|
||
delete(w.wd, fd)
|
||
delete(w.seen, path)
|
||
return isDir
|
||
}
|
||
|
||
func (w *watches) markSeen(path string, exists bool) {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
if exists {
|
||
w.seen[path] = struct{}{}
|
||
} else {
|
||
delete(w.seen, path)
|
||
}
|
||
}
|
||
|
||
func (w *watches) seenBefore(path string) bool {
|
||
w.mu.RLock()
|
||
defer w.mu.RUnlock()
|
||
_, ok := w.seen[path]
|
||
return ok
|
||
}
|
||
|
||
var defaultBufferSize = 0
|
||
|
||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||
kq, closepipe, err := newKqueue()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
w := &kqueue{
|
||
shared: newShared(ev, errs),
|
||
Events: ev,
|
||
Errors: errs,
|
||
kq: kq,
|
||
closepipe: closepipe,
|
||
watches: newWatches(),
|
||
}
|
||
|
||
go w.readEvents()
|
||
return w, nil
|
||
}
|
||
|
||
// newKqueue creates a new kernel event queue and returns a descriptor.
|
||
//
|
||
// This registers a new event on closepipe, which will trigger an event when
|
||
// it's closed. This way we can use kevent() without timeout/polling; without
|
||
// the closepipe, it would block forever and we wouldn't be able to stop it at
|
||
// all.
|
||
func newKqueue() (kq int, closepipe [2]int, err error) {
|
||
kq, err = unix.Kqueue()
|
||
if err != nil {
|
||
return kq, closepipe, err
|
||
}
|
||
|
||
// Register the close pipe.
|
||
err = unix.Pipe(closepipe[:])
|
||
if err != nil {
|
||
unix.Close(kq)
|
||
return kq, closepipe, err
|
||
}
|
||
unix.CloseOnExec(closepipe[0])
|
||
unix.CloseOnExec(closepipe[1])
|
||
|
||
// Register changes to listen on the closepipe.
|
||
changes := make([]unix.Kevent_t, 1)
|
||
// SetKevent converts int to the platform-specific types.
|
||
unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ,
|
||
unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT)
|
||
|
||
ok, err := unix.Kevent(kq, changes, nil, nil)
|
||
if ok == -1 {
|
||
unix.Close(kq)
|
||
unix.Close(closepipe[0])
|
||
unix.Close(closepipe[1])
|
||
return kq, closepipe, err
|
||
}
|
||
return kq, closepipe, nil
|
||
}
|
||
|
||
func (w *kqueue) Close() error {
|
||
if w.shared.close() {
|
||
return nil
|
||
}
|
||
|
||
pathsToRemove := w.watches.listPaths(false)
|
||
for _, name := range pathsToRemove {
|
||
w.Remove(name)
|
||
}
|
||
|
||
unix.Close(w.closepipe[1]) // Send "quit" message to readEvents
|
||
return nil
|
||
}
|
||
|
||
func (w *kqueue) Add(name string) error { return w.AddWith(name) }
|
||
|
||
func (w *kqueue) AddWith(name string, opts ...addOpt) error {
|
||
if debug {
|
||
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||
time.Now().Format("15:04:05.000000000"), name)
|
||
}
|
||
|
||
with := getOptions(opts...)
|
||
if !w.xSupports(with.op) {
|
||
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||
}
|
||
|
||
_, err := w.addWatch(name, noteAllEvents, false)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
w.watches.addUserWatch(name)
|
||
return nil
|
||
}
|
||
|
||
func (w *kqueue) Remove(name string) error {
|
||
if debug {
|
||
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||
time.Now().Format("15:04:05.000000000"), name)
|
||
}
|
||
return w.remove(name, true)
|
||
}
|
||
|
||
func (w *kqueue) remove(name string, unwatchFiles bool) error {
|
||
if w.isClosed() {
|
||
return nil
|
||
}
|
||
|
||
name = filepath.Clean(name)
|
||
info, ok := w.watches.byPath(name)
|
||
if !ok {
|
||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||
}
|
||
|
||
err := w.register([]int{info.wd}, unix.EV_DELETE, 0)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
unix.Close(info.wd)
|
||
|
||
isDir := w.watches.remove(info.wd, name)
|
||
|
||
// Find all watched paths that are in this directory that are not external.
|
||
if unwatchFiles && isDir {
|
||
pathsToRemove := w.watches.watchesInDir(name)
|
||
for _, name := range pathsToRemove {
|
||
// Since these are internal, not much sense in propagating error to
|
||
// the user, as that will just confuse them with an error about a
|
||
// path they did not explicitly watch themselves.
|
||
w.Remove(name)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (w *kqueue) WatchList() []string {
|
||
if w.isClosed() {
|
||
return nil
|
||
}
|
||
return w.watches.listPaths(true)
|
||
}
|
||
|
||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||
|
||
// addWatch adds name to the watched file set; the flags are interpreted as
|
||
// described in kevent(2).
|
||
//
|
||
// Returns the real path to the file which was added, with symlinks resolved.
|
||
func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, error) {
|
||
if w.isClosed() {
|
||
return "", ErrClosed
|
||
}
|
||
|
||
name = filepath.Clean(name)
|
||
|
||
info, alreadyWatching := w.watches.byPath(name)
|
||
if !alreadyWatching {
|
||
fi, err := os.Lstat(name)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// Don't watch sockets or named pipes.
|
||
if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
|
||
return "", nil
|
||
}
|
||
|
||
// Follow symlinks, but only for paths added with Add(), and not paths
|
||
// we're adding from internalWatch from a listdir.
|
||
if !listDir && fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||
link, err := os.Readlink(name)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if !filepath.IsAbs(link) {
|
||
link = filepath.Join(filepath.Dir(name), link)
|
||
}
|
||
|
||
_, alreadyWatching = w.watches.byPath(link)
|
||
if alreadyWatching {
|
||
// Add to watches so we don't get spurious Create events later
|
||
// on when we diff the directories.
|
||
w.watches.addLink(name, 0)
|
||
return link, nil
|
||
}
|
||
|
||
info.linkName = name
|
||
name = link
|
||
fi, err = os.Lstat(name)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
}
|
||
|
||
// Retry on EINTR; open() can return EINTR in practice on macOS.
|
||
// See #354, and Go issues 11180 and 39237.
|
||
for {
|
||
info.wd, err = unix.Open(name, openMode, 0)
|
||
if err == nil {
|
||
break
|
||
}
|
||
if errors.Is(err, unix.EINTR) {
|
||
continue
|
||
}
|
||
return "", err
|
||
}
|
||
|
||
info.isDir = fi.IsDir()
|
||
}
|
||
|
||
err := w.register([]int{info.wd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
|
||
if err != nil {
|
||
unix.Close(info.wd)
|
||
return "", err
|
||
}
|
||
|
||
if !alreadyWatching {
|
||
w.watches.add(name, info.linkName, info.wd, info.isDir)
|
||
}
|
||
|
||
// Watch the directory if it has not been watched before, or if it was
|
||
// watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||
if info.isDir {
|
||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||
(!alreadyWatching || (info.dirFlags&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||
if !w.watches.updateDirFlags(name, flags) {
|
||
return "", nil
|
||
}
|
||
|
||
if watchDir {
|
||
d := name
|
||
if info.linkName != "" {
|
||
d = info.linkName
|
||
}
|
||
if err := w.watchDirectoryFiles(d); err != nil {
|
||
return "", err
|
||
}
|
||
}
|
||
}
|
||
return name, nil
|
||
}
|
||
|
||
// readEvents reads from kqueue and converts the received kevents into
|
||
// Event values that it sends down the Events channel.
|
||
func (w *kqueue) readEvents() {
|
||
defer func() {
|
||
close(w.Events)
|
||
close(w.Errors)
|
||
_ = unix.Close(w.kq)
|
||
unix.Close(w.closepipe[0])
|
||
}()
|
||
|
||
eventBuffer := make([]unix.Kevent_t, 10)
|
||
for {
|
||
kevents, err := w.read(eventBuffer)
|
||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||
if err != nil && err != unix.EINTR {
|
||
if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
|
||
return
|
||
}
|
||
}
|
||
|
||
for _, kevent := range kevents {
|
||
var (
|
||
wd = int(kevent.Ident)
|
||
mask = uint32(kevent.Fflags)
|
||
)
|
||
|
||
// Shut down the loop when the pipe is closed, but only after all
|
||
// other events have been processed.
|
||
if wd == w.closepipe[0] {
|
||
return
|
||
}
|
||
|
||
path, ok := w.watches.byWd(wd)
|
||
if debug {
|
||
internal.Debug(path.name, &kevent)
|
||
}
|
||
|
||
// On macOS it seems that sometimes an event with Ident=0 is
|
||
// delivered, and no other flags/information beyond that, even
|
||
// though we never saw such a file descriptor. For example in
|
||
// TestWatchSymlink/277 (usually at the end, but sometimes sooner):
|
||
//
|
||
// fmt.Printf("READ: %2d %#v\n", kevent.Ident, kevent)
|
||
// unix.Kevent_t{Ident:0x2a, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)}
|
||
// unix.Kevent_t{Ident:0x0, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)}
|
||
//
|
||
// The first is a normal event, the second with Ident 0. No error
|
||
// flag, no data, no ... nothing.
|
||
//
|
||
// I read a bit through bsd/kern_event.c from the xnu source, but I
|
||
// don't really see an obvious location where this is triggered –
|
||
// this doesn't seem intentional, but idk...
|
||
//
|
||
// Technically fd 0 is a valid descriptor, so only skip it if
|
||
// there's no path, and if we're on macOS.
|
||
if !ok && kevent.Ident == 0 && runtime.GOOS == "darwin" {
|
||
continue
|
||
}
|
||
|
||
event := w.newEvent(path.name, path.linkName, mask)
|
||
|
||
if event.Has(Rename) || event.Has(Remove) {
|
||
w.remove(event.Name, false)
|
||
w.watches.markSeen(event.Name, false)
|
||
}
|
||
|
||
if path.isDir && event.Has(Write) && !event.Has(Remove) {
|
||
w.dirChange(event.Name)
|
||
} else if !w.sendEvent(event) {
|
||
return
|
||
}
|
||
|
||
if event.Has(Remove) {
|
||
// Look for a file that may have overwritten this; for example,
|
||
// mv f1 f2 will delete f2, then create f2.
|
||
if path.isDir {
|
||
fileDir := filepath.Clean(event.Name)
|
||
_, found := w.watches.byPath(fileDir)
|
||
if found {
|
||
// TODO: this branch is never triggered in any test.
|
||
// Added in d6220df (2012).
|
||
// isDir check added in 8611c35 (2016): https://github.com/fsnotify/fsnotify/pull/111
|
||
//
|
||
// I don't really get how this can be triggered either.
|
||
// And it wasn't triggered in the patch that added it,
|
||
// either.
|
||
//
|
||
// Original also had a comment:
|
||
// make sure the directory exists before we watch for
|
||
// changes. When we do a recursive watch and perform
|
||
// rm -rf, the parent directory might have gone
|
||
// missing, ignore the missing directory and let the
|
||
// upcoming delete event remove the watch from the
|
||
// parent directory.
|
||
err := w.dirChange(fileDir)
|
||
if !w.sendError(err) {
|
||
return
|
||
}
|
||
}
|
||
} else {
|
||
path := filepath.Clean(event.Name)
|
||
if fi, err := os.Lstat(path); err == nil {
|
||
err := w.sendCreateIfNew(path, fi)
|
||
if !w.sendError(err) {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||
func (w *kqueue) newEvent(name, linkName string, mask uint32) Event {
|
||
e := Event{Name: name}
|
||
if linkName != "" {
|
||
// If the user watched "/path/link" then emit events as "/path/link"
|
||
// rather than "/path/target".
|
||
e.Name = linkName
|
||
}
|
||
|
||
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||
e.Op |= Remove
|
||
}
|
||
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||
e.Op |= Write
|
||
}
|
||
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||
e.Op |= Rename
|
||
}
|
||
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||
e.Op |= Chmod
|
||
}
|
||
// No point sending a write and delete event at the same time: if it's gone,
|
||
// then it's gone.
|
||
if e.Op.Has(Write) && e.Op.Has(Remove) {
|
||
e.Op &^= Write
|
||
}
|
||
return e
|
||
}
|
||
|
||
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||
func (w *kqueue) watchDirectoryFiles(dirPath string) error {
|
||
files, err := os.ReadDir(dirPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, f := range files {
|
||
path := filepath.Join(dirPath, f.Name())
|
||
|
||
fi, err := f.Info()
|
||
if err != nil {
|
||
return fmt.Errorf("%q: %w", path, err)
|
||
}
|
||
|
||
cleanPath, err := w.internalWatch(path, fi)
|
||
if err != nil {
|
||
// No permission to read the file; that's not a problem: just skip.
|
||
// But do add it to w.fileExists to prevent it from being picked up
|
||
// as a "new" file later (it still shows up in the directory
|
||
// listing).
|
||
switch {
|
||
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
|
||
cleanPath = filepath.Clean(path)
|
||
default:
|
||
return fmt.Errorf("%q: %w", path, err)
|
||
}
|
||
}
|
||
|
||
w.watches.markSeen(cleanPath, true)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Search the directory for new files and send an event for them.
|
||
//
|
||
// This functionality is to have the BSD watcher match the inotify, which sends
|
||
// a create event for files created in a watched directory.
|
||
func (w *kqueue) dirChange(dir string) error {
|
||
files, err := os.ReadDir(dir)
|
||
if err != nil {
|
||
// Directory no longer exists: we can ignore this safely. kqueue will
|
||
// still give us the correct events.
|
||
if errors.Is(err, os.ErrNotExist) {
|
||
return nil
|
||
}
|
||
return fmt.Errorf("fsnotify.dirChange %q: %w", dir, err)
|
||
}
|
||
|
||
for _, f := range files {
|
||
fi, err := f.Info()
|
||
if err != nil {
|
||
if errors.Is(err, os.ErrNotExist) {
|
||
return nil
|
||
}
|
||
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||
}
|
||
|
||
err = w.sendCreateIfNew(filepath.Join(dir, fi.Name()), fi)
|
||
if err != nil {
|
||
// Don't need to send an error if this file isn't readable.
|
||
if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) || errors.Is(err, os.ErrNotExist) {
|
||
return nil
|
||
}
|
||
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Send a create event if the file isn't already being tracked, and start
|
||
// watching this file.
|
||
func (w *kqueue) sendCreateIfNew(path string, fi os.FileInfo) error {
|
||
if !w.watches.seenBefore(path) {
|
||
if !w.sendEvent(Event{Name: path, Op: Create}) {
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// Like watchDirectoryFiles, but without doing another ReadDir.
|
||
path, err := w.internalWatch(path, fi)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
w.watches.markSeen(path, true)
|
||
return nil
|
||
}
|
||
|
||
func (w *kqueue) internalWatch(name string, fi os.FileInfo) (string, error) {
|
||
if fi.IsDir() {
|
||
// mimic Linux providing delete events for subdirectories, but preserve
|
||
// the flags used if currently watching subdirectory
|
||
info, _ := w.watches.byPath(name)
|
||
return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME, true)
|
||
}
|
||
|
||
// Watch file to mimic Linux inotify.
|
||
return w.addWatch(name, noteAllEvents, true)
|
||
}
|
||
|
||
// Register events with the queue.
|
||
func (w *kqueue) register(fds []int, flags int, fflags uint32) error {
|
||
changes := make([]unix.Kevent_t, len(fds))
|
||
for i, fd := range fds {
|
||
// SetKevent converts int to the platform-specific types.
|
||
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||
changes[i].Fflags = fflags
|
||
}
|
||
|
||
// Register the events.
|
||
success, err := unix.Kevent(w.kq, changes, nil, nil)
|
||
if success == -1 {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// read retrieves pending events, or waits until an event occurs.
|
||
func (w *kqueue) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
||
n, err := unix.Kevent(w.kq, nil, events, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return events[0:n], nil
|
||
}
|
||
|
||
func (w *kqueue) xSupports(op Op) bool {
|
||
//if runtime.GOOS == "freebsd" {
|
||
// return true // Supports everything.
|
||
//}
|
||
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||
return false
|
||
}
|
||
return true
|
||
}
|