diff --git a/unix/syscall_solaris.go b/unix/syscall_solaris.go index 77fcde7c1..e48703da6 100644 --- a/unix/syscall_solaris.go +++ b/unix/syscall_solaris.go @@ -13,7 +13,10 @@ package unix import ( + "fmt" + "os" "runtime" + "sync" "syscall" "unsafe" ) @@ -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 +} diff --git a/unix/syscall_solaris_test.go b/unix/syscall_solaris_test.go index 910bdf1c3..13490bed1 100644 --- a/unix/syscall_solaris_test.go +++ b/unix/syscall_solaris_test.go @@ -8,7 +8,11 @@ package unix_test import ( + "fmt" + "io/ioutil" + "os" "os/exec" + "runtime" "testing" "golang.org/x/sys/unix" @@ -41,3 +45,184 @@ 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() + defer port.Close() + 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") + } +} + +func ExampleEventPortUserCookie() { + type MyCookie struct { + Name string + } + mycookie := MyCookie{"Cookie Monster"} + port, err := unix.NewEventPort() + if err != nil { + fmt.Printf("NewEventPort failed: %v\n", err) + return + } + defer port.Close() + r, w, err := os.Pipe() + if err != nil { + fmt.Printf("os.Pipe() failed: %v\n", err) + return + } + defer w.Close() + defer r.Close() + fd := r.Fd() + + // cast mycookie as EventPortUserCookie + var cookie unix.EventPortUserCookie = mycookie + 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 != nil { + fmt.Printf("didn't get the expected event: %v\n", err) + } + + // cast the received cookie back to its original type + c := (*pevent.Cookie).(MyCookie) + fmt.Printf("%s", c.Name) + //Output: Cookie Monster +} diff --git a/unix/zsyscall_solaris_amd64.go b/unix/zsyscall_solaris_amd64.go index 4e18d5c99..3db59afeb 100644 --- a/unix/zsyscall_solaris_amd64.go +++ b/unix/zsyscall_solaris_amd64.go @@ -141,6 +141,10 @@ import ( //go:cgo_import_dynamic libc_getpeername getpeername "libsocket.so" //go:cgo_import_dynamic libc_setsockopt setsockopt "libsocket.so" //go:cgo_import_dynamic libc_recvfrom recvfrom "libsocket.so" +//go:cgo_import_dynamic libc_port_create port_create "libc.so" +//go:cgo_import_dynamic libc_port_associate port_associate "libc.so" +//go:cgo_import_dynamic libc_port_dissociate port_dissociate "libc.so" +//go:cgo_import_dynamic libc_port_get port_get "libc.so" //go:linkname procpipe libc_pipe //go:linkname procpipe2 libc_pipe2 @@ -272,6 +276,10 @@ import ( //go:linkname procgetpeername libc_getpeername //go:linkname procsetsockopt libc_setsockopt //go:linkname procrecvfrom libc_recvfrom +//go:linkname procport_create libc_port_create +//go:linkname procport_associate libc_port_associate +//go:linkname procport_dissociate libc_port_dissociate +//go:linkname procport_get libc_port_get var ( procpipe, @@ -403,7 +411,11 @@ var ( proc__xnet_getsockopt, procgetpeername, procsetsockopt, - procrecvfrom syscallFunc + procrecvfrom, + procport_create, + procport_associate, + procport_dissociate, + procport_get syscallFunc ) // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT @@ -1981,3 +1993,47 @@ func recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Sockl } return } + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func port_create() (n int, err error) { + r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_create)), 0, 0, 0, 0, 0, 0, 0) + n = int(r0) + if e1 != 0 { + err = e1 + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func port_associate(port int, source int, object uintptr, events int, user *byte) (n int, err error) { + r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_associate)), 5, uintptr(port), uintptr(source), uintptr(object), uintptr(events), uintptr(unsafe.Pointer(user)), 0) + n = int(r0) + if e1 != 0 { + err = e1 + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func port_dissociate(port int, source int, object uintptr) (n int, err error) { + r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_dissociate)), 3, uintptr(port), uintptr(source), uintptr(object), 0, 0, 0) + n = int(r0) + if e1 != 0 { + err = e1 + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func port_get(port int, pe *portEvent, timeout *Timespec) (n int, err error) { + r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_get)), 3, uintptr(port), uintptr(unsafe.Pointer(pe)), uintptr(unsafe.Pointer(timeout)), 0, 0, 0) + n = int(r0) + if e1 != 0 { + err = e1 + } + return +}