[fuchsia] os: migrate StartProcess to using fdio_spawn

TC-155 #done

Change-Id: Ie6cc2c29d0cab0cb207b095e74e452484dd88de6
diff --git a/src/os/exec_fuchsia.go b/src/os/exec_fuchsia.go
index 52d227a..87d6020 100644
--- a/src/os/exec_fuchsia.go
+++ b/src/os/exec_fuchsia.go
@@ -63,7 +63,7 @@
 }
 
 func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
-	h, err := launchpadLaunchFdio(name, argv, attr)
+	h, err := fdioStartProcess(name, argv, attr)
 	if err != nil {
 		return nil, err
 	}
diff --git a/src/os/exec_fuchsia_cgo.go b/src/os/exec_fuchsia_cgo.go
index ed5f1eb..1039468 100644
--- a/src/os/exec_fuchsia_cgo.go
+++ b/src/os/exec_fuchsia_cgo.go
@@ -6,18 +6,25 @@
 
 package os
 
-// #cgo fuchsia CFLAGS: -I${SRCDIR}/../../../../../zircon/system/ulib/launchpad/include
-// #cgo fuchsia LDFLAGS: -llaunchpad
-// #include <launchpad/launchpad.h>
-// #include <launchpad/vmo.h>
-// #include <zircon/process.h>
-// #include <zircon/syscalls.h>
+// #cgo fuchsia CFLAGS: -I${SRCDIR}/../../../../../zircon/system/ulib/fdio/include
+// #cgo fuchsia LDFLAGS: -lfdio
+//
 // #include <zircon/syscalls/object.h>
 // #include <zircon/types.h>
+// #include <lib/fdio/spawn.h>
+// #include <lib/fdio/util.h>
 // #include <stdlib.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 (
 	"errors"
+	"strings"
 	"syscall"
 	"syscall/zx"
 	"syscall/zx/fdio"
@@ -25,16 +32,6 @@
 	"unsafe"
 )
 
-const (
-	LaunchpadCloneFdioNamespace = 1
-	LaunchpadCloneFdioCwd       = 2
-	LaunchpadCloneFdioStdio     = 4
-	LaunchpadCloneFdioAll       = 0xff
-	LaunchpadCloneEnviron       = 0x100
-	LaunchpadCloneDefaultJob    = 0x200
-	LaunchpadCloneAll           = 0xffff
-)
-
 func makeCStringArray(s []string) []*C.char {
 	ret := make([]*C.char, len(s)+1)
 	for i, s := range s {
@@ -52,15 +49,14 @@
 	}
 }
 
-// Clone an fdio to a C array of handle values and types.
-func cloneHandleToCHandleArray(m fdio.FDIO) (int, []C.zx_handle_t, []C.uint32_t, error) {
-	handles, err := m.Clone()
-	if err != nil {
-		return 0, nil, nil, ErrInvalid
+// 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
 	}
-	handlesC := make([]C.zx_handle_t, fdio.MaxHandles)
-	typesC := make([]C.uint32_t, fdio.MaxHandles)
-	fdioType := 0
+
+	var fdioType C.uint32_t
 	switch m.(type) {
 	case *fdio.RemoteIO:
 		fdioType = fdio.HandleTypeRemote
@@ -69,145 +65,121 @@
 	case *fdio.Logger:
 		fdioType = fdio.HandleTypeLogger
 	default:
-		for _, h := range handles {
-			h.Close()
-		}
-		return 0, nil, nil, ErrInvalid
+		return -1, ErrInvalid
 	}
+
+	handles, err := m.Clone()
+	if err != nil {
+		return -1, err
+	}
+
+	var handlesC [fdio.MaxHandles]C.zx_handle_t
+	var typesC [fdio.MaxHandles]C.uint32_t
 	for i, h := range handles {
 		handlesC[i] = C.zx_handle_t(h)
 		typesC[i] = C.uint32_t(fdioType)
 	}
-	return len(handles), handlesC, typesC, nil
+
+	var fd C.int
+	status := zx.Status(C.fdio_create_fd(&handlesC[0], &typesC[0], C.size_t(len(handles)), &fd))
+	if status != zx.ErrOk {
+		return -1, errors.New("fdio_create_fd failed")
+	}
+	return int(fd), nil
 }
 
-func closeAll(handles []C.zx_handle_t) {
-	for _, h := range handles {
-		if h != 0 {
-			h := zx.Handle(h)
-			h.Close()
+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 {
+			if strings.HasPrefix(s, "PWD=") {
+				found = true
+				env[i] = "PWD=" + attr.Dir
+				break
+			}
+		}
+		if !found {
+			env = append(env, "PWD="+attr.Dir)
 		}
 	}
-}
 
-func launchpadTransferFileToTargetFd(lp *C.launchpad_t, f *File, target int) error {
-	if f == nil {
-		return nil
-	}
-	m := syscall.FDIOForFD(int(f.Fd()))
-	if m == nil {
-		return ErrInvalid
-	}
-	numHandles, handlesC, typesC, err := cloneHandleToCHandleArray(m)
-	if err != nil {
-		return err
-	}
-	for i := 0; i < numHandles; i = i + 1 {
-		typesC[i] = typesC[i] | C.uint32_t(target<<16)
-	}
-
-	status := zx.Status(C.launchpad_add_handles(lp, C.size_t(numHandles), &(handlesC[0]), &(typesC[0])))
-	if status != zx.ErrOk {
-		closeAll(handlesC)
-		return errors.New("transfer file")
-	}
-	return nil
-}
-
-func launchpadSetWd(lp *C.launchpad_t, dir string) error {
-	f, err := Open(dir)
-	if err != nil {
-		return err
-	}
-	m := syscall.FDIOForFD(int(f.Fd()))
-	if m == nil {
-		return ErrNotExist
-	}
-	numHandles, handlesC, typesC, err := cloneHandleToCHandleArray(m)
-	if err != nil {
-		return err
-	}
-	typesC[0] = fdio.HandleTypeCWD
-	status := zx.Status(C.launchpad_add_handles(lp, C.size_t(numHandles), &(handlesC[0]), &(typesC[0])))
-	if status != zx.ErrOk {
-		closeAll(handlesC)
-		return errors.New("transfer wd error: " + itoa(int(status)))
-	}
-	return nil
-}
-
-func launchpadLaunchFdio(name string, argv []string, attr *ProcAttr) (zx.Handle, error) {
 	nameC := C.CString(name)
 	defer C.free(unsafe.Pointer(nameC))
 	argvC := makeCStringArray(argv)
 	defer freeCStringArray(argvC)
-	env := Environ()
-	if attr.Env != nil {
-		env = attr.Env
-	}
 	envC := makeCStringArray(env)
 	defer freeCStringArray(envC)
-	jobToChild := zx.Handle(0)
-	job := zx.Handle(C.zx_job_default())
-	if job > 0 {
-		err := error(nil)
-		jobToChild, err = job.Duplicate(zx.RightSameRights)
-		if err != nil {
-			return 0, errors.New("duplicating job")
-		}
+
+	actions, err := fdioSpawnActions(attr)
+	if err != nil {
+		return 0, err
 	}
-	lp := &C.launchpad_t{}
-	status := zx.Status(C.launchpad_create(C.zx_handle_t(jobToChild), nameC, &lp))
+
+	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,
+		C.FDIO_SPAWN_CLONE_JOB | C.FDIO_SPAWN_CLONE_LDSVC | C.FDIO_SPAWN_CLONE_NAMESPACE, // TODO(mdempsky): Flags.
+		nameC,
+		&argvC[0],
+		&envC[0],
+		C.size_t(len(actions)),
+		actions0,
+		&h,
+		&errmsg[0],
+	))
 	if status != zx.ErrOk {
-		return 0, errors.New("launchpad_create")
-	}
-	defer func() {
-		if lp != nil {
-			C.launchpad_destroy(lp)
-		}
-	}()
-	status = zx.Status(C.launchpad_load_from_file(lp, nameC))
-	if status != zx.ErrOk {
-		return 0, errors.New("loading vdso")
-	}
-	status = zx.Status(C.launchpad_set_args(lp, C.int(len(argvC)-1), &(argvC[0])))
-	if status != zx.ErrOk {
-		return 0, errors.New("arguments")
-	}
-	status = zx.Status(C.launchpad_set_environ(lp, &(envC[0])))
-	if status != zx.ErrOk {
-		return 0, errors.New("environ")
-	}
-	status = zx.Status(C.launchpad_clone(lp, C.uint32_t(LaunchpadCloneFdioNamespace)))
-	if status != zx.ErrOk {
-		return 0, errors.New("launchpad_clone")
-	}
-	if attr.Dir != "" {
-		err := launchpadSetWd(lp, attr.Dir)
-		if err != nil {
-			return 0, err
-		}
-	} else {
-		status = zx.Status(C.launchpad_clone(lp, C.uint32_t(LaunchpadCloneFdioCwd))) // TODO: Go to Attr.Dir
-		if status != zx.ErrOk {
-			return 0, errors.New("clone_fdio_cwd")
-		}
-	}
-	for i, f := range attr.Files {
-		if err := launchpadTransferFileToTargetFd(lp, f, i); err != nil {
-			return 0, err
-		}
-	}
-	h := C.zx_handle_t(0)
-	status = zx.Status(C.launchpad_go(lp, &h, nil))
-	// lp is freed by launchpad_go unconditionally
-	lp = nil
-	if status < 0 {
-		return 0, errors.New("launchpad error")
+		return 0, errors.New("fdio_spawn_etc: " + itoa(int(status)) + ": " + 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)
diff --git a/src/syscall/zx/fdio/fdio.go b/src/syscall/zx/fdio/fdio.go
index af255de..dfdffd7 100644
--- a/src/syscall/zx/fdio/fdio.go
+++ b/src/syscall/zx/fdio/fdio.go
@@ -227,9 +227,9 @@
 )
 
 const (
-	HandleTypeCWD    = 0x31
 	HandleTypeRemote = 0x32
 	HandleTypePipe   = 0x33
 	HandleTypeEvent  = 0x34
 	HandleTypeLogger = 0x35
+	HandleTypeSocket = 0x36
 )