| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <errno.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fdio/unsafe.h> |
| #include <poll.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/device/pty.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/object.h> |
| |
| #include "shell.h" |
| #include "memalloc.h" |
| #include "nodes.h" |
| #include "exec.h" |
| #include "process.h" |
| #include "options.h" |
| #include "var.h" |
| |
| static zx_status_t launch(const char* filename, const char* const* argv, |
| const char* const* envp, zx_handle_t* process, |
| zx_handle_t job, char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]) { |
| // cancel any ^c generated before running the command |
| uint32_t events = 0; |
| ioctl_pty_read_events(STDIN_FILENO, &events); // ignore any error |
| |
| // TODO(abarth): Including FDIO_SPAWN_DEFAULT_LDSVC doesn't fully make sense. |
| // We should find a library loader that's appropriate for this program |
| // rather than cloning the library loader used by the shell. |
| uint32_t flags = FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_ENVIRON; |
| return fdio_spawn_etc(job, flags, filename, argv, envp, 0, NULL, process, err_msg); |
| } |
| |
| // Add all function definitions to our nodelist, so we can package them up for a |
| // subshell. |
| static void |
| addfuncdef(struct cmdentry *entry, void *token) |
| { |
| if (entry->cmdtype == CMDFUNCTION) { |
| struct nodelist **cmdlist = (struct nodelist **) token; |
| struct nodelist *newnode = ckmalloc(sizeof(struct nodelist)); |
| newnode->next = *cmdlist; |
| newnode->n = &entry->u.func->n; |
| *cmdlist = newnode; |
| } |
| } |
| |
| zx_status_t process_subshell(union node* n, const char* const* envp, |
| zx_handle_t* process, zx_handle_t job, |
| int *fds, char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]) |
| { |
| if (!orig_arg0) |
| return ZX_ERR_NOT_FOUND; |
| |
| // TODO(abarth): Handle the redirects properly (i.e., implement |
| // redirect(n->nredir.redirect) using launchpad); |
| zx_handle_t ast_vmo = ZX_HANDLE_INVALID; |
| |
| // Create a node for our expression |
| struct nodelist *nlist = ckmalloc(sizeof(struct nodelist)); |
| nlist->n = n; |
| nlist->next = NULL; |
| |
| // Create nodes for all function definitions |
| hashiter(addfuncdef, &nlist); |
| |
| // Encode the node list |
| zx_status_t status = codec_encode(nlist, &ast_vmo); |
| |
| // Clean up |
| while (nlist) { |
| struct nodelist *next = nlist->next; |
| ckfree(nlist); |
| nlist = next; |
| } |
| |
| if (status != ZX_OK) |
| return status; |
| |
| // Construct an argv array |
| int argc = 1 + shellparam.nparam; |
| const char *argv[argc + 1]; |
| argv[0] = orig_arg0; |
| size_t arg_ndx; |
| for (arg_ndx = 0; arg_ndx < shellparam.nparam; arg_ndx++) { |
| argv[arg_ndx + 1] = shellparam.p[arg_ndx]; |
| } |
| argv[argc] = NULL; |
| |
| fdio_spawn_action_t actions[] = { |
| {.action = FDIO_SPAWN_ACTION_CLONE_FD, .fd = {.local_fd = fds ? fds[0] : 0, .target_fd = 0}}, |
| {.action = FDIO_SPAWN_ACTION_CLONE_FD, .fd = {.local_fd = fds ? fds[1] : 1, .target_fd = 1}}, |
| {.action = FDIO_SPAWN_ACTION_CLONE_FD, .fd = {.local_fd = fds ? fds[2] : 2, .target_fd = 2}}, |
| {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, .h = {.id = PA_HND(PA_USER0, 0), .handle = ast_vmo}}, |
| }; |
| |
| // TODO(abarth): Including FDIO_SPAWN_DEFAULT_LDSVC doesn't fully make sense. |
| // We should find a library loader that's appropriate for this program |
| // rather than cloning the library loader used by the shell. |
| uint32_t flags = FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_DEFAULT_LDSVC | FDIO_SPAWN_CLONE_NAMESPACE; |
| return fdio_spawn_etc(job, flags, orig_arg0, argv, envp, |
| countof(actions), actions, process, err_msg); |
| } |
| |
| int process_launch(const char* const* argv, const char* path, int index, |
| zx_handle_t* process, zx_handle_t job, |
| zx_status_t* status_out, char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]) { |
| zx_status_t status = ZX_OK; |
| |
| // All exported variables |
| const char* const* envp = (const char* const*)environment(); |
| |
| if (strchr(argv[0], '/') != NULL) { |
| status = launch(argv[0], argv, envp, process, job, err_msg); |
| } else { |
| status = ZX_ERR_NOT_FOUND; |
| const char* filename = NULL; |
| while (status != ZX_OK && (filename = padvance(&path, argv[0])) != NULL) { |
| if (--index < 0 && pathopt == NULL) |
| status = launch(filename, argv, envp, process, job, err_msg); |
| stunalloc(filename); |
| } |
| } |
| |
| *status_out = status; |
| |
| switch (status) { |
| case ZX_OK: |
| return 0; |
| case ZX_ERR_ACCESS_DENIED: |
| return 126; |
| case ZX_ERR_NOT_FOUND: |
| return 127; |
| default: |
| return 2; |
| } |
| } |
| |
| // TODO(ZX-972) When isatty correctly examines the fd, use that instead |
| int isapty(int fd) { |
| fdio_t* io = fdio_unsafe_fd_to_io(fd); |
| if (io == NULL) { |
| errno = EBADF; |
| return 0; |
| } |
| |
| // if we can find the window size, it's a tty |
| int ret; |
| pty_window_size_t ws; |
| int noread = ioctl_pty_get_window_size(fd, &ws); |
| if (noread == sizeof(ws)) { |
| ret = 1; |
| } else { |
| ret = 0; |
| errno = ENOTTY; |
| } |
| |
| fdio_unsafe_release(io); |
| |
| return ret; |
| } |
| |
| /* Check for process termination (block if requested). When not blocking, |
| returns ZX_ERR_TIMED_OUT if process hasn't exited yet. */ |
| int process_await_termination(zx_handle_t process, zx_handle_t job, bool blocking) { |
| zx_time_t timeout = blocking ? ZX_TIME_INFINITE : 0; |
| zx_status_t status; |
| zx_wait_item_t wait_objects[2]; |
| fdio_t* tty = (isapty(STDIN_FILENO) ? fdio_unsafe_fd_to_io(STDIN_FILENO) : NULL); |
| |
| bool running = true; |
| while (running) { |
| int no_wait_obj = 0; |
| int tty_wait_obj = -1; |
| wait_objects[no_wait_obj].handle = process; |
| wait_objects[no_wait_obj].waitfor = ZX_TASK_TERMINATED; |
| wait_objects[no_wait_obj].pending = 0; |
| no_wait_obj++; |
| |
| if (tty) { |
| wait_objects[no_wait_obj].pending = 0; |
| fdio_unsafe_wait_begin(tty, POLLPRI, &wait_objects[no_wait_obj].handle, &wait_objects[no_wait_obj].waitfor); |
| tty_wait_obj = no_wait_obj; |
| no_wait_obj++; |
| } |
| |
| status = zx_object_wait_many(wait_objects, no_wait_obj, timeout); |
| |
| uint32_t interrupt_event = 0; |
| if (tty) { |
| fdio_unsafe_wait_end(tty, wait_objects[tty_wait_obj].pending, &interrupt_event); |
| } |
| |
| if (status != ZX_OK && status != ZX_ERR_TIMED_OUT) { |
| running = false; |
| } else if (wait_objects[0].pending & ZX_TASK_TERMINATED) { |
| // process ended normally |
| status = ZX_OK; |
| running = false; |
| } else if (tty && (interrupt_event & POLLPRI)) { |
| // interrupted - kill process |
| uint32_t events = 0; |
| int noread = ioctl_pty_read_events(STDIN_FILENO, &events); |
| if (noread == sizeof(events) && (events & PTY_EVENT_INTERRUPT)) { |
| // process belongs to job, so killing the job kills the process |
| status = zx_task_kill(job); |
| // If the kill failed status is going to be ZX_ERR_ACCESS_DENIED |
| // which is not likely since the user started this process |
| if (status == ZX_OK) { |
| status = ZX_ERR_CANCELED; |
| } |
| running = false; |
| } |
| } |
| } |
| if (tty) |
| fdio_unsafe_release(tty); |
| |
| if (status != ZX_OK) |
| return status; |
| |
| zx_info_process_t proc_info; |
| status = zx_object_get_info(process, ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), NULL, NULL); |
| if (status != ZX_OK) |
| return status; |
| |
| return proc_info.return_code; |
| } |