Serenity Operating System
1/*
2 * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "Command.h"
8#include <AK/Format.h>
9#include <AK/ScopeGuard.h>
10#include <LibCore/DeprecatedFile.h>
11#include <fcntl.h>
12#include <stdio.h>
13#include <sys/wait.h>
14#include <unistd.h>
15
16namespace Core {
17
18// Only supported in serenity mode because we use `posix_spawn_file_actions_addchdir`
19#ifdef AK_OS_SERENITY
20
21ErrorOr<CommandResult> command(DeprecatedString const& command_string, Optional<LexicalPath> chdir)
22{
23 auto parts = command_string.split(' ');
24 if (parts.is_empty())
25 return Error::from_string_literal("empty command");
26 auto program = parts[0];
27 parts.remove(0);
28 return command(program, parts, chdir);
29}
30
31ErrorOr<CommandResult> command(DeprecatedString const& program, Vector<DeprecatedString> const& arguments, Optional<LexicalPath> chdir)
32{
33 int stdout_pipe[2] = {};
34 int stderr_pipe[2] = {};
35 if (pipe2(stdout_pipe, O_CLOEXEC)) {
36 return Error::from_errno(errno);
37 }
38 if (pipe2(stderr_pipe, O_CLOEXEC)) {
39 perror("pipe2");
40 return Error::from_errno(errno);
41 }
42
43 auto close_pipes = ScopeGuard([stderr_pipe, stdout_pipe] {
44 // The write-ends of these pipes are closed manually
45 close(stdout_pipe[0]);
46 close(stderr_pipe[0]);
47 });
48
49 Vector<char const*> parts = { program.characters() };
50 for (auto const& part : arguments) {
51 parts.append(part.characters());
52 }
53 parts.append(nullptr);
54
55 char const** argv = parts.data();
56
57 posix_spawn_file_actions_t action;
58 posix_spawn_file_actions_init(&action);
59 if (chdir.has_value()) {
60 posix_spawn_file_actions_addchdir(&action, chdir.value().string().characters());
61 }
62 posix_spawn_file_actions_adddup2(&action, stdout_pipe[1], STDOUT_FILENO);
63 posix_spawn_file_actions_adddup2(&action, stderr_pipe[1], STDERR_FILENO);
64
65 pid_t pid;
66 if ((errno = posix_spawnp(&pid, program.characters(), &action, nullptr, const_cast<char**>(argv), environ))) {
67 perror("posix_spawn");
68 VERIFY_NOT_REACHED();
69 }
70
71 // close the write-ends so reading wouldn't block
72 close(stdout_pipe[1]);
73 close(stderr_pipe[1]);
74
75 auto read_all_from_pipe = [](int pipe[2]) {
76 auto result_file = Core::DeprecatedFile::construct();
77 if (!result_file->open(pipe[0], Core::OpenMode::ReadOnly, Core::DeprecatedFile::ShouldCloseFileDescriptor::Yes)) {
78 perror("open");
79 VERIFY_NOT_REACHED();
80 }
81 return DeprecatedString::copy(result_file->read_all());
82 };
83 auto output = read_all_from_pipe(stdout_pipe);
84 auto error = read_all_from_pipe(stderr_pipe);
85
86 int wstatus { 0 };
87 waitpid(pid, &wstatus, 0);
88 posix_spawn_file_actions_destroy(&action);
89 int exit_code = WEXITSTATUS(wstatus);
90
91 if (exit_code != 0) {
92# ifdef DBG_FAILED_COMMANDS
93 dbgln("command failed. stderr: {}", );
94# endif
95 }
96
97 return CommandResult { WEXITSTATUS(wstatus), output, error };
98}
99
100#endif
101
102}