libamxp  1.4.0
Patterns C Implementation
amxp_subproc.c
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** SPDX-License-Identifier: BSD-2-Clause-Patent
4 **
5 ** SPDX-FileCopyrightText: Copyright (c) 2023 SoftAtHome
6 **
7 ** Redistribution and use in source and binary forms, with or without modification,
8 ** are permitted provided that the following conditions are met:
9 **
10 ** 1. Redistributions of source code must retain the above copyright notice,
11 ** this list of conditions and the following disclaimer.
12 **
13 ** 2. Redistributions in binary form must reproduce the above copyright notice,
14 ** this list of conditions and the following disclaimer in the documentation
15 ** and/or other materials provided with the distribution.
16 **
17 ** Subject to the terms and conditions of this license, each copyright holder
18 ** and contributor hereby grants to those receiving rights under this license
19 ** a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
20 ** (except for failure to satisfy the conditions of this license) patent license
21 ** to make, have made, use, offer to sell, sell, import, and otherwise transfer
22 ** this software, where such license applies only to those patent claims, already
23 ** acquired or hereafter acquired, licensable by such copyright holder or contributor
24 ** that are necessarily infringed by:
25 **
26 ** (a) their Contribution(s) (the licensed copyrights of copyright holders and
27 ** non-copyrightable additions of contributors, in source or binary form) alone;
28 ** or
29 **
30 ** (b) combination of their Contribution(s) with the work of authorship to which
31 ** such Contribution(s) was added by such copyright holder or contributor, if,
32 ** at the time the Contribution is added, such addition causes such combination
33 ** to be necessarily infringed. The patent license shall not apply to any other
34 ** combinations which include the Contribution.
35 **
36 ** Except as expressly stated above, no rights or licenses from any copyright
37 ** holder or contributor is granted under this license, whether expressly, by
38 ** implication, estoppel or otherwise.
39 **
40 ** DISCLAIMER
41 **
42 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
43 ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
45 ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
46 ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
47 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
48 ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
49 ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
51 ** USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52 **
53 ****************************************************************************/
54 
55 #ifndef _GNU_SOURCE
56 #define _GNU_SOURCE
57 #endif
58 
59 #include <stdlib.h>
60 #include <string.h>
61 #include <unistd.h>
62 #include <fcntl.h>
63 #include <stdarg.h>
64 #include <sys/wait.h>
65 #include <sys/prctl.h>
66 #include <sys/resource.h>
67 #include <poll.h>
68 #include <time.h>
69 
70 #include <amxc/amxc_macros.h>
71 #include <amxc/amxc.h>
72 #include <amxp/amxp.h>
73 
74 #define AMXP_SUB_PROC_NR_PIPES 3
75 
76 #define AMXP_SUBPROC_FD_PARENT 0
77 #define AMXP_SUBPROC_FD_CHILD 1
78 
79 static amxc_llist_t amxp_subprocs;
80 
81 static int amxp_subproc_child_status(amxp_subproc_t* subproc, int status) {
82  int retval = -1;
83  amxc_var_t* exit_info = NULL;
84  amxc_var_t data;
85 
86  subproc->is_running = false;
87  subproc->status = status;
88 
89  amxc_var_init(&data);
90  amxc_var_new(&exit_info);
91  amxc_var_set_type(exit_info, AMXC_VAR_ID_HTABLE);
92 
93  amxc_var_set(int64_t, &data, subproc->pid);
94  amxc_var_set_key(exit_info, "PID", &data, AMXC_VAR_FLAG_COPY);
95 
96  if(WIFEXITED(status)) {
97  amxc_var_set(int64_t, &data, WEXITSTATUS(status));
98  amxc_var_set_key(exit_info, "ExitCode", &data, AMXC_VAR_FLAG_COPY);
99  } else if(WIFSIGNALED(status)) {
100  if(WCOREDUMP(status)) {
101  amxc_var_set(bool, &data, true);
102  amxc_var_set_key(exit_info, "CoreDump", &data, AMXC_VAR_FLAG_COPY);
103  } else {
104  amxc_var_set(bool, &data, true);
105  amxc_var_set_key(exit_info, "CoreDump", &data, AMXC_VAR_FLAG_COPY);
106  }
107  amxc_var_set(bool, &data, true);
108  amxc_var_set_key(exit_info, "Signalled", &data, AMXC_VAR_FLAG_COPY);
109  amxc_var_set(int64_t, &data, WTERMSIG(status));
110  amxc_var_set_key(exit_info, "Signal", &data, AMXC_VAR_FLAG_COPY);
111  } else {
112  goto exit;
113  }
114 
115 
116  amxp_sigmngr_emit_signal(subproc->sigmngr, "stop", exit_info);
117  amxc_var_delete(&exit_info);
118 
119  retval = 0;
120 
121 exit:
122  return retval;
123 }
124 
125 static void amxp_subproc_close_fds(void) {
126  struct rlimit limit;
127  int max_files = 1024;
128 
129  int ret = getrlimit(RLIMIT_NOFILE, &limit);
130  if(ret == 0) {
131  max_files = limit.rlim_cur;
132  }
133 
134  for(int i = 3; i < max_files; i++) {
135  close(i);
136  }
137 }
138 
139 static void amxp_subproc_exec_child(amxp_subproc_t* const subproc,
140  char** argv) {
141  int fd_null = -1;
142  sigset_t sig_set;
143 
144  prctl(PR_SET_PDEATHSIG, SIGKILL);
145 
146  fd_null = open("/dev/null", O_RDWR);
147  for(int i = 0; i < AMXP_SUB_PROC_NR_PIPES; i++) {
148  if(subproc->fd[i][AMXP_SUBPROC_FD_CHILD] >= 0) {
149  if(i != subproc->fd[i][AMXP_SUBPROC_FD_CHILD]) {
150  close(i);
151  dup2(subproc->fd[i][AMXP_SUBPROC_FD_CHILD], i);
152  }
153  } else {
154  dup2(fd_null, i);
155  }
156  }
157 
159 
160  pthread_sigmask(SIG_SETMASK, NULL, &sig_set);
161  pthread_sigmask(SIG_UNBLOCK, &sig_set, NULL);
162 
163  execvp(argv[0], argv);
164 
165  _exit(EXIT_FAILURE);
166 }
167 
168 static void amxp_subproc_sigchild(UNUSED const char* const sig_name,
169  UNUSED const amxc_var_t* const data,
170  UNUSED void* const priv) {
171  int status = -1;
172  pid_t ret = -1;
173  amxp_subproc_t* subproc;
174 
175  while((ret = waitpid(-1, &status, WNOHANG)) > 0) {
176  subproc = amxp_subproc_find(ret);
177  if(subproc == NULL) {
178  continue;
179  }
180 
181  amxp_subproc_child_status(subproc, status);
182  }
183 
184  return;
185 }
186 
187 static int amxp_recalculate_timeout(struct timespec* start, int timeout_msec) {
188  int err;
189  struct timespec stop;
190  int remaining_timeout = 0;
191 
192  int msec_passed;
193  time_t sec_to_msec;
194  long nsec_to_msec;
195 
196  err = clock_gettime(CLOCK_REALTIME, &stop);
197  when_failed(err, exit);
198 
199  sec_to_msec = (( stop.tv_sec - start->tv_sec ) * ((time_t) 1000));
200  nsec_to_msec = (( stop.tv_nsec - start->tv_nsec) / ((long) 1000000));
201 
202  msec_passed = (int) sec_to_msec + (int) nsec_to_msec;
203  remaining_timeout = timeout_msec - msec_passed;
204 
205  if(remaining_timeout < 0) {
206  // this is to avoid that a timeout gets converted
207  // to wait indefinitely
208  remaining_timeout = 0;
209  }
210 
211 exit:
212  return remaining_timeout;
213 }
214 
216  int retval = -1;
217  when_null(subproc, exit);
218  when_not_null(*subproc, exit);
219 
220  *subproc = (amxp_subproc_t*) calloc(1, sizeof(amxp_subproc_t));
221  when_null(*subproc, exit);
222 
223  for(int i = 0; i < AMXP_SUB_PROC_NR_PIPES; i++) {
224  (*subproc)->fd[i][AMXP_SUBPROC_FD_PARENT] = -1;
225  (*subproc)->fd[i][AMXP_SUBPROC_FD_CHILD] = -1;
226  }
227 
228  amxp_sigmngr_new(&(*subproc)->sigmngr);
229  amxp_sigmngr_add_signal((*subproc)->sigmngr, "stop");
230 
231  amxc_llist_append(&amxp_subprocs, &(*subproc)->it);
232 
233  retval = 0;
234 
235 exit:
236  return retval;
237 }
238 
240  int retval = -1;
241  when_null(subproc, exit);
242  when_null(*subproc, exit);
243 
244  for(int i = 0; i < AMXP_SUB_PROC_NR_PIPES; i++) {
245  if((*subproc)->fd[i][AMXP_SUBPROC_FD_PARENT] >= 0) {
246  close((*subproc)->fd[i][AMXP_SUBPROC_FD_PARENT]);
247  }
248  if((*subproc)->fd[i][AMXP_SUBPROC_FD_CHILD] >= 0) {
249  close((*subproc)->fd[i][AMXP_SUBPROC_FD_CHILD]);
250  }
251  }
252 
253  amxc_llist_it_take(&(*subproc)->it);
254  amxp_sigmngr_delete(&(*subproc)->sigmngr);
255  free(*subproc);
256  *subproc = NULL;
257 
258  retval = 0;
259 exit:
260  return retval;
261 }
262 
263 int amxp_subproc_open_fd(amxp_subproc_t* subproc, int requested) {
264  int retval = -1;
265  when_null(subproc, exit);
266  when_true(requested < STDIN_FILENO ||
267  requested > STDERR_FILENO, exit)
268 
269  if(subproc->fd[requested][AMXP_SUBPROC_FD_PARENT] < 0) {
270  if(pipe2(subproc->fd[requested], O_NONBLOCK | O_CLOEXEC) < 0) {
271  return -1;
272  }
273  if(requested == STDIN_FILENO) {
274  int swap = subproc->fd[STDIN_FILENO][0];
275  subproc->fd[STDIN_FILENO][0] = subproc->fd[STDIN_FILENO][1];
276  subproc->fd[STDIN_FILENO][1] = swap;
277  }
278  retval = subproc->fd[requested][AMXP_SUBPROC_FD_PARENT];
279  } else {
280  retval = subproc->fd[requested][AMXP_SUBPROC_FD_PARENT];
281  }
282 
283 exit:
284  return retval;
285 }
286 
287 
289  char** argv) {
290  int retval = -1;
291  pid_t pid = 0;
292  sigset_t original_mask;
293  sigset_t block_mask;
294 
295  when_null(subproc, exit);
296  when_null(argv, exit);
297  when_null(*argv, exit);
298  when_true(subproc->is_running, exit);
299 
300  sigfillset(&block_mask);
301  sigprocmask(SIG_SETMASK, &block_mask, &original_mask);
302 
303  pid = fork();
304  if(pid > 0) {
305  waitpid(pid, NULL, WNOHANG);
306  sigprocmask(SIG_SETMASK, &original_mask, NULL);
307  amxp_syssig_enable(SIGCHLD, true);
308  amxp_slot_connect(NULL,
309  strsignal(SIGCHLD),
310  NULL,
312  NULL);
313  subproc->pid = pid;
314  subproc->is_running = true;
315  subproc->status = 0;
316  retval = 0;
317  goto exit;
318  } else if(pid == -1) {
319  goto exit;
320  }
321 
322  amxp_subproc_exec_child(subproc, argv);
323 
324  retval = 0;
325 
326 exit:
327  return retval;
328 }
329 
331  char* cmd,
332  ...) {
333  int retval = -1;
334  va_list ap;
335  int argc = 0;
336  char** argv = NULL;
337  when_null(subproc, exit);
338  when_null(cmd, exit);
339  when_true(subproc->is_running, exit);
340 
341  va_start(ap, cmd);
342  while(va_arg(ap, char*)) {
343  argc++;
344  }
345  va_end(ap);
346 
347  argv = (char**) calloc(argc + 2, sizeof(char*));
348  when_null(argv, exit);
349 
350  va_start(ap, cmd);
351  // By convention, the first argument is always the executable name
352  argv[0] = cmd;
353  for(int i = 1; i <= argc; i++) {
354  argv[i] = va_arg(ap, char*);
355  }
356  va_end(ap);
357 
358  retval = amxp_subproc_vstart(subproc, argv);
359  free(argv);
360 
361 exit:
362  return retval;
363 }
364 
366  amxc_array_t* cmd) {
367  int retval = -1;
368  char** argv = NULL;
369  when_null(subproc, exit);
370  when_null(cmd, exit);
371  when_true(amxc_array_is_empty(cmd), exit);
372  when_true(subproc->is_running, exit);
373 
374  argv = (char**) calloc(amxc_array_capacity(cmd) + 1, sizeof(char*));
375  when_null(argv, exit);
376 
377  for(size_t i = 0; i < amxc_array_capacity(cmd); i++) {
378  if(amxc_array_get_data_at(cmd, i) == NULL) {
379  break;
380  }
381  argv[i] = (char*) amxc_array_get_data_at(cmd, i);
382  }
383 
384  retval = amxp_subproc_vstart(subproc, argv);
385  free(argv);
386 
387 exit:
388  return retval;
389 }
390 
391 int amxp_subproc_kill(const amxp_subproc_t* const subproc, const int sig) {
392  int retval = -1;
393  when_null(subproc, exit);
394  when_true(!subproc->is_running, exit);
395 
396  retval = kill(subproc->pid, sig);
397 
398 exit:
399  return retval;
400 }
401 
403  amxp_subproc_t* subproc = NULL;
404  amxc_llist_for_each(it, (&amxp_subprocs)) {
405  subproc = amxc_llist_it_get_data(it, amxp_subproc_t, it);
406  if(subproc->pid == pid) {
407  break;
408  }
409  subproc = NULL;
410  }
411 
412  return subproc;
413 }
414 
415 pid_t amxp_subproc_get_pid(const amxp_subproc_t* const subproc) {
416  return subproc != NULL ? subproc->pid : -1;
417 }
418 
420  return subproc != NULL ? subproc->sigmngr : NULL;
421 }
422 
423 bool amxp_subproc_is_running(const amxp_subproc_t* const subproc) {
424  return subproc != NULL ? subproc->is_running : false;
425 }
426 
427 // return code: error = -1 ; success (child exited) = 0 ; timeout reached (child still running) = 1
428 int amxp_subproc_wait(amxp_subproc_t* subproc, int timeout_msec) {
429  int retval = -1;
430  int err;
431  struct pollfd pollfd;
432  struct timespec start;
433  int remaining_timeout_msec = timeout_msec;
434 
435  when_null(subproc, exit);
436 
437  pollfd.fd = amxp_syssig_get_fd();
438  pollfd.events = POLLIN;
439 
440  err = clock_gettime(CLOCK_REALTIME, &start);
441  when_failed(err, exit);
442 
443  while(amxp_subproc_is_running(subproc)) {
444  err = poll(&pollfd, 1, remaining_timeout_msec);
445  if(err == 0) {
446  retval = 1;
447  goto exit;
448  } else if(err > 0) {
450  if(!amxp_subproc_is_running(subproc)) {
451  break;
452  }
453  }
454 
455  if(remaining_timeout_msec > 0) {
456  remaining_timeout_msec = amxp_recalculate_timeout(&start, timeout_msec);
457  }
458  }
459 
460  retval = 0;
461 
462 exit:
463  return retval;
464 }
465 
466 int amxp_subproc_vstart_wait(amxp_subproc_t* subproc, int timeout_msec, char** cmd) {
467  int retval = -1;
468 
469  retval = amxp_subproc_vstart(subproc, cmd);
470  when_failed(retval, exit);
471 
472  retval = amxp_subproc_wait(subproc, timeout_msec);
473  when_failed(retval, exit);
474 
475  retval = 0;
476 exit:
477  return retval;
478 }
479 
480 int amxp_subproc_start_wait(amxp_subproc_t* subproc, int timeout_msec, char* cmd, ...) {
481  int retval = -1;
482 
483  va_list ap;
484  int argc = 0;
485  char** argv = NULL;
486 
487  when_null(subproc, exit);
488  when_null(cmd, exit);
489 
490  va_start(ap, cmd);
491  while(va_arg(ap, char*)) {
492  argc++;
493  }
494  va_end(ap);
495 
496  argv = (char**) calloc(argc + 2, sizeof(char*));
497  when_null(argv, exit);
498 
499  va_start(ap, cmd);
500  // By convention, the first argument is always the executable name
501  argv[0] = cmd;
502  for(int i = 1; i <= argc; i++) {
503  argv[i] = va_arg(ap, char*);
504  }
505  va_end(ap);
506 
507  retval = amxp_subproc_vstart_wait(subproc, timeout_msec, argv);
508  when_failed(retval, exit_free);
509 
510  retval = 0;
511 exit_free:
512  free(argv);
513 exit:
514  return retval;
515 }
516 
518  int retval = -1;
519 
520  when_null(subproc, exit);
521 
522  retval = WIFEXITED(subproc->status);
523 exit:
524  return retval;
525 }
526 
528  int retval = -1;
529 
530  when_null(subproc, exit);
531 
532  retval = WIFSIGNALED(subproc->status);
533 exit:
534  return retval;
535 }
536 
538  int retval = -1;
539 
540  when_null(subproc, exit);
541 
542  retval = WEXITSTATUS(subproc->status);
543 exit:
544  return retval;
545 }
546 
548  int retval = -1;
549 
550  when_null(subproc, exit);
551 
552  retval = WTERMSIG(subproc->status);
553 exit:
554  return retval;
555 }
static int amxp_subproc_child_status(amxp_subproc_t *subproc, int status)
Definition: amxp_subproc.c:81
static int amxp_recalculate_timeout(struct timespec *start, int timeout_msec)
Definition: amxp_subproc.c:187
static void amxp_subproc_exec_child(amxp_subproc_t *const subproc, char **argv)
Definition: amxp_subproc.c:139
static void amxp_subproc_close_fds(void)
Definition: amxp_subproc.c:125
#define AMXP_SUBPROC_FD_CHILD
Definition: amxp_subproc.c:77
#define AMXP_SUBPROC_FD_PARENT
Definition: amxp_subproc.c:76
#define AMXP_SUB_PROC_NR_PIPES
Definition: amxp_subproc.c:74
static void amxp_subproc_sigchild(UNUSED const char *const sig_name, UNUSED const amxc_var_t *const data, UNUSED void *const priv)
Definition: amxp_subproc.c:168
static amxc_llist_t amxp_subprocs
Definition: amxp_subproc.c:79
#define UNUSED
Definition: main.c:68
int amxp_sigmngr_add_signal(amxp_signal_mngr_t *const sig_mngr, const char *name)
Adds a signal to a signal manager.
Definition: amxp_signal.c:433
int amxp_sigmngr_new(amxp_signal_mngr_t **sig_mngr)
Constructor function, creates a new signal manager instance.
Definition: amxp_signal.c:330
int amxp_sigmngr_delete(amxp_signal_mngr_t **sig_mngr)
Destructor function, deletes a signal manager instance.
Definition: amxp_signal.c:349
int amxp_sigmngr_emit_signal(const amxp_signal_mngr_t *const sig_mngr, const char *name, const amxc_var_t *const data)
Emits a signal.
Definition: amxp_signal.c:514
int amxp_slot_connect(amxp_signal_mngr_t *const sig_mngr, const char *const sig_name, const char *const expression, amxp_slot_fn_t fn, void *const priv)
Connects a slot (function) to a named signal of a signal manager.
Definition: amxp_slot.c:300
amxp_subproc_t * amxp_subproc_find(const int pid)
Retrieve a amxp_subproc_t for a child process using it's process identifier.
Definition: amxp_subproc.c:402
int amxp_subproc_open_fd(amxp_subproc_t *subproc, int requested)
Opens standard file descriptor to the child process.
Definition: amxp_subproc.c:263
bool amxp_subproc_is_running(const amxp_subproc_t *const subproc)
Checks if the child process is running.
Definition: amxp_subproc.c:423
int amxp_subproc_new(amxp_subproc_t **subproc)
Constructor function, creates a new child process data structure.
Definition: amxp_subproc.c:215
int amxp_subproc_get_exitstatus(amxp_subproc_t *subproc)
Gets the exit code of the child process.
Definition: amxp_subproc.c:537
int amxp_subproc_start(amxp_subproc_t *const subproc, char *cmd,...)
Start a child process.
Definition: amxp_subproc.c:330
int amxp_subproc_kill(const amxp_subproc_t *const subproc, const int sig)
Sends a Linux signal to the child process.
Definition: amxp_subproc.c:391
int amxp_subproc_ifexited(amxp_subproc_t *subproc)
Checks if the child process terminated normally.
Definition: amxp_subproc.c:517
int amxp_subproc_start_wait(amxp_subproc_t *subproc, int timeout_msec, char *cmd,...)
Starts a child process and waits until it exits.
Definition: amxp_subproc.c:480
int amxp_subproc_wait(amxp_subproc_t *subproc, int timeout_msec)
Waits until the child process has stopped.
Definition: amxp_subproc.c:428
int amxp_subproc_vstart(amxp_subproc_t *const subproc, char **argv)
Start a child process.
Definition: amxp_subproc.c:288
int amxp_subproc_vstart_wait(amxp_subproc_t *subproc, int timeout_msec, char **cmd)
Starts a child process and waits until it exits.
Definition: amxp_subproc.c:466
int amxp_subproc_astart(amxp_subproc_t *const subproc, amxc_array_t *cmd)
Start a child process.
Definition: amxp_subproc.c:365
int amxp_subproc_ifsignaled(amxp_subproc_t *subproc)
Checks if the child process was stopped because of an uncaught Linux signal.
Definition: amxp_subproc.c:527
amxp_signal_mngr_t * amxp_subproc_get_sigmngr(const amxp_subproc_t *const subproc)
Get the Signal managers of the child process.
Definition: amxp_subproc.c:419
int amxp_subproc_get_termsig(amxp_subproc_t *subproc)
Gets the Linux signal id that caused the child process to stop.
Definition: amxp_subproc.c:547
int amxp_subproc_delete(amxp_subproc_t **subproc)
Destructor function, deletes a child process data structure.
Definition: amxp_subproc.c:239
pid_t amxp_subproc_get_pid(const amxp_subproc_t *const subproc)
Get the PID of a child process.
Definition: amxp_subproc.c:415
int amxp_syssig_get_fd(void)
Returns a file descriptor that can be used in an eventloop.
Definition: amxp_syssig.c:110
int amxp_syssig_read(void)
Reads from the file descriptor.
Definition: amxp_syssig.c:114
int amxp_syssig_enable(const int sigid, const bool enable)
Enables or disables monitoring of a system signal.
Definition: amxp_syssig.c:70
Structure containing the signal manager information.
Definition: amxp_signal.h:103
Child process information structure.
Definition: amxp_subproc.h:87
amxp_signal_mngr_t * sigmngr
Definition: amxp_subproc.h:93
int fd[3][2]
Definition: amxp_subproc.h:91
bool is_running
Definition: amxp_subproc.h:90