blob: 6c73958e6f1ad8beaf599ce3763919b6000c8f7a [file] [log] [blame]
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// `go mod` ignores file names for the purpose of resolving
// dependencies, and zxwait doesn't build on not-Fuchsia.
//go:build fuchsia
// +build fuchsia
package os
// #cgo fuchsia CFLAGS: -I${SRCDIR}/../../../../../zircon/system/ulib/fdio/include
// #cgo fuchsia LDFLAGS: -lfdio
//
// #include <lib/fdio/fd.h>
// #include <lib/fdio/spawn.h>
// #include <stdlib.h>
// #include <unistd.h>
// #include <zircon/syscalls/object.h>
// #include <zircon/types.h>
//
// static int fsa_get_local_fd(fdio_spawn_action_t *fsa) { return fsa->fd.local_fd; }
// static int fsa_get_target_fd(fdio_spawn_action_t *fsa) { return fsa->fd.target_fd; }
//
// static void fsa_set_local_fd(fdio_spawn_action_t *fsa, int fd) { fsa->fd.local_fd = fd; }
// static void fsa_set_target_fd(fdio_spawn_action_t *fsa, int fd) { fsa->fd.target_fd = fd; }
import "C"
import (
"context"
"errors"
"syscall"
"syscall/zx"
"syscall/zx/zxwait"
"unsafe"
)
func makeCStringArray(s []string) []*C.char {
ret := make([]*C.char, len(s)+1)
for i, s := range s {
ret[i] = (*C.char)(C.CString(s))
}
ret[len(ret)-1] = nil
return ret
}
func freeCStringArray(a []*C.char) {
for i := range a {
if a[i] != nil {
C.free(unsafe.Pointer(a[i]))
}
}
}
// asLibfdioFD returns a File as a libfdio file descriptor.
func asLibfdioFD(f *File) (int, error) {
m := syscall.FDIOForFD(int(f.Fd()))
if m == nil {
return -1, ErrInvalid
}
mCopy, err := m.Clone()
if err != nil {
return -1, err
}
handles := mCopy.Handles()
var fd C.int
status := zx.Status(C.fdio_fd_create(C.zx_handle_t(handles[0]), &fd))
if status != zx.ErrOk {
return -1, errors.New("fdio_fd_create failed")
}
return int(fd), nil
}
func fdioSpawnActions(attr *ProcAttr) (actions []C.fdio_spawn_action_t, err error) {
defer func() {
if err != nil {
for _, action := range actions {
C.close(C.fsa_get_local_fd(&action))
}
}
}()
for i, f := range attr.Files {
if f == nil {
continue
}
fd, err := asLibfdioFD(f)
if err != nil {
return nil, err
}
action := C.fdio_spawn_action_t{action: C.FDIO_SPAWN_ACTION_TRANSFER_FD}
C.fsa_set_local_fd(&action, C.int(fd))
C.fsa_set_target_fd(&action, C.int(i))
actions = append(actions, action)
}
return actions, nil
}
func fdioStartProcess(name string, argv []string, attr *ProcAttr) (zx.Handle, error) {
env := attr.Env
if env == nil {
env = Environ()
}
if attr.Dir != "" {
found := false
for i, s := range env {
const prefix = "PWD="
// strings.HasPrefix
if len(s) >= len(prefix) && s[:len(prefix)] == prefix {
found = true
env[i] = "PWD=" + attr.Dir
break
}
}
if !found {
env = append(env, "PWD="+attr.Dir)
}
}
nameC := C.CString(name)
defer C.free(unsafe.Pointer(nameC))
argvC := makeCStringArray(argv)
defer freeCStringArray(argvC)
envC := makeCStringArray(env)
defer freeCStringArray(envC)
actions, err := fdioSpawnActions(attr)
if err != nil {
return 0, err
}
var actions0 *C.fdio_spawn_action_t
if len(actions) > 0 {
actions0 = &actions[0]
}
var h C.zx_handle_t
var errmsg [C.FDIO_SPAWN_ERR_MSG_MAX_LENGTH]C.char
status := zx.Status(C.fdio_spawn_etc(
C.ZX_HANDLE_INVALID,
// TODO(mdempsky): Flags.
C.FDIO_SPAWN_CLONE_JOB|C.FDIO_SPAWN_DEFAULT_LDSVC|C.FDIO_SPAWN_CLONE_NAMESPACE|C.FDIO_SPAWN_CLONE_UTC_CLOCK,
nameC,
&argvC[0],
&envC[0],
C.size_t(len(actions)),
actions0,
&h,
&errmsg[0],
))
if status != zx.ErrOk {
return 0, &zx.Error{
Status: status,
Text: "fdio_spawn_etc: " + charsAsString(errmsg[:]),
}
}
return zx.Handle(h), nil
}
func charsAsString(s []C.char) string {
var x []byte
for _, c := range s {
if c == 0 {
break
}
x = append(x, byte(c))
}
return string(x)
}
func (p *Process) kill() error {
procHandle := zx.Handle(p.handle)
status := zx.Sys_task_kill(procHandle)
if status != zx.ErrOk {
return &zx.Error{
Status: status,
Text: "kill error",
}
}
return nil
}
func (p *Process) wait() (*ProcessState, error) {
procHandle := zx.Handle(p.handle)
if _, err := zxwait.WaitContext(context.Background(), procHandle, zx.SignalTaskTerminated); err != nil {
return nil, err
}
procInfo := C.zx_info_process_t{}
status := zx.Sys_object_get_info(procHandle, zx.ObjectInfoProcess, unsafe.Pointer(&procInfo), C.sizeof_zx_info_process_t, nil, nil)
if status != zx.ErrOk {
return nil, &zx.Error{
Status: status,
Text: "error retrieving process info",
}
}
return &ProcessState{retCode: int(procInfo.return_code)}, p.Release()
}