1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <dirent.h>
6#include <fcntl.h>
7#include <stdlib.h>
8#include <stdio.h>
9#include <string.h>
10#include <unistd.h>
11
12#include <lib/fdio/io.h>
13#include <lib/fdio/namespace.h>
14#include <lib/fdio/spawn.h>
15#include <zircon/status.h>
16#include <zircon/syscalls.h>
17
18void print_namespace(fdio_flat_namespace_t* flat) {
19    for (size_t n = 0; n < flat->count; n++) {
20        fprintf(stderr, "{ .handle = 0x%08x, type = 0x%08x, .path = '%s' },\n",
21                flat->handle[n], flat->type[n], flat->path[n]);
22    }
23}
24
25zx_status_t load_file(const char* path, zx_handle_t* vmo) {
26    int fd = open(path, O_RDONLY);
27    if (fd < 0)
28        return ZX_ERR_IO;
29    zx_status_t status = fdio_get_vmo_clone(fd, vmo);
30    close(fd);
31    return status;
32}
33
34int run_in_namespace(const char** argv, size_t count, const char* const* mapping) {
35    zx_status_t status;
36    zx_handle_t binary;
37    if ((status = load_file(argv[0], &binary)) != ZX_OK) {
38        fprintf(stderr, "error: failed to read '%s': %d (%s)\n", argv[0], status,
39                zx_status_get_string(status));
40        return -1;
41    }
42
43    fdio_ns_t* ns;
44    if ((status = fdio_ns_create(&ns)) < 0) {
45        fprintf(stderr, "error: failed to create namespace: %d (%s)\n", status,
46                zx_status_get_string(status));
47        return -1;
48    }
49    const char* replacement_argv0 = NULL;
50    for (size_t n = 0; n < count; n++) {
51        const char* dst = *mapping++;
52        char* src = strchr(dst, '=');
53        if (src == NULL) {
54            fprintf(stderr, "error: mapping '%s' not in form of '<dst>=<src>'\n", dst);
55            return -1;
56        }
57        *src++ = 0;
58        if (strcmp(dst, "--replace-child-argv0") == 0) {
59            if (replacement_argv0) {
60                fprintf(stderr, "error: multiple --replace-child-argv0 specified\n");
61                return -1;
62            }
63            replacement_argv0 = src;
64            continue;
65        }
66        int fd = open(src, O_RDONLY | O_DIRECTORY);
67        if (fd < 0) {
68            fprintf(stderr, "error: cannot open '%s'\n", src);
69            return -1;
70        }
71        if ((status = fdio_ns_bind_fd(ns, dst, fd)) < 0) {
72            fprintf(stderr, "error: binding fd %d to '%s' failed: %d (%s)\n", fd, dst, status,
73                    zx_status_get_string(status));
74            close(fd);
75            return -1;
76        }
77        close(fd);
78    }
79    fdio_flat_namespace_t* flat;
80    fdio_ns_opendir(ns);
81    status = fdio_ns_export(ns, &flat);
82    fdio_ns_destroy(ns);
83    if (status < 0) {
84        fprintf(stderr, "error: cannot flatten namespace: %d (%s)\n", status,
85                zx_status_get_string(status));
86        return -1;
87    }
88
89    print_namespace(flat);
90
91    fdio_spawn_action_t actions[flat->count + 1];
92
93    for (size_t i = 0; i < flat->count; ++i) {
94        fdio_spawn_action_t add_ns_entry = {
95            .action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
96            .ns = {
97                .prefix = flat->path[i],
98                .handle = flat->handle[i],
99            },
100        };
101        actions[i] = add_ns_entry;
102    }
103
104    fdio_spawn_action_t set_name = {.action = FDIO_SPAWN_ACTION_SET_NAME,
105                                    .name = {.data = argv[0]}};
106    actions[flat->count] = set_name;
107
108    uint32_t flags = FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_NAMESPACE;
109
110    if (replacement_argv0)
111        argv[0] = replacement_argv0;
112
113    char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
114    zx_handle_t proc = ZX_HANDLE_INVALID;
115    status = fdio_spawn_vmo(ZX_HANDLE_INVALID, flags, binary, argv, NULL,
116                            countof(actions), actions, &proc, err_msg);
117
118    free(flat);
119
120    if (status != ZX_OK) {
121        fprintf(stderr, "error: failed to launch command: %d (%s): %s\n",
122                status, zx_status_get_string(status), err_msg);
123        return -1;
124    }
125
126    zx_object_wait_one(proc, ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, NULL);
127    zx_info_process_t info;
128    zx_object_get_info(proc, ZX_INFO_PROCESS, &info, sizeof(info), NULL, NULL);
129    fprintf(stderr, "[done]\n");
130    return info.return_code;
131}
132
133int dump_current_namespace(void) {
134    fdio_flat_namespace_t* flat;
135    zx_status_t r = fdio_ns_export_root(&flat);
136
137    if (r < 0) {
138        fprintf(stderr, "error: cannot export namespace: %d\n", r);
139        return -1;
140    }
141
142    print_namespace(flat);
143    return 0;
144}
145
146int main(int argc, const char** argv) {
147    if (argc == 2 && strcmp(argv[1], "--dump") == 0) {
148        return dump_current_namespace();
149    }
150
151    if (argc > 1) {
152        const char* kDefaultArgv[] = { "/boot/bin/sh", NULL };
153        const char** child_argv = kDefaultArgv;
154        size_t count = 0;
155        const char* const* mapping = argv + 1;
156        for (int i = 1; i < argc; ++i) {
157            if (strcmp(argv[i], "--") == 0) {
158                if (i + 1 < argc)
159                    child_argv = argv + i + 1;
160                break;
161            }
162            ++count;
163        }
164        return run_in_namespace(child_argv, count, mapping);
165    }
166
167    printf("Usage: %s ( --dump | [dst=src]+ [--replace-child-argv0=child_argv0] [ -- cmd arg1 ... argn ] )\n"
168           "Dumps the current namespace or runs a command with src mapped to dst.\n"
169           "If no command is specified, runs a shell.\n"
170           "If --replace-child-argv0 is supplied, that string will be used for argv[0]\n"
171           "as the child process sees it.\n",
172           argv[0]);
173    return -1;
174}
175