Skip to content

Commit

Permalink
unix: create wrappers for solaris/illumos Event Ports
Browse files Browse the repository at this point in the history
This work is in support of a cleanup of fsnotify/fsnotify#263

Change-Id: Ibd7500d20322765bfd50aa18333eb43ee7b659d7
  • Loading branch information
nshalman committed Jun 17, 2021
1 parent 961cca9 commit 0d4fc1a
Show file tree
Hide file tree
Showing 3 changed files with 442 additions and 1 deletion.
200 changes: 200 additions & 0 deletions unix/syscall_solaris.go
Expand Up @@ -13,7 +13,10 @@
package unix

import (
"fmt"
"os"
"runtime"
"sync"
"syscall"
"unsafe"
)
Expand Down Expand Up @@ -744,3 +747,200 @@ func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, e
func Munmap(b []byte) (err error) {
return mapper.Munmap(b)
}

// Event Ports

// EventPortUserCookie is an empty interface used for type safety when passing pointers
// into the port_associate(3c) wrappers AssociatePath and AssociateFd
type EventPortUserCookie interface{}

// EventPort provides a safe abstraction on top of Solaris/illumos Event Ports
type EventPort struct {
port int
fds map[uintptr]*EventPortUserCookie
paths map[string]*fileObj
fobjs map[*fileObj]*EventPortUserCookie
mu sync.Mutex
}

// PortEvent is an abstraction of the port_event C struct.
// compare Source against PORT_SOURCE_FILE or PORT_SOURCE_FD
// to see if Path or Fd was the event source. The other will be
// uninitialized.
type PortEvent struct {
Cookie *EventPortUserCookie
Events int32 //must match portEvent.Events
Fd uintptr
Path string
Source uint16 //must match portEvent.Source
fobj *fileObj
}

// NewEventPort creates a new EventPort including the
// underlying call to port_create(3c)
func NewEventPort() (*EventPort, error) {
port, err := port_create()
if err != nil {
return nil, err
}
e := new(EventPort)
e.port = port
e.fds = make(map[uintptr]*EventPortUserCookie)
e.paths = make(map[string]*fileObj)
e.fobjs = make(map[*fileObj]*EventPortUserCookie)
return e, nil
}

//sys port_create() (n int, err error)
//sys port_associate(port int, source int, object uintptr, events int, user *byte) (n int, err error)
//sys port_dissociate(port int, source int, object uintptr) (n int, err error)
//sys port_get(port int, pe *portEvent, timeout *Timespec) (n int, err error)

// Close dissociates from all remaining paths and file descriptors before closing
// the event port
func (e *EventPort) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
for fd, _ := range e.fds {
port_dissociate(e.port, PORT_SOURCE_FD, fd)
delete(e.fds, fd)
}
for path, _ := range e.paths {
fobj := e.paths[path]
port_dissociate(e.port, PORT_SOURCE_FILE, uintptr(unsafe.Pointer(fobj)))
delete(e.fobjs, fobj)
delete(e.paths, path)
}
return Close(e.port)
}

// PathIsWatched checks to see if path is associated with this EventPort
func (e *EventPort) PathIsWatched(path string) bool {
e.mu.Lock()
defer e.mu.Unlock()
_, found := e.paths[path]
return found
}

// FdIsWatched checks to see if fd is associated with this EventPort
func (e *EventPort) FdIsWatched(fd uintptr) bool {
e.mu.Lock()
defer e.mu.Unlock()
_, found := e.fds[fd]
return found
}

// AssociatePath wraps port_associate(3c) for a filesystem path including
// creating the necessary file_obj from the provided stat information
func (e *EventPort) AssociatePath(path string, stat os.FileInfo, events int, cookie *EventPortUserCookie) error {
e.mu.Lock()
defer e.mu.Unlock()
if _, found := e.paths[path]; found {
return fmt.Errorf("%v is already associated with this Event Port", path)
}
fobj, err := createFileObj(path, stat)
if err != nil {
return err
}
_, err = port_associate(e.port, PORT_SOURCE_FILE, uintptr(unsafe.Pointer(fobj)), events, (*byte)(unsafe.Pointer(cookie)))
if err != nil {
return err
}
e.paths[path] = fobj
e.fobjs[fobj] = cookie
return nil
}

// DissociatePath wraps port_dissociate(3c) for a filesystem path
func (e *EventPort) DissociatePath(path string) error {
e.mu.Lock()
defer e.mu.Unlock()
fobj, ok := e.paths[path]
if !ok {
return fmt.Errorf("%v is not associated with this Event Port", path)
}
_, err := port_dissociate(e.port, PORT_SOURCE_FILE, uintptr(unsafe.Pointer(fobj)))
if err != nil {
return err
}
delete(e.fobjs, fobj)
delete(e.paths, path)
return nil
}

// AssociateFd wraps calls to port_associate(3c) on file descriptors
func (e *EventPort) AssociateFd(fd uintptr, events int, cookie *EventPortUserCookie) error {
e.mu.Lock()
defer e.mu.Unlock()
if _, found := e.fds[fd]; found {
return fmt.Errorf("%v is already associated with this Event Port", fd)
}
_, err := port_associate(e.port, PORT_SOURCE_FD, fd, events, (*byte)(unsafe.Pointer(cookie)))
if err != nil {
return err
}
e.fds[fd] = cookie
return nil
}

// DissociateFd wraps calls to port_dissociate(3c) on file descriptors
func (e *EventPort) DissociateFd(fd uintptr) error {
e.mu.Lock()
defer e.mu.Unlock()
_, ok := e.fds[fd]
if !ok {
return fmt.Errorf("%v is not associated with this Event Port", fd)
}
_, err := port_dissociate(e.port, PORT_SOURCE_FD, fd)
if err != nil {
return err
}
delete(e.fds, fd)
return nil
}

func createFileObj(name string, stat os.FileInfo) (*fileObj, error) {
fobj := new(fileObj)
bs, err := ByteSliceFromString(name)
if err != nil {
return nil, err
}
fobj.Name = (*int8)(unsafe.Pointer(&bs[0]))
s := stat.Sys().(*syscall.Stat_t)
fobj.Atim.Sec = s.Atim.Sec
fobj.Atim.Nsec = s.Atim.Nsec
fobj.Mtim.Sec = s.Mtim.Sec
fobj.Mtim.Nsec = s.Mtim.Nsec
fobj.Ctim.Sec = s.Ctim.Sec
fobj.Ctim.Nsec = s.Ctim.Nsec
return fobj, nil
}

// Get wraps port_get(3c) and returns a PortEvent
func (e *EventPort) Get(t *Timespec) (*PortEvent, error) {
pe := new(portEvent)
_, err := port_get(e.port, pe, t)
if err != nil {
return nil, err
}
p := new(PortEvent)
p.Events = pe.Events
p.Source = pe.Source
e.mu.Lock()
defer e.mu.Unlock()
switch pe.Source {
case PORT_SOURCE_FD:
p.Fd = uintptr(pe.Object)
p.Cookie = e.fds[p.Fd]
delete(e.fds, p.Fd)
case PORT_SOURCE_FILE:
p.fobj = (*fileObj)(unsafe.Pointer(uintptr(pe.Object)))
p.Path = BytePtrToString((*byte)(unsafe.Pointer(p.fobj.Name)))
p.Cookie = (*EventPortUserCookie)(unsafe.Pointer(pe.User))
if fobj, found := e.paths[p.Path]; found {
delete(e.fobjs, fobj)
delete(e.paths, p.Path)
}
}
return p, nil
}

0 comments on commit 0d4fc1a

Please sign in to comment.