Skip to content

Commit 0fd4a59

Browse files
Add non-blocking execution of script
1 parent a0745db commit 0fd4a59

File tree

4 files changed

+129
-8
lines changed

4 files changed

+129
-8
lines changed

src/es_node_local.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <sys/types.h>
99
#include <netinet/in.h>
1010
#include <arpa/inet.h>
11+
#include <sys/types.h>
1112
#include "es_node.h"
1213
#include "es_status.h"
1314
#include "stun.h"
@@ -138,14 +139,21 @@ es_local_process_binding_response(es_node *node,
138139

139140
{
140141
char full_cmd[1024];
141-
int ret;
142+
pid_t pid;
142143

143144
sprintf(full_cmd, "%s bind %s %u", node->params.script,
144145
node->status.mapped_addr,
145146
(unsigned)node->status.mapped_port);
146-
ret = system(full_cmd);
147-
ring("Script '%s' executed with return code %d", node->params.script,
148-
ret);
147+
148+
pid = es_spawn_sh_noblock(full_cmd);
149+
if (pid < 0)
150+
{
151+
warn("Failed to spawn script '%s': %s", node->params.script, strerror(errno));
152+
}
153+
else
154+
{
155+
ring("Script '%s' spawned (pid %ld)", node->params.script, (long)pid);
156+
}
149157
}
150158
return ES_EOK;
151159
}

src/es_node_local_cr.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <fcntl.h>
44
#include <string.h>
55
#include <stdlib.h>
6+
#include <errno.h>
67
#include <sys/socket.h>
78
#include <sys/types.h>
89
#include <netinet/in.h>
@@ -13,24 +14,31 @@
1314
#include "es_msg.h"
1415
#include "es_bool.h"
1516
#include "debug.h"
17+
#include "helper.h"
1618

1719
es_status
1820
es_local_conn_request(es_node *node, const char *buf, uint32_t buf_len)
1921
{
2022
char full_cmd[1024];
21-
int ret;
23+
pid_t pid;
24+
25+
UNUSED(buf);
26+
UNUSED(buf_len);
2227

2328
/* FIXME: we need to parse connection request properly */
2429

2530
sprintf(full_cmd, "%s cr %s %u", node->params.script,
2631
node->status.mapped_addr,
2732
(unsigned)node->status.mapped_port);
28-
ret = system(full_cmd);
29-
ring("Script '%s' executed with return code %d", node->params.script, ret);
30-
if (ret != 0)
33+
34+
/* Fire-and-forget: do not block the request processing path */
35+
pid = es_spawn_sh_noblock(full_cmd);
36+
if (pid < 0)
3137
{
38+
warn("Failed to spawn script '%s': %s", node->params.script, strerror(errno));
3239
return ES_ESCRIPTFAIL;
3340
}
3441

42+
ring("Script '%s' spawned (pid %ld)", node->params.script, (long)pid);
3543
return ES_EOK;
3644
}

src/helper.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,61 @@
11
#pragma once
22

3+
#include <unistd.h>
4+
#include <errno.h>
5+
#include <string.h>
6+
#include <sys/types.h>
7+
#include <sys/wait.h>
8+
9+
/*
10+
* Execute a shell command asynchronously:
11+
* - does NOT block the caller
12+
* - returns immediately with child's PID (>0) on success
13+
* - returns -1 on failure and sets errno
14+
*
15+
* If `dbg`/`warn` are available (debug.h), the implementation logs failures.
16+
*
17+
* The child:
18+
* - detaches from parent's process group
19+
* - execs `/bin/sh -lc <cmd>`
20+
*
21+
* Reaping:
22+
* - child is waited for using WNOHANG once immediately
23+
* - on many systems the child will be reaped when it exits if SIGCHLD is
24+
* configured appropriately by the embedding program; otherwise it may become
25+
* a zombie until reaped elsewhere.
26+
*
27+
* If you want strict zombie-free behavior without an event loop, install a
28+
* SIGCHLD handler that loops waitpid(-1, ..., WNOHANG) in your program init.
29+
*/
30+
static inline pid_t
31+
es_spawn_sh_noblock(const char *cmd)
32+
{
33+
pid_t pid = fork();
34+
if (pid < 0)
35+
{
36+
/* fork failed */
37+
#ifdef warn
38+
warn("fork() failed: %s", strerror(errno));
39+
#endif
40+
return (pid_t)-1;
41+
}
42+
43+
if (pid == 0)
44+
{
45+
/* child */
46+
(void)setsid();
47+
execl("/bin/sh", "sh", "-lc", cmd, (char *)NULL);
48+
49+
/* exec failed */
50+
#ifdef warn
51+
warn("exec(/bin/sh -lc ...) failed: %s", strerror(errno));
52+
#endif
53+
_exit(127);
54+
}
55+
56+
(void)waitpid(pid, NULL, WNOHANG);
57+
return pid;
58+
}
359

460
#define EXIT_ON_ERROR(__msg, __rc) do { \
561
if ((__rc) != ES_EOK) { \

src/main.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include <errno.h>
88
#include <fcntl.h>
99
#include <sys/socket.h>
10+
#include <signal.h>
11+
#include <sys/wait.h>
1012
#include "stun.h"
1113
#include "debug.h"
1214

@@ -56,6 +58,37 @@ optimize_socket(int sockfd)
5658
return 0;
5759
}
5860

61+
static void
62+
es_reap_children(int signo)
63+
{
64+
(void)signo;
65+
66+
/* Reap all finished children (non-blocking). */
67+
for (;;)
68+
{
69+
int status;
70+
pid_t pid = waitpid(-1, &status, WNOHANG);
71+
if (pid <= 0)
72+
break;
73+
74+
if (WIFEXITED(status))
75+
{
76+
ring("Script process %ld exited with code %d",
77+
(long)pid, WEXITSTATUS(status));
78+
}
79+
else if (WIFSIGNALED(status))
80+
{
81+
ring("Script process %ld terminated by signal %d",
82+
(long)pid, WTERMSIG(status));
83+
}
84+
else
85+
{
86+
ring("Script process %ld finished (status=0x%x)",
87+
(long)pid, status);
88+
}
89+
}
90+
}
91+
5992
int main(int argc, const char *argv[])
6093
{
6194
es_status rc;
@@ -94,6 +127,22 @@ int main(int argc, const char *argv[])
94127
freopen("/dev/null", "w", stderr);
95128
}
96129

130+
/*
131+
* Reap child processes spawned for scripts (fire-and-forget) without blocking.
132+
* Use SA_RESTART so syscalls like poll() resume across signals.
133+
*/
134+
{
135+
struct sigaction sa;
136+
memset(&sa, 0, sizeof(sa));
137+
sa.sa_handler = es_reap_children;
138+
sigemptyset(&sa.sa_mask);
139+
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
140+
if (sigaction(SIGCHLD, &sa, NULL) != 0)
141+
{
142+
warn("sigaction(SIGCHLD) failed: %s", strerror(errno));
143+
}
144+
}
145+
97146
es_init(node);
98147
es_init_params(node, params);
99148

0 commit comments

Comments
 (0)