Skip to content

Commit eae1feb

Browse files
committed
feat: add sentry__process_spawn() (#1318)
#1318
1 parent 8012a9e commit eae1feb

File tree

8 files changed

+397
-0
lines changed

8 files changed

+397
-0
lines changed

src/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ sentry_target_sources_cwd(sentry
2222
sentry_os.c
2323
sentry_os.h
2424
sentry_path.h
25+
sentry_process.h
2526
sentry_ratelimiter.c
2627
sentry_ratelimiter.h
2728
sentry_ringbuffer.c
@@ -65,12 +66,14 @@ if(WIN32)
6566
sentry_windows_dbghelp.c
6667
sentry_windows_dbghelp.h
6768
path/sentry_path_windows.c
69+
process/sentry_process_windows.c
6870
symbolizer/sentry_symbolizer_windows.c
6971
)
7072
elseif(NX OR PROSPERO)
7173
sentry_target_sources_cwd(sentry
7274
sentry_unix_spinlock.h
7375
path/sentry_path_unix.c
76+
process/sentry_process_none.c
7477
)
7578
else()
7679
sentry_target_sources_cwd(sentry
@@ -81,6 +84,7 @@ else()
8184
sentry_unix_spinlock.h
8285
symbolizer/sentry_symbolizer_unix.c
8386
path/sentry_path_unix.c
87+
process/sentry_process_unix.c
8488
)
8589
endif()
8690

src/process/sentry_process_none.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "sentry_process.h"
2+
3+
#include "sentry_core.h"
4+
5+
void
6+
sentry__process_spawn(const sentry_path_t *UNUSED(executable),
7+
const sentry_pathchar_t *UNUSED(arg0), ...)
8+
{
9+
}

src/process/sentry_process_unix.c

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#include "sentry_process.h"
2+
3+
#include "sentry_alloc.h"
4+
#include "sentry_logger.h"
5+
#include "sentry_string.h"
6+
7+
#include <errno.h>
8+
#include <fcntl.h>
9+
#include <stdarg.h>
10+
#include <stdio.h>
11+
#include <string.h>
12+
#include <sys/types.h>
13+
#include <sys/wait.h>
14+
#include <unistd.h>
15+
16+
static char **
17+
argv_new(size_t len)
18+
{
19+
char **argv = sentry_malloc((len + 1) * sizeof(char *));
20+
if (argv) {
21+
argv[len] = NULL;
22+
}
23+
return argv;
24+
}
25+
26+
static bool
27+
argv_set(char **argv, size_t index, const char *value)
28+
{
29+
if (!argv || !value) {
30+
return false;
31+
}
32+
33+
argv[index] = sentry_malloc(strlen(value) + 1);
34+
if (!argv[index]) {
35+
return false;
36+
}
37+
strcpy(argv[index], value);
38+
return true;
39+
}
40+
41+
static char *
42+
argv_to_string(char **argv)
43+
{
44+
if (!argv) {
45+
return NULL;
46+
}
47+
48+
size_t len = 0;
49+
for (int i = 0; argv[i]; i++) {
50+
len += sentry__guarded_strlen(argv[i]) + 1;
51+
}
52+
53+
char *str = sentry_malloc(len);
54+
if (!str) {
55+
return NULL;
56+
}
57+
str[0] = '\0';
58+
59+
for (int i = 0; argv[i]; i++) {
60+
if (i > 0) {
61+
strcat(str, " ");
62+
}
63+
strcat(str, argv[i]);
64+
}
65+
return str;
66+
}
67+
68+
static void
69+
argv_free(char **argv)
70+
{
71+
if (!argv) {
72+
return;
73+
}
74+
75+
for (int i = 0; argv[i]; i++) {
76+
sentry_free(argv[i]);
77+
}
78+
sentry_free(argv);
79+
}
80+
81+
/**
82+
* Spawns a new fully detached subprocess by double-forking.
83+
*/
84+
static void
85+
spawn_process(char **argv)
86+
{
87+
pid_t pid1 = fork();
88+
if (pid1 == -1) {
89+
SENTRY_ERRORF("first fork() failed: %s", strerror(errno));
90+
return;
91+
}
92+
93+
if (pid1 == 0) {
94+
// first child process: create new session and process group to detach
95+
// from parent
96+
if (setsid() == -1) {
97+
SENTRY_ERRORF("setsid() failed: %s", strerror(errno));
98+
_exit(1);
99+
}
100+
101+
// second fork to ensure the process is not a session leader and cannot
102+
// acquire a controlling terminal
103+
pid_t pid2 = fork();
104+
if (pid2 == -1) {
105+
SENTRY_ERRORF("second fork() failed: %s", strerror(errno));
106+
_exit(1);
107+
}
108+
109+
if (pid2 == 0) {
110+
// second child process: redirect stdin/out/err to /dev/null
111+
int dev_null = open("/dev/null", O_RDWR);
112+
if (dev_null != -1) {
113+
dup2(dev_null, STDIN_FILENO);
114+
dup2(dev_null, STDOUT_FILENO);
115+
dup2(dev_null, STDERR_FILENO);
116+
if (dev_null > STDERR_FILENO) {
117+
close(dev_null);
118+
}
119+
}
120+
121+
if (strstr(argv[0], "/") != NULL) {
122+
execv(argv[0], argv);
123+
} else {
124+
execvp(argv[0], argv);
125+
}
126+
127+
SENTRY_ERRORF("execv failed: %s", strerror(errno));
128+
_exit(1);
129+
} else {
130+
// the first child exits immediately
131+
_exit(0);
132+
}
133+
} else {
134+
// parent process: wait for the first child to exit
135+
int status;
136+
if (waitpid(pid1, &status, 0) == -1) {
137+
SENTRY_ERRORF("waitpid() failed: %s", strerror(errno));
138+
} else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
139+
SENTRY_ERRORF("child process failed with status %d", status);
140+
}
141+
}
142+
}
143+
144+
void
145+
sentry__process_spawn(const sentry_path_t *executable, const char *arg0, ...)
146+
{
147+
if (!executable || !executable->path || strcmp(executable->path, "") == 0) {
148+
return;
149+
}
150+
151+
int argc = 1;
152+
#ifdef SENTRY_PLATFORM_MACOS
153+
bool is_bundle = sentry__path_ends_with(executable, ".app");
154+
if (is_bundle) {
155+
argc += 2; // /usr/bin/open -a <bundle>
156+
}
157+
#endif
158+
159+
if (arg0) {
160+
#ifdef SENTRY_PLATFORM_MACOS
161+
if (is_bundle) {
162+
argc++; // --args
163+
}
164+
#endif
165+
argc++;
166+
va_list args;
167+
va_start(args, arg0);
168+
while (va_arg(args, const char *) != NULL) {
169+
argc++;
170+
}
171+
va_end(args);
172+
}
173+
174+
int i = 0;
175+
char **argv = argv_new(argc);
176+
177+
#ifdef SENTRY_PLATFORM_MACOS
178+
if (is_bundle
179+
&& (!argv_set(argv, i++, "/usr/bin/open")
180+
|| !argv_set(argv, i++, "-a"))) {
181+
argv_free(argv);
182+
return;
183+
}
184+
#endif
185+
186+
if (!argv_set(argv, i++, executable->path)) {
187+
argv_free(argv);
188+
return;
189+
}
190+
191+
if (arg0) {
192+
#ifdef SENTRY_PLATFORM_MACOS
193+
if (is_bundle && !argv_set(argv, i++, "--args")) {
194+
argv_free(argv);
195+
return;
196+
}
197+
#endif
198+
if (!argv_set(argv, i++, arg0)) {
199+
argv_free(argv);
200+
return;
201+
}
202+
va_list args;
203+
va_start(args, arg0);
204+
const char *argn;
205+
while ((argn = va_arg(args, const char *)) != NULL) {
206+
if (!argv_set(argv, i++, argn)) {
207+
va_end(args);
208+
argv_free(argv);
209+
return;
210+
}
211+
}
212+
va_end(args);
213+
}
214+
215+
char *cli = argv_to_string(argv);
216+
SENTRY_DEBUGF("spawning %s", cli);
217+
sentry_free(cli);
218+
219+
spawn_process(argv);
220+
argv_free(argv);
221+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#include "sentry_process.h"
2+
3+
#include "sentry_alloc.h"
4+
#include "sentry_logger.h"
5+
6+
#include <stdarg.h>
7+
#include <windows.h>
8+
9+
void
10+
sentry__process_spawn(const sentry_path_t *executable, const wchar_t *arg0, ...)
11+
{
12+
if (!executable || !executable->path
13+
|| wcscmp(executable->path, L"") == 0) {
14+
return;
15+
}
16+
17+
size_t cli_len = wcslen(executable->path) + 1; // \0
18+
if (arg0) {
19+
cli_len += wcslen(arg0) + 1; // space
20+
va_list args;
21+
va_start(args, arg0);
22+
const wchar_t *argn;
23+
while ((argn = va_arg(args, const wchar_t *)) != NULL) {
24+
cli_len += wcslen(argn) + 1; // space
25+
}
26+
va_end(args);
27+
}
28+
29+
wchar_t *cli = sentry_malloc(cli_len * sizeof(wchar_t));
30+
if (!cli) {
31+
return;
32+
}
33+
wcscpy(cli, executable->path);
34+
if (arg0) {
35+
wcscat(cli, L" ");
36+
wcscat(cli, arg0);
37+
va_list args;
38+
va_start(args, arg0);
39+
const wchar_t *argn;
40+
while ((argn = va_arg(args, const wchar_t *)) != NULL) {
41+
wcscat(cli, L" ");
42+
wcscat(cli, argn);
43+
}
44+
va_end(args);
45+
}
46+
47+
SENTRY_DEBUGF("spawning %S", cli);
48+
49+
STARTUPINFOW si = { 0 };
50+
PROCESS_INFORMATION pi = { 0 };
51+
si.cb = sizeof(si);
52+
53+
BOOL rv = CreateProcessW(NULL, // lpApplicationName
54+
cli, // lpCommandLine
55+
NULL, // lpProcessAttributes
56+
NULL, // lpThreadAttributes
57+
FALSE, // bInheritHandles
58+
DETACHED_PROCESS, // dwCreationFlags
59+
NULL, // lpEnvironment
60+
NULL, // lpCurrentDirectory
61+
&si, // lpStartupInfo
62+
&pi // lpProcessInformation
63+
);
64+
65+
sentry_free(cli);
66+
67+
if (!rv) {
68+
SENTRY_ERRORF("CreateProcess failed: %lu", GetLastError());
69+
return;
70+
}
71+
72+
CloseHandle(pi.hProcess);
73+
CloseHandle(pi.hThread);
74+
}

src/sentry_process.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#ifndef SENTRY_PROCESS_H_INCLUDED
2+
#define SENTRY_PROCESS_H_INCLUDED
3+
4+
#include "sentry_boot.h"
5+
#include "sentry_path.h"
6+
7+
/**
8+
* Spawns a new detached subprocess with the given executable and variable
9+
* arguments as platform native strings terminated by a NULL.
10+
*/
11+
void sentry__process_spawn(
12+
const sentry_path_t *executable, const sentry_pathchar_t *arg0, ...);
13+
14+
#endif

tests/unit/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ add_executable(sentry_test_unit
3535
test_options.c
3636
test_os.c
3737
test_path.c
38+
test_process.c
3839
test_ratelimiter.c
3940
test_ringbuffer.c
4041
test_sampling.c

0 commit comments

Comments
 (0)