NAH 2.0.16
Native Application Host - Library API Reference
Loading...
Searching...
No Matches
nah_exec.h
Go to the documentation of this file.
1/*
2 * NAH Exec - Contract Execution for NAH
3 *
4 * This file provides process spawning to execute a LaunchContract.
5 * Platform-specific implementations for Unix and Windows.
6 *
7 * SPDX-License-Identifier: Apache-2.0
8 */
9
10#ifndef NAH_EXEC_H
11#define NAH_EXEC_H
12
13#ifdef __cplusplus
14
15#include "nah_core.h"
16
17#include <cstdlib>
18#include <string>
19#include <vector>
20
21#ifdef _WIN32
22#include <windows.h>
23#include <process.h>
24#else
25#include <unistd.h>
26#include <sys/wait.h>
27#include <cstring>
28#endif
29
30namespace nah {
31namespace exec {
32
33// ============================================================================
34// EXECUTION RESULT
35// ============================================================================
36
37struct ExecResult {
38 bool ok = false;
39 int exit_code = -1;
40 std::string error;
41};
42
43// ============================================================================
44// ENVIRONMENT BUILDING
45// ============================================================================
46
52inline std::vector<std::string> build_environment(const core::LaunchContract& contract) {
53 std::vector<std::string> env;
54
55 // Add all contract environment variables
56 for (const auto& [key, value] : contract.environment) {
57 env.push_back(key + "=" + value);
58 }
59
60 // Build library path
61 if (!contract.execution.library_paths.empty()) {
62 std::string lib_path;
63 char sep = core::get_path_separator();
64
65 for (size_t i = 0; i < contract.execution.library_paths.size(); i++) {
66 if (i > 0) lib_path += sep;
67 lib_path += contract.execution.library_paths[i];
68 }
69
70 // Check if key already exists in environment
71 std::string lib_key = contract.execution.library_path_env_key;
72 bool found = false;
73 for (auto& e : env) {
74 if (e.find(lib_key + "=") == 0) {
75 // Prepend to existing value
76 std::string existing = e.substr(lib_key.size() + 1);
77 e = lib_key + "=" + lib_path + sep + existing;
78 found = true;
79 break;
80 }
81 }
82
83 if (!found) {
84 env.push_back(lib_key + "=" + lib_path);
85 }
86 }
87
88 return env;
89}
90
91// ============================================================================
92// COMMAND LINE BUILDING
93// ============================================================================
94
98inline std::vector<std::string> build_argv(const core::LaunchContract& contract) {
99 std::vector<std::string> argv;
100 argv.push_back(contract.execution.binary);
101 for (const auto& arg : contract.execution.arguments) {
102 argv.push_back(arg);
103 }
104 return argv;
105}
106
107// ============================================================================
108// UNIX EXECUTION
109// ============================================================================
110
111#ifndef _WIN32
112
119inline ExecResult execute_unix(const core::LaunchContract& contract, bool wait_for_exit = true) {
120 ExecResult result;
121
122 auto argv_strings = build_argv(contract);
123 auto env_strings = build_environment(contract);
124
125 // Build C-style arrays
126 std::vector<char*> argv;
127 for (auto& s : argv_strings) {
128 argv.push_back(const_cast<char*>(s.c_str()));
129 }
130 argv.push_back(nullptr);
131
132 std::vector<char*> envp;
133 for (auto& s : env_strings) {
134 envp.push_back(const_cast<char*>(s.c_str()));
135 }
136 envp.push_back(nullptr);
137
138 pid_t pid = fork();
139
140 if (pid == -1) {
141 result.error = "fork failed: " + std::string(strerror(errno));
142 return result;
143 }
144
145 if (pid == 0) {
146 // Child process
147
148 // Change directory
149 if (!contract.execution.cwd.empty()) {
150 if (chdir(contract.execution.cwd.c_str()) != 0) {
151 _exit(127);
152 }
153 }
154
155 // Execute
156 execve(contract.execution.binary.c_str(), argv.data(), envp.data());
157
158 // If execve returns, it failed
159 _exit(127);
160 }
161
162 // Parent process
163 if (wait_for_exit) {
164 int status;
165 if (waitpid(pid, &status, 0) == -1) {
166 result.error = "waitpid failed: " + std::string(strerror(errno));
167 return result;
168 }
169
170 if (WIFEXITED(status)) {
171 result.exit_code = WEXITSTATUS(status);
172 result.ok = true;
173 } else if (WIFSIGNALED(status)) {
174 result.exit_code = 128 + WTERMSIG(status);
175 result.ok = true;
176 } else {
177 result.error = "process terminated abnormally";
178 }
179 } else {
180 result.ok = true;
181 result.exit_code = 0;
182 }
183
184 return result;
185}
186
192inline ExecResult exec_replace_unix(const core::LaunchContract& contract) {
193 ExecResult result;
194
195 auto argv_strings = build_argv(contract);
196 auto env_strings = build_environment(contract);
197
198 std::vector<char*> argv;
199 for (auto& s : argv_strings) {
200 argv.push_back(const_cast<char*>(s.c_str()));
201 }
202 argv.push_back(nullptr);
203
204 std::vector<char*> envp;
205 for (auto& s : env_strings) {
206 envp.push_back(const_cast<char*>(s.c_str()));
207 }
208 envp.push_back(nullptr);
209
210 // Change directory
211 if (!contract.execution.cwd.empty()) {
212 if (chdir(contract.execution.cwd.c_str()) != 0) {
213 result.error = "chdir failed: " + std::string(strerror(errno));
214 return result;
215 }
216 }
217
218 // Replace process
219 execve(contract.execution.binary.c_str(), argv.data(), envp.data());
220
221 // If we get here, execve failed
222 result.error = "execve failed: " + std::string(strerror(errno));
223 return result;
224}
225
226#endif // !_WIN32
227
228// ============================================================================
229// WINDOWS EXECUTION
230// ============================================================================
231
232#ifdef _WIN32
233
237inline std::string build_command_line(const std::vector<std::string>& argv) {
238 std::string cmd;
239 for (size_t i = 0; i < argv.size(); i++) {
240 if (i > 0) cmd += " ";
241
242 // Quote arguments containing spaces
243 bool needs_quotes = argv[i].find(' ') != std::string::npos ||
244 argv[i].find('\t') != std::string::npos;
245
246 if (needs_quotes) cmd += "\"";
247 cmd += argv[i];
248 if (needs_quotes) cmd += "\"";
249 }
250 return cmd;
251}
252
256inline std::string build_environment_block(const std::vector<std::string>& env) {
257 std::string block;
258 for (const auto& e : env) {
259 block += e;
260 block += '\0';
261 }
262 block += '\0'; // Double null terminator
263 return block;
264}
265
269inline ExecResult execute_windows(const core::LaunchContract& contract, bool wait_for_exit = true) {
270 ExecResult result;
271
272 auto argv = build_argv(contract);
273 auto env = build_environment(contract);
274
275 std::string cmd_line = build_command_line(argv);
276 std::string env_block = build_environment_block(env);
277
278 STARTUPINFOA si = {0};
279 si.cb = sizeof(si);
280
281 PROCESS_INFORMATION pi = {0};
282
283 BOOL success = CreateProcessA(
284 contract.execution.binary.c_str(),
285 const_cast<char*>(cmd_line.c_str()),
286 nullptr, // Process security attributes
287 nullptr, // Thread security attributes
288 FALSE, // Inherit handles
289 0, // Creation flags
290 const_cast<char*>(env_block.c_str()),
291 contract.execution.cwd.empty() ? nullptr : contract.execution.cwd.c_str(),
292 &si,
293 &pi
294 );
295
296 if (!success) {
297 result.error = "CreateProcess failed: " + std::to_string(GetLastError());
298 return result;
299 }
300
301 if (wait_for_exit) {
302 WaitForSingleObject(pi.hProcess, INFINITE);
303
304 DWORD exit_code;
305 if (GetExitCodeProcess(pi.hProcess, &exit_code)) {
306 result.exit_code = static_cast<int>(exit_code);
307 result.ok = true;
308 } else {
309 result.error = "GetExitCodeProcess failed";
310 }
311 } else {
312 result.ok = true;
313 result.exit_code = 0;
314 }
315
316 CloseHandle(pi.hProcess);
317 CloseHandle(pi.hThread);
318
319 return result;
320}
321
322#endif // _WIN32
323
324// ============================================================================
325// CROSS-PLATFORM API
326// ============================================================================
327
337inline ExecResult execute(const core::LaunchContract& contract, bool wait_for_exit = true) {
338#ifdef _WIN32
339 return execute_windows(contract, wait_for_exit);
340#else
341 return execute_unix(contract, wait_for_exit);
342#endif
343}
344
353inline ExecResult exec_replace(const core::LaunchContract& contract) {
354#ifdef _WIN32
355 auto result = execute_windows(contract, false);
356 if (result.ok) {
357 ExitProcess(0);
358 }
359 return result;
360#else
361 return exec_replace_unix(contract);
362#endif
363}
364
365} // namespace exec
366} // namespace nah
367
368#endif // __cplusplus
369
370#endif // NAH_EXEC_H