1 // Copyright 2018 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 <runtests-utils/fuchsia-run-test.h>
6 
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <libgen.h>
10 #include <stdio.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13 
14 #include <fbl/auto_call.h>
15 #include <fbl/string.h>
16 #include <fbl/string_printf.h>
17 #include <fbl/unique_fd.h>
18 #include <lib/async-loop/cpp/loop.h>
19 #include <lib/fdio/io.h>
20 #include <lib/fdio/spawn.h>
21 #include <lib/zx/job.h>
22 #include <lib/zx/process.h>
23 #include <loader-service/loader-service.h>
24 #include <zircon/dlfcn.h>
25 #include <zircon/process.h>
26 #include <zircon/processargs.h>
27 #include <zircon/status.h>
28 #include <zircon/syscalls.h>
29 
30 #include <utility>
31 
32 namespace runtests {
33 
34 // Path to helper binary which can run test as a component. This binary takes
35 // component url as its parameter.
36 constexpr char kRunTestComponentPath[] = "/system/bin/run_test_component";
37 
DirectoryName(const fbl::String & path)38 fbl::String DirectoryName(const fbl::String& path) {
39     char* cpath = strndup(path.data(), path.length());
40     fbl::String ret(dirname(cpath));
41     free(cpath);
42     return ret;
43 }
44 
BaseName(const fbl::String & path)45 fbl::String BaseName(const fbl::String& path) {
46     char* cpath = strndup(path.data(), path.length());
47     fbl::String ret(basename(cpath));
48     free(cpath);
49     return ret;
50 }
51 
TestFileComponentInfo(const fbl::String path,fbl::String * component_url_out,fbl::String * cmx_file_path_out)52 void TestFileComponentInfo(const fbl::String path,
53                            fbl::String* component_url_out,
54                            fbl::String* cmx_file_path_out) {
55     if (strncmp(path.c_str(), kPkgPrefix, strlen(kPkgPrefix)) != 0) {
56         return;
57     }
58 
59     // Consume suffixes of the form
60     // "test/<test filename>" or "test/disabled/<test filename>"
61     bool is_disabled = (strstr(path.c_str(), "/disabled/") != nullptr);
62     const auto folder_path = is_disabled?
63                              DirectoryName(DirectoryName(DirectoryName(path))):
64                              DirectoryName(DirectoryName(path));
65 
66     // folder_path should also start with |kPkgPrefix| and should not be equal
67     // to |kPkgPrefix|.
68     if (strncmp(folder_path.c_str(), kPkgPrefix, strlen(kPkgPrefix)) != 0 ||
69         folder_path == fbl::String(kPkgPrefix)) {
70         return;
71     }
72 
73     const char* package_name = path.c_str() + strlen(kPkgPrefix);
74     // find occurence of first '/'
75     size_t i = 0;
76     while (package_name[i] != '\0' && package_name[i] != '/') {
77         i++;
78     }
79     const fbl::String package_name_str(package_name, i);
80     const auto test_file_name = BaseName(path);
81     *cmx_file_path_out = fbl::StringPrintf(
82         "%s/meta/%s.cmx", folder_path.c_str(), test_file_name.c_str());
83     *component_url_out =
84         fbl::StringPrintf("fuchsia-pkg://fuchsia.com/%s#meta/%s.cmx",
85                           package_name_str.c_str(), test_file_name.c_str());
86 }
87 
88 namespace {
89 
90 struct DataSinkDump {
91     fbl::String sink_name;
92     zx::vmo file_data;
93 };
94 
95 struct LoaderServiceState {
96     fbl::unique_fd root_dir_fd;
97     fbl::Vector<DataSinkDump> data;
98 };
99 
100 // This is a default set of library paths.
101 //
102 // Unfortunately this is duplicated in loader-service. We could get rid of this duplication by
103 // delegating to the existing loader service over FIDL for everything except PublishDataSink(),
104 // but the added complexity doesn't seem worth it.
105 const char* const kLibPaths[] = {"system/lib", "boot/lib"};
106 
107 // This is a helper specifically for the C API boundary with the implementation code.
VmoFromFd(fbl::unique_fd fd,const char * file_name,zx_handle_t * out)108 zx_status_t VmoFromFd(fbl::unique_fd fd, const char* file_name, zx_handle_t* out) {
109     zx_status_t status = fdio_get_vmo_clone(fd.get(), out);
110     if (status != ZX_OK) {
111         return status;
112     }
113     return zx_object_set_property(*out, ZX_PROP_NAME, file_name, strlen(file_name));
114 }
115 
LoadObject(void * ctx,const char * name,zx_handle_t * out)116 zx_status_t LoadObject(void* ctx, const char* name, zx_handle_t* out) {
117     const auto state = static_cast<LoaderServiceState*>(ctx);
118     for (const auto libdir : kLibPaths) {
119         char path[PATH_MAX];
120         if (snprintf(path, sizeof(path), "%s/%s", libdir, name) < 0) {
121             return ZX_ERR_INTERNAL;
122         }
123         fbl::unique_fd fd(openat(state->root_dir_fd.get(), path, O_RDONLY));
124         if (fd) {
125             return VmoFromFd(std::move(fd), name, out);
126         }
127     }
128     return ZX_ERR_NOT_FOUND;
129 }
130 
LoadAbspath(void * ctx,const char * path,zx_handle_t * out)131 zx_status_t LoadAbspath(void* ctx, const char* path, zx_handle_t* out) {
132     const auto state = static_cast<LoaderServiceState*>(ctx);
133     fbl::unique_fd fd(openat(state->root_dir_fd.get(), path, O_RDONLY));
134     if (fd) {
135         return VmoFromFd(std::move(fd), path, out);
136     }
137     return ZX_ERR_NOT_FOUND;
138 }
139 
PublishDataSink(void * ctx,const char * sink_name,zx_handle_t vmo)140 zx_status_t PublishDataSink(void* ctx, const char* sink_name, zx_handle_t vmo) {
141     const auto state = static_cast<LoaderServiceState*>(ctx);
142     state->data.push_back({ sink_name, zx::vmo{vmo} });
143     return ZX_OK;
144 }
145 
Finalizer(void * ctx)146 void Finalizer(void* ctx) {
147     const auto state = static_cast<LoaderServiceState*>(ctx);
148     delete state;
149 }
150 
151 const loader_service_ops_t fd_ops = {
152     .load_object = LoadObject,
153     .load_abspath = LoadAbspath,
154     .publish_data_sink = PublishDataSink,
155     .finalizer = Finalizer,
156 };
157 
158 // To avoid creating a separate service thread for each test, we have a global
159 // instance of the async loop which is shared by all tests and their loader services.
160 fbl::unique_ptr<async::Loop> loop;
161 
162 } // namespace
163 
FuchsiaRunTest(const char * argv[],const char * output_dir,const char * output_filename)164 fbl::unique_ptr<Result> FuchsiaRunTest(const char* argv[],
165                                        const char* output_dir,
166                                        const char* output_filename) {
167     // The arguments passed to fdio_spaws_etc. May be overridden.
168     const char** args = argv;
169     // calculate size of argv
170     size_t argc = 0;
171     while (argv[argc] != nullptr) {
172         argc++;
173     }
174 
175     // Values used when running the test as a component.
176     const char* component_launch_args[argc + 2];
177     fbl::String component_url;
178     fbl::String cmx_file_path;
179 
180     const char* path = argv[0];
181 
182     TestFileComponentInfo(path, &component_url, &cmx_file_path);
183     struct stat s;
184     // If we get a non empty |cmx_file_path|, check that it exists, and if
185     // present launch the test as component using generated |component_url|.
186     if (cmx_file_path != "" && stat(cmx_file_path.c_str(), &s) == 0) {
187         if (stat(kRunTestComponentPath, &s) != 0) {
188             // TODO(anmittal): Make this a error once we have a stable
189             // system and we can run all tests as components.
190             fprintf(stderr,
191                     "WARNING: Cannot find '%s', running '%s' as normal test "
192                     "binary.",
193                     kRunTestComponentPath, path);
194         } else {
195             component_launch_args[0] = kRunTestComponentPath;
196             component_launch_args[1] = component_url.c_str();
197             for (size_t i = 1; i <= argc; i++) {
198                 component_launch_args[1 + i] = argv[i];
199             }
200             args = component_launch_args;
201         }
202     }
203 
204     fbl::Vector<fdio_spawn_action_t> fdio_actions = {
205         fdio_spawn_action_t{.action = FDIO_SPAWN_ACTION_SET_NAME,
206           .name = {.data = path}},
207     };
208 
209     LoaderServiceState* state = nullptr;
210     zx_handle_t svc_handle = ZX_HANDLE_INVALID;
211     loader_service_t* loader_service = nullptr;
212     auto release_service = fbl::MakeAutoCall([&]() {
213         if (loader_service != nullptr) {
214             loader_service_release(loader_service);
215         }
216     });
217 
218     if (output_dir != nullptr) {
219         state = new LoaderServiceState();
220         // If |output_dir| is provided, set up the loader service that will be
221         // used to capture any data published.
222         state->root_dir_fd.reset(open("/", O_RDONLY | O_DIRECTORY));
223         if (!state->root_dir_fd) {
224             printf("FAILURE: Could not open root directory /\n");
225             return fbl::make_unique<Result>(path, FAILED_UNKNOWN, 0);
226         }
227 
228         if (!loop) {
229             loop.reset(new async::Loop(&kAsyncLoopConfigNoAttachToThread));
230             if (loop->StartThread("loader-service") != ZX_OK) {
231                 printf("FAILURE: cannot start message loop\n");
232                 loop.reset();
233                 return fbl::make_unique<Result>(path, FAILED_UNKNOWN, 0);
234             }
235         }
236 
237         if (loader_service_create(loop->dispatcher(), &fd_ops, state, &loader_service) != ZX_OK) {
238             printf("FAILURE: cannot create loader service\n");
239             delete state;
240             return fbl::make_unique<Result>(path, FAILED_UNKNOWN, 0);
241         }
242 
243         if (loader_service_connect(loader_service, &svc_handle) != ZX_OK) {
244             printf("FAILURE: cannot connect loader service\n");
245             return fbl::make_unique<Result>(path, FAILED_UNKNOWN, 0);
246         }
247 
248         fdio_actions.push_back(
249             fdio_spawn_action{.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
250              .h = {.id = PA_LDSVC_LOADER, .handle = svc_handle}});
251     }
252 
253     // If |output_filename| is provided, prepare the file descriptors that will
254     // be used to tee the stdout/stderr of the test into the associated file.
255     fbl::unique_fd fds[2];
256     if (output_filename != nullptr) {
257         int temp_fds[2] = {-1, -1};
258         if (pipe(temp_fds)) {
259             fprintf(stderr, "FAILURE: Failed to create pipe: %s\n",
260                     strerror(errno));
261             return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
262         }
263         fds[0].reset(temp_fds[0]);
264         fds[1].reset(temp_fds[1]);
265 
266         fdio_actions.push_back(
267             fdio_spawn_action{.action = FDIO_SPAWN_ACTION_CLONE_FD,
268              .fd = {.local_fd = fds[1].get(), .target_fd = STDOUT_FILENO}});
269         fdio_actions.push_back(
270             fdio_spawn_action{.action = FDIO_SPAWN_ACTION_TRANSFER_FD,
271              .fd = {.local_fd = fds[1].get(), .target_fd = STDERR_FILENO}});
272     }
273     zx_status_t status = ZX_OK;
274     zx::job test_job;
275     status = zx::job::create(*zx::job::default_job(), 0, &test_job);
276     if (status != ZX_OK) {
277         fprintf(stderr, "FAILURE: zx::job::create() returned %d\n", status);
278         return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
279     }
280     auto auto_call_kill_job =
281         fbl::MakeAutoCall([&test_job]() { test_job.kill(); });
282     status =
283         test_job.set_property(ZX_PROP_NAME, "run-test", sizeof("run-test"));
284     if (status != ZX_OK) {
285         fprintf(stderr, "FAILURE: set_property() returned %d\n", status);
286         return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
287     }
288 
289     fds[1].release(); // To avoid double close since fdio_spawn_etc() closes it.
290     zx::process process;
291     char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
292     status = fdio_spawn_etc(test_job.get(), FDIO_SPAWN_CLONE_ALL,
293                             args[0], args, nullptr,
294                             fdio_actions.size(), fdio_actions.get(),
295                             process.reset_and_get_address(), err_msg);
296     if (status != ZX_OK) {
297         fprintf(stderr, "FAILURE: Failed to launch %s: %d (%s): %s\n", path,
298                 status, zx_status_get_string(status), err_msg);
299         return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
300     }
301     // Tee output.
302     if (output_filename != nullptr) {
303         FILE* output_file = fopen(output_filename, "w");
304         if (output_file == nullptr) {
305             fprintf(stderr, "FAILURE: Could not open output file at %s: %s\n",
306                     output_filename, strerror(errno));
307             return fbl::make_unique<Result>(path, FAILED_DURING_IO, 0);
308         }
309         char buf[1024];
310         ssize_t bytes_read = 0;
311         while ((bytes_read = read(fds[0].get(), buf, sizeof(buf))) > 0) {
312             fwrite(buf, 1, bytes_read, output_file);
313             fwrite(buf, 1, bytes_read, stdout);
314         }
315         if (fclose(output_file)) {
316             fprintf(stderr, "FAILURE:  Could not close %s: %s", output_filename,
317                     strerror(errno));
318             return fbl::make_unique<Result>(path, FAILED_DURING_IO, 0);
319         }
320     }
321     status =
322         process.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr);
323     if (status != ZX_OK) {
324         fprintf(stderr, "FAILURE: Failed to wait for process exiting %s: %d\n",
325                 path, status);
326         return fbl::make_unique<Result>(path, FAILED_TO_WAIT, 0);
327     }
328 
329     // Read the return code.
330     zx_info_process_t proc_info;
331     status = process.get_info(ZX_INFO_PROCESS, &proc_info, sizeof(proc_info),
332                               nullptr, nullptr);
333 
334     if (status != ZX_OK) {
335         fprintf(stderr, "FAILURE: Failed to get process return code %s: %d\n",
336                 path, status);
337         return fbl::make_unique<Result>(path, FAILED_TO_RETURN_CODE, 0);
338     }
339 
340     fbl::unique_ptr<Result> result;
341     if (proc_info.return_code == 0) {
342         fprintf(stderr, "PASSED: %s passed\n", path);
343         result = fbl::make_unique<Result>(path, SUCCESS, 0);
344     } else {
345         fprintf(stderr, "FAILURE: %s exited with nonzero status: %" PRId64 "\n",
346                 path, proc_info.return_code);
347         result = fbl::make_unique<Result>(path, FAILED_NONZERO_RETURN_CODE,
348                                           proc_info.return_code);
349     }
350 
351     if (output_dir == nullptr) {
352         return result;
353     }
354 
355     // Make sure that all job processes are dead before touching any data.
356     auto_call_kill_job.call();
357 
358     fbl::unique_fd data_sink_dir_fd{open(output_dir, O_RDONLY | O_DIRECTORY)};
359     if (!data_sink_dir_fd) {
360         printf("FAILURE: Could not open output directory %s: %s\n", output_dir,
361                strerror(errno));
362         return result;
363     }
364 
365     for (auto& data : state->data) {
366         if (mkdirat(data_sink_dir_fd.get(), data.sink_name.c_str(), 0777) != 0 && errno != EEXIST) {
367             fprintf(stderr, "FAILURE: cannot mkdir \"%s\" for data-sink: %s\n",
368                     data.sink_name.c_str(), strerror(errno));
369             if (result->return_code == 0) {
370                 result->launch_status = FAILED_COLLECTING_SINK_DATA;
371             }
372             continue;
373         }
374         fbl::unique_fd sink_dir_fd{openat(data_sink_dir_fd.get(), data.sink_name.c_str(),
375                                           O_RDONLY | O_DIRECTORY)};
376         if (!sink_dir_fd) {
377             fprintf(stderr, "FAILURE: cannot open data-sink directory \"%s\": %s\n",
378                     data.sink_name.c_str(), strerror(errno));
379             if (result->return_code == 0) {
380                 result->launch_status = FAILED_COLLECTING_SINK_DATA;
381             }
382             continue;
383         }
384 
385         uint64_t size;
386         zx_status_t status = data.file_data.get_size(&size);
387         if (status != ZX_OK) {
388             fprintf(stderr, "FAILURE: Cannot get VMO size for data-sink \"%s\": %s\n",
389                     data.sink_name.c_str(), zx_status_get_string(status));
390             result->launch_status = FAILED_COLLECTING_SINK_DATA;
391             continue;
392         }
393 
394         uintptr_t mapping;
395         status = zx::vmar::root_self()->map(0, data.file_data, 0, size,
396                                             ZX_VM_PERM_READ, &mapping);
397         if (status != ZX_OK) {
398             fprintf(stderr, "FAILURE: Cannot map VMO of %" PRIu64 " for data-sink \"%s\": %s\n",
399                     size, data.sink_name.c_str(), zx_status_get_string(status));
400             if (result->return_code == 0) {
401                 result->launch_status = FAILED_COLLECTING_SINK_DATA;
402             }
403             continue;
404         }
405         auto unmap_vmar = fbl::MakeAutoCall([&]() { zx::vmar::root_self()->unmap(mapping, size); });
406 
407         zx_info_handle_basic_t info;
408         status = data.file_data.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info),
409                                          nullptr, nullptr);
410         if (status != ZX_OK) {
411             if (result->return_code == 0) {
412                 result->launch_status = FAILED_COLLECTING_SINK_DATA;
413             }
414             continue;
415         }
416         char filename[ZX_MAX_NAME_LEN];
417         snprintf(filename, sizeof(filename), "%s.%" PRIu64, data.sink_name.c_str(), info.koid);
418 
419         fbl::unique_fd fd{openat(sink_dir_fd.get(), filename, O_WRONLY | O_CREAT | O_EXCL, 0666)};
420         if (!fd) {
421             fprintf(stderr, "FAILURE: Cannot open data-sink file \"%s\": %s\n",
422                     data.sink_name.c_str(), strerror(errno));
423             if (result->return_code == 0) {
424                 result->launch_status = FAILED_COLLECTING_SINK_DATA;
425             }
426             continue;
427         }
428         // Strip any leading slashes (including a sequence of slashes) so the dump
429         // file path is a relative to directory that contains the summary file.
430         size_t i = strspn(path, "/");
431         auto dump_file = JoinPath(&path[i], JoinPath(data.sink_name, filename));
432 
433         uint8_t *buf = reinterpret_cast<uint8_t *>(mapping);
434         ssize_t count = size;
435         ssize_t len = 0;
436         while (count > 0 && (len = write(fd.get(), buf, count)) != count) {
437             if (len == -1) {
438                 fprintf(stderr, "FAILURE: Cannot write data to \"%s\": %s\n",
439                         dump_file.c_str(), strerror(errno));
440                 if (result->return_code == 0) {
441                     result->launch_status = FAILED_COLLECTING_SINK_DATA;
442                 }
443                 break;
444             }
445             count -= len;
446             buf += len;
447         }
448         if (len == -1) {
449             continue;
450         }
451 
452         char name[ZX_MAX_NAME_LEN];
453         status = data.file_data.get_property(ZX_PROP_NAME, name, sizeof(name));
454         if (status != ZX_OK) {
455             if (result->return_code == 0) {
456                 result->launch_status = FAILED_COLLECTING_SINK_DATA;
457             }
458             continue;
459         }
460         if (name[0] == '\0') {
461             snprintf(name, sizeof(name), "unnamed.%" PRIu64, info.koid);
462         }
463 
464         Result::HashTable::iterator it;
465         result->data_sinks.insert_or_find(fbl::make_unique<DataSink>(data.sink_name), &it);
466         it->files.push_back(DumpFile{name, dump_file});
467     }
468 
469     return result;
470 }
471 
472 } // namespace runtests
473