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 13, 2021
1 parent 961cca9 commit ccb195a
Show file tree
Hide file tree
Showing 3 changed files with 386 additions and 1 deletion.
185 changes: 185 additions & 0 deletions unix/syscall_solaris.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
package unix

import (
"fmt"
"os"
"runtime"
"sync"
"syscall"
"unsafe"
)
Expand Down Expand Up @@ -744,3 +747,185 @@ 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

type EventPortUserCookie interface{}

type EventPort struct {
port int
fds map[uintptr]*EventPortUserCookie
paths map[string]*fileObj
fobjs map[*fileObj]*EventPortUserCookie
mu sync.Mutex
}

type PortEvent struct {
Cookie *EventPortUserCookie
Events int32 //must match portEvent.Events
Fd uintptr
Path string
Source uint16 //must match portEvent.Source
fobj *fileObj
}

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)

func (e *EventPort) Close() error {
for f, _ := range e.fds {
e.DissociateFd(f)
}
for p, _ := range e.paths {
e.DissociatePath(p)
}
return Close(e.port)
}

func (e *EventPort) PathIsWatched(path string) bool {
e.mu.Lock()
defer e.mu.Unlock()
_, found := e.paths[path]
return found
}

func (e *EventPort) FdIsWatched(fd uintptr) bool {
e.mu.Lock()
defer e.mu.Unlock()
_, found := e.fds[fd]
return found
}

func (e *EventPort) AssociatePath(path string, stat os.FileInfo, events int, cookie *EventPortUserCookie) error {
if e.PathIsWatched(path) {
return fmt.Errorf("%v is already associated with this Event Port", path)
}
e.mu.Lock()
defer e.mu.Unlock()
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
}

func (e *EventPort) deletePath(path string) {
e.mu.Lock()
defer e.mu.Unlock()
delete(e.fobjs, e.paths[path])
delete(e.paths, path)
}

func (e *EventPort) DissociatePath(path string) error {
if !e.PathIsWatched(path) {
return fmt.Errorf("%v is not associated with this Event Port", path)
}
e.mu.Lock()
_, err := port_dissociate(e.port, PORT_SOURCE_FILE, uintptr(unsafe.Pointer(e.paths[path])))
e.mu.Unlock()
if err != nil {
return err
}
e.deletePath(path)
return nil
}

func (e *EventPort) AssociateFd(fd uintptr, events int, cookie *EventPortUserCookie) error {
if e.FdIsWatched(fd) {
return fmt.Errorf("%v is already associated with this Event Port", fd)
}
e.mu.Lock()
defer e.mu.Unlock()
_, 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
}

func (e *EventPort) deleteFd(fd uintptr) {
e.mu.Lock()
defer e.mu.Unlock()
delete(e.fds, fd)
}

func (e *EventPort) DissociateFd(fd uintptr) error {
if !e.FdIsWatched(fd) {
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
}
e.deleteFd(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]))
fobj.Atim.Sec = stat.Sys().(*syscall.Stat_t).Atim.Sec
fobj.Atim.Nsec = stat.Sys().(*syscall.Stat_t).Atim.Nsec
fobj.Mtim.Sec = stat.Sys().(*syscall.Stat_t).Mtim.Sec
fobj.Mtim.Nsec = stat.Sys().(*syscall.Stat_t).Mtim.Nsec
fobj.Ctim.Sec = stat.Sys().(*syscall.Stat_t).Ctim.Sec
fobj.Ctim.Nsec = stat.Sys().(*syscall.Stat_t).Ctim.Nsec
return fobj, nil
}

func (f *fileObj) Path() string {
return BytePtrToString((*byte)(unsafe.Pointer(f.Name)))
}

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
switch pe.Source {
case PORT_SOURCE_FD:
p.Fd = uintptr(pe.Object)
e.mu.Lock()
p.Cookie = e.fds[p.Fd]
e.mu.Unlock()
e.deleteFd(p.Fd)
case PORT_SOURCE_FILE:
p.fobj = (*fileObj)(unsafe.Pointer(uintptr(pe.Object)))
p.Path = p.fobj.Path()
e.mu.Lock()
p.Cookie = e.fobjs[p.fobj]
e.mu.Unlock()
e.deletePath(p.Path)
}
return p, nil
}
144 changes: 144 additions & 0 deletions unix/syscall_solaris_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
package unix_test

import (
"io/ioutil"
"os"
"os/exec"
"runtime"
"testing"

"golang.org/x/sys/unix"
Expand Down Expand Up @@ -41,3 +44,144 @@ func TestSysconf(t *testing.T) {
}
t.Logf("Sysconf(SC_CLK_TCK) = %d", n)
}

// Event Ports

func TestBasicEventPort(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "eventport")
if err != nil {
t.Errorf("unable to create a tempfile: %v", err)
}
path := tmpfile.Name()
defer os.Remove(path)

stat, err := os.Stat(path)
if err != nil {
t.Errorf("Failed to stat %s: %v", path, err)
}
port, err := unix.NewEventPort()
if err != nil {
t.Errorf("NewEventPort failed: %v", err)
}
defer port.Close()
var cookie unix.EventPortUserCookie = stat.Mode()
err = port.AssociatePath(path, stat, unix.FILE_MODIFIED, &cookie)
if err != nil {
t.Errorf("AssociatePath failed: %v", err)
}
if !port.PathIsWatched(path) {
t.Errorf("PathIsWatched unexpectedly returned false")
}
err = port.DissociatePath(path)
if err != nil {
t.Errorf("DissociatePath failed: %v", err)
}
err = port.AssociatePath(path, stat, unix.FILE_MODIFIED, &cookie)
if err != nil {
t.Errorf("AssociatePath failed: %v", err)
}
bs := []byte{42}
tmpfile.Write(bs)
timeout := new(unix.Timespec)
timeout.Sec = 1
pevent, err := port.Get(timeout)
if err == unix.ETIME {
t.Errorf("PortGet timed out: %v", err)
}
if err != nil {
t.Errorf("PortGet failed: %v", err)
}
if pevent.Path != path {
t.Errorf("Path mismatch: %v != %v", pevent.Path, path)
}
err = port.AssociatePath(path, stat, unix.FILE_MODIFIED, &cookie)
if err != nil {
t.Errorf("AssociatePath failed: %v", err)
}
err = port.AssociatePath(path, stat, unix.FILE_MODIFIED, &cookie)
if err == nil {
t.Errorf("Unexpected success associating already associated path")
}
}

func TestEventPortFds(t *testing.T) {
_, path, _, _ := runtime.Caller(0)
stat, err := os.Stat(path)
fmode := stat.Mode()
port, err := unix.NewEventPort()
if err != nil {
t.Errorf("NewEventPort failed: %v", err)
}
defer port.Close()
r, w, err := os.Pipe()
if err != nil {
t.Errorf("unable to create a pipe: %v", err)
}
defer w.Close()
defer r.Close()
fd := r.Fd()

var cookie unix.EventPortUserCookie = fmode
port.AssociateFd(fd, unix.POLLIN, &cookie)
if !port.FdIsWatched(fd) {
t.Errorf("FdIsWatched unexpectedly returned false")
}
err = port.DissociateFd(fd)
err = port.AssociateFd(fd, unix.POLLIN, &cookie)
bs := []byte{42}
w.Write(bs)
timeout := new(unix.Timespec)
timeout.Sec = 1
pevent, err := port.Get(timeout)
if err == unix.ETIME {
t.Errorf("PortGet timed out: %v", err)
}
if err != nil {
t.Errorf("PortGet failed: %v", err)
}
if pevent.Fd != fd {
t.Errorf("Fd mismatch: %v != %v", pevent.Fd, fd)
}
var c = pevent.Cookie
if c == nil {
t.Errorf("Cookie missing: %v != %v", &cookie, c)
return
}
if *c != cookie {
t.Errorf("Cookie mismatch: %v != %v", cookie, *c)
}
port.AssociateFd(fd, unix.POLLIN, &cookie)
err = port.AssociateFd(fd, unix.POLLIN, &cookie)
if err == nil {
t.Errorf("unexpected success associating already associated fd")
}
}

func TestEventPortErrors(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "eventport")
if err != nil {
t.Errorf("unable to create a tempfile: %v", err)
}
path := tmpfile.Name()
stat, _ := os.Stat(path)
os.Remove(path)
port, _ := unix.NewEventPort()
err = port.AssociatePath(path, stat, unix.FILE_MODIFIED, nil)
if err == nil {
t.Errorf("unexpected success associating nonexistant file")
}
err = port.DissociatePath(path)
if err == nil {
t.Errorf("unexpected success dissociating unassociated path")
}
timeout := new(unix.Timespec)
timeout.Nsec = 1
_, err = port.Get(timeout)
if err != unix.ETIME {
t.Errorf("unexpected lack of timeout")
}
err = port.DissociateFd(uintptr(0))
if err == nil {
t.Errorf("unexpected success dissociating unassociated fd")
}
}

0 comments on commit ccb195a

Please sign in to comment.