<p>laforge <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/libosmocore/+/16619">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins Builder: Verified
  laforge: Looks good to me, approved

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Introduce helper functions for safe fork+exec of processes<br><br>In some situations, we want to execute an external shell command<br>in a non-blocking way.  Similar to 'system', but without waiting for<br>the child to complete.  We also want to close all file descriptors<br>ahead of the exec() and filter + modify the environment.<br><br>Change-Id: Ib24ac8a083db32e55402ce496a5eabd8749cc888<br>Related: OS#4332<br>---<br>M configure.ac<br>M include/Makefile.am<br>A include/osmocom/core/exec.h<br>M src/Makefile.am<br>A src/exec.c<br>M tests/Makefile.am<br>A tests/exec/exec_test.c<br>A tests/exec/exec_test.err<br>A tests/exec/exec_test.ok<br>M tests/testsuite.at<br>10 files changed, 487 insertions(+), 4 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/configure.ac b/configure.ac</span><br><span>index e45ec9f..1056a0a 100644</span><br><span>--- a/configure.ac</span><br><span>+++ b/configure.ac</span><br><span>@@ -282,7 +282,7 @@</span><br><span>        )],</span><br><span>  [embedded=$enableval], [embedded="no"])</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-AM_CONDITIONAL(ENABLE_STATS_TEST, true)</span><br><span style="color: hsl(120, 100%, 40%);">+AM_CONDITIONAL(EMBEDDED, false)</span><br><span> AM_CONDITIONAL(ENABLE_SERCOM_STUB, false)</span><br><span> </span><br><span> if test x"$embedded" = x"yes"</span><br><span>@@ -301,7 +301,7 @@</span><br><span>    AM_CONDITIONAL(ENABLE_PCSC, false)</span><br><span>   AM_CONDITIONAL(ENABLE_PSEUDOTALLOC, true)</span><br><span>    AM_CONDITIONAL(ENABLE_SERCOM_STUB, true)</span><br><span style="color: hsl(0, 100%, 40%);">-        AM_CONDITIONAL(ENABLE_STATS_TEST, false)</span><br><span style="color: hsl(120, 100%, 40%);">+      AM_CONDITIONAL(EMBEDDED, true)</span><br><span>       AC_DEFINE([USE_GNUTLS], [0])</span><br><span>         AC_DEFINE([PANIC_INFLOOP],[1],[Use infinite loop on panic rather than fprintf/abort])</span><br><span> fi</span><br><span>diff --git a/include/Makefile.am b/include/Makefile.am</span><br><span>index dc6eaa7..b341ee3 100644</span><br><span>--- a/include/Makefile.am</span><br><span>+++ b/include/Makefile.am</span><br><span>@@ -23,6 +23,7 @@</span><br><span>                        osmocom/core/crcgen.h \</span><br><span>                        osmocom/core/endian.h \</span><br><span>                        osmocom/core/defs.h \</span><br><span style="color: hsl(120, 100%, 40%);">+                       osmocom/core/exec.h \</span><br><span>                        osmocom/core/fsm.h \</span><br><span>                        osmocom/core/gsmtap.h \</span><br><span>                        osmocom/core/gsmtap_util.h \</span><br><span>diff --git a/include/osmocom/core/exec.h b/include/osmocom/core/exec.h</span><br><span>new file mode 100644</span><br><span>index 0000000..6bbd352</span><br><span>--- /dev/null</span><br><span>+++ b/include/osmocom/core/exec.h</span><br><span>@@ -0,0 +1,28 @@</span><br><span style="color: hsl(120, 100%, 40%);">+#pragma once</span><br><span style="color: hsl(120, 100%, 40%);">+/* (C) 2019 by Harald Welte <laforge@gnumonks.org></span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * All Rights Reserved</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * SPDX-License-Identifier: GPL-2.0+</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * This program is free software; you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+ * it under the terms of the GNU General Public License as published by</span><br><span style="color: hsl(120, 100%, 40%);">+ * the Free Software Foundation; either version 2 of the License, or</span><br><span style="color: hsl(120, 100%, 40%);">+ * (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+ * but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the</span><br><span style="color: hsl(120, 100%, 40%);">+ * GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * You should have received a copy of the GNU General Public License along</span><br><span style="color: hsl(120, 100%, 40%);">+ * with this program; if not, write to the Free Software Foundation, Inc.,</span><br><span style="color: hsl(120, 100%, 40%);">+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+extern const char *osmo_environment_whitelist[];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int osmo_environment_filter(char **out, size_t out_len, char **in, const char **whitelist);</span><br><span style="color: hsl(120, 100%, 40%);">+int osmo_environment_append(char **out, size_t out_len, char **in);</span><br><span style="color: hsl(120, 100%, 40%);">+int osmo_close_all_fds_above(int last_fd_to_keep);</span><br><span style="color: hsl(120, 100%, 40%);">+int osmo_system_nowait(const char *command, const char **env_whitelist, char **addl_env);</span><br><span>diff --git a/src/Makefile.am b/src/Makefile.am</span><br><span>index 9943281..eeb3f7d 100644</span><br><span>--- a/src/Makefile.am</span><br><span>+++ b/src/Makefile.am</span><br><span>@@ -27,6 +27,7 @@</span><br><span>                     tdef.c \</span><br><span>                     sockaddr_str.c \</span><br><span>                     use_count.c \</span><br><span style="color: hsl(120, 100%, 40%);">+                         exec.c \</span><br><span>                     $(NULL)</span><br><span> </span><br><span> if HAVE_SSSE3</span><br><span>diff --git a/src/exec.c b/src/exec.c</span><br><span>new file mode 100644</span><br><span>index 0000000..a9d8ce0</span><br><span>--- /dev/null</span><br><span>+++ b/src/exec.c</span><br><span>@@ -0,0 +1,238 @@</span><br><span style="color: hsl(120, 100%, 40%);">+/* (C) 2019 by Harald Welte <laforge@gnumonks.org></span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * All Rights Reserved</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * SPDX-License-Identifier: GPL-2.0+</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * This program is free software; you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+ * it under the terms of the GNU General Public License as published by</span><br><span style="color: hsl(120, 100%, 40%);">+ * the Free Software Foundation; either version 2 of the License, or</span><br><span style="color: hsl(120, 100%, 40%);">+ * (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+ * but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the</span><br><span style="color: hsl(120, 100%, 40%);">+ * GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * You should have received a copy of the GNU General Public License along</span><br><span style="color: hsl(120, 100%, 40%);">+ * with this program; if not, write to the Free Software Foundation, Inc.,</span><br><span style="color: hsl(120, 100%, 40%);">+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#include "config.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#ifndef EMBEDDED</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#include <unistd.h></span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#include <errno.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <string.h></span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#include <stdio.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <dirent.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <sys/types.h></span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#include <osmocom/core/logging.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <osmocom/core/utils.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <osmocom/core/exec.h></span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! suggested list of environment variables to pass (if they exist) to a sub-process/script */</span><br><span style="color: hsl(120, 100%, 40%);">+const char *osmo_environment_whitelist[] = {</span><br><span style="color: hsl(120, 100%, 40%);">+        "USER", "LOGNAME", "HOME",</span><br><span style="color: hsl(120, 100%, 40%);">+      "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME",</span><br><span style="color: hsl(120, 100%, 40%);">+    "PATH",</span><br><span style="color: hsl(120, 100%, 40%);">+     "PWD",</span><br><span style="color: hsl(120, 100%, 40%);">+      "SHELL",</span><br><span style="color: hsl(120, 100%, 40%);">+    "TERM",</span><br><span style="color: hsl(120, 100%, 40%);">+     "TMPDIR",</span><br><span style="color: hsl(120, 100%, 40%);">+   "LD_LIBRARY_PATH",</span><br><span style="color: hsl(120, 100%, 40%);">+  "LD_PRELOAD",</span><br><span style="color: hsl(120, 100%, 40%);">+       "POSIXLY_CORRECT",</span><br><span style="color: hsl(120, 100%, 40%);">+  "HOSTALIASES",</span><br><span style="color: hsl(120, 100%, 40%);">+      "TZ", "TZDIR",</span><br><span style="color: hsl(120, 100%, 40%);">+    "TERMCAP",</span><br><span style="color: hsl(120, 100%, 40%);">+  "COLUMNS", "LINES",</span><br><span style="color: hsl(120, 100%, 40%);">+       NULL</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static bool str_in_list(const char **list, const char *key)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    const char **ent;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   for (ent = list; *ent; ent++) {</span><br><span style="color: hsl(120, 100%, 40%);">+               if (!strcmp(*ent, key))</span><br><span style="color: hsl(120, 100%, 40%);">+                       return true;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+     return false;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! filtered a process environment by whitelist; only copying pointers, no actual strings.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  This function is useful if you'd like to generate an environment to pass exec*e()</span><br><span style="color: hsl(120, 100%, 40%);">+ *  functions.  It will create a new environment containing only those entries whose</span><br><span style="color: hsl(120, 100%, 40%);">+ *  keys (as per environment convention KEY=VALUE) are contained in the whitelist.  The</span><br><span style="color: hsl(120, 100%, 40%);">+ *  function will not copy the actual strings, but just create a new pointer array, pointing</span><br><span style="color: hsl(120, 100%, 40%);">+ *  to the same memory as the input strings.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  Constraints: Keys up to a maximum length of 255 characters are supported.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \oaram[out] out caller-allocated array of pointers for the generated output</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \param[in] out_len size of out (number of pointers)</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \param[in] in input environment (NULL-terminated list of pointers like **environ)</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \param[in] whitelist whitelist of permitted keys in environment (like **environ)</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \returns number of entries filled in 'out'; negtive on error */</span><br><span style="color: hsl(120, 100%, 40%);">+int osmo_environment_filter(char **out, size_t out_len, char **in, const char **whitelist)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    char tmp[256];</span><br><span style="color: hsl(120, 100%, 40%);">+        char **ent;</span><br><span style="color: hsl(120, 100%, 40%);">+   size_t out_used = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        /* invalid calls */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (!out || out_len == 0 || !whitelist)</span><br><span style="color: hsl(120, 100%, 40%);">+               return -EINVAL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* legal, but unusual: no input to filter should generate empty, terminated out */</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!in) {</span><br><span style="color: hsl(120, 100%, 40%);">+            out[0] = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+                return 1;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* iterate over input entries */</span><br><span style="color: hsl(120, 100%, 40%);">+      for (ent = in; *ent; ent++) {</span><br><span style="color: hsl(120, 100%, 40%);">+         char *eq = strchr(*ent, '=');</span><br><span style="color: hsl(120, 100%, 40%);">+         unsigned long eq_pos;</span><br><span style="color: hsl(120, 100%, 40%);">+         if (!eq) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    /* no '=' in string, skip it */</span><br><span style="color: hsl(120, 100%, 40%);">+                       continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+             eq_pos = eq - *ent;</span><br><span style="color: hsl(120, 100%, 40%);">+           if (eq_pos >= ARRAY_SIZE(tmp))</span><br><span style="color: hsl(120, 100%, 40%);">+                     continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             strncpy(tmp, *ent, eq_pos);</span><br><span style="color: hsl(120, 100%, 40%);">+           tmp[eq_pos] = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+           if (str_in_list(whitelist, tmp)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    if (out_used == out_len-1)</span><br><span style="color: hsl(120, 100%, 40%);">+                            break;</span><br><span style="color: hsl(120, 100%, 40%);">+                        /* append to output */</span><br><span style="color: hsl(120, 100%, 40%);">+                        out[out_used++] = *ent;</span><br><span style="color: hsl(120, 100%, 40%);">+               }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+     OSMO_ASSERT(out_used < out_len);</span><br><span style="color: hsl(120, 100%, 40%);">+   out[out_used++] = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+       return out_used;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! append one environment to another; only copying pointers, not actual strings.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  This function is useful if you'd like to append soem entries to an environment</span><br><span style="color: hsl(120, 100%, 40%);">+ *  befoer passing it to exec*e() functions.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  It will append all entries from 'in' to the environment in 'out', as long as</span><br><span style="color: hsl(120, 100%, 40%);">+ *  'out' has space (determined by 'out_len').</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  Constraints: If the same key exists in 'out' and 'in', duplicate keys are</span><br><span style="color: hsl(120, 100%, 40%);">+ *  generated.  It is a simple append, without any duplicate checks.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \oaram[out] out caller-allocated array of pointers for the generated output</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \param[in] out_len size of out (number of pointers)</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \param[in] in input environment (NULL-terminated list of pointers like **environ)</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \returns number of entries filled in 'out'; negative on error */</span><br><span style="color: hsl(120, 100%, 40%);">+int osmo_environment_append(char **out, size_t out_len, char **in)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   size_t out_used = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!out || out_len == 0)</span><br><span style="color: hsl(120, 100%, 40%);">+             return -EINVAL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* seek to end of existing output */</span><br><span style="color: hsl(120, 100%, 40%);">+  for (out_used = 0; out[out_used]; out_used++) {}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!in) {</span><br><span style="color: hsl(120, 100%, 40%);">+            if (out_used == 0)</span><br><span style="color: hsl(120, 100%, 40%);">+                    out[out_used++] = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+               return out_used;</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   for (; *in && out_used < out_len-1; in++)</span><br><span style="color: hsl(120, 100%, 40%);">+          out[out_used++] = *in;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_ASSERT(out_used < out_len);</span><br><span style="color: hsl(120, 100%, 40%);">+   out[out_used++] = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     return out_used;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Iterate over files in /proc/self/fd and close all above lst_fd_to_keep */</span><br><span style="color: hsl(120, 100%, 40%);">+int osmo_close_all_fds_above(int last_fd_to_keep)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     struct dirent *ent;</span><br><span style="color: hsl(120, 100%, 40%);">+   DIR *dir;</span><br><span style="color: hsl(120, 100%, 40%);">+     int rc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     dir = opendir("/proc/self/fd");</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!dir) {</span><br><span style="color: hsl(120, 100%, 40%);">+           LOGP(DLGLOBAL, LOGL_ERROR, "Cannot open /proc/self/fd: %s\n", strerror(errno));</span><br><span style="color: hsl(120, 100%, 40%);">+             return -ENODEV;</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   while ((ent = readdir(dir))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                int fd = atoi(ent->d_name);</span><br><span style="color: hsl(120, 100%, 40%);">+                if (fd <= last_fd_to_keep)</span><br><span style="color: hsl(120, 100%, 40%);">+                 continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             if (fd == dirfd(dir))</span><br><span style="color: hsl(120, 100%, 40%);">+                 continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             rc = close(fd);</span><br><span style="color: hsl(120, 100%, 40%);">+               if (rc)</span><br><span style="color: hsl(120, 100%, 40%);">+                       LOGP(DLGLOBAL, LOGL_ERROR, "Error closing fd=%d: %s\n", fd, strerror(errno));</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+     closedir(dir);</span><br><span style="color: hsl(120, 100%, 40%);">+        return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Seems like POSIX has no header file for this, and even glibc + __USE_GNU doesn't help */</span><br><span style="color: hsl(120, 100%, 40%);">+extern char **environ;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! call an external shell command without waiting for it.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  This mimics the behavior of system(3), with the following differences:</span><br><span style="color: hsl(120, 100%, 40%);">+ *  - it doesn't wait for completion of the child process</span><br><span style="color: hsl(120, 100%, 40%);">+ *  - it closes all non-stdio file descriptors by iterating /proc/self/fd</span><br><span style="color: hsl(120, 100%, 40%);">+ *  - it constructs a reduced environment where only whitelisted keys survive</span><br><span style="color: hsl(120, 100%, 40%);">+ *  - it (optionally) appends additional variables to the environment</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \param[in] command the shell command to be executed, see system(3)</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \param[in] env_whitelist A white-list of keys for environment variables</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \param[in] addl_env any additional environment variables to be appended</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \returns PID of generated child process; negative on error</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int osmo_system_nowait(const char *command, const char **env_whitelist, char **addl_env)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ int rc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     rc = fork();</span><br><span style="color: hsl(120, 100%, 40%);">+  if (rc == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                /* we are in the child */</span><br><span style="color: hsl(120, 100%, 40%);">+             char *new_env[1024];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                /* close all file descriptors above stdio */</span><br><span style="color: hsl(120, 100%, 40%);">+          osmo_close_all_fds_above(2);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                /* build the new environment */</span><br><span style="color: hsl(120, 100%, 40%);">+               if (env_whitelist)</span><br><span style="color: hsl(120, 100%, 40%);">+                    osmo_environment_filter(new_env, ARRAY_SIZE(new_env), environ, env_whitelist);</span><br><span style="color: hsl(120, 100%, 40%);">+                if (addl_env)</span><br><span style="color: hsl(120, 100%, 40%);">+                 osmo_environment_append(new_env, ARRAY_SIZE(new_env), addl_env);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            /* if we want to behave like system(3), we must go via the shell */</span><br><span style="color: hsl(120, 100%, 40%);">+           execle("/bin/sh", "sh", "-c", command, (char *) NULL, new_env);</span><br><span style="color: hsl(120, 100%, 40%);">+         /* only reached in case of error */</span><br><span style="color: hsl(120, 100%, 40%);">+           LOGP(DLGLOBAL, LOGL_ERROR, "Error executing command '%s' after fork: %s\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                 command, strerror(errno));</span><br><span style="color: hsl(120, 100%, 40%);">+            return -EIO;</span><br><span style="color: hsl(120, 100%, 40%);">+  } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              /* we are in the parent */</span><br><span style="color: hsl(120, 100%, 40%);">+            return rc;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#endif /* EMBEDDED */</span><br><span>diff --git a/tests/Makefile.am b/tests/Makefile.am</span><br><span>index 3a3ea37..bf7017b 100644</span><br><span>--- a/tests/Makefile.am</span><br><span>+++ b/tests/Makefile.am</span><br><span>@@ -60,8 +60,10 @@</span><br><span>  $(NULL)</span><br><span> endif</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-if ENABLE_STATS_TEST</span><br><span style="color: hsl(0, 100%, 40%);">-check_PROGRAMS += stats/stats_test</span><br><span style="color: hsl(120, 100%, 40%);">+if !EMBEDDED</span><br><span style="color: hsl(120, 100%, 40%);">+check_PROGRAMS += \</span><br><span style="color: hsl(120, 100%, 40%);">+ stats/stats_test \</span><br><span style="color: hsl(120, 100%, 40%);">+    exec/exec_test</span><br><span> endif</span><br><span> </span><br><span> if ENABLE_GB</span><br><span>@@ -259,6 +261,9 @@</span><br><span> context_context_test_SOURCES = context/context_test.c</span><br><span> context_context_test_LDADD = $(LDADD)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+exec_exec_test_SOURCES = exec/exec_test.c</span><br><span style="color: hsl(120, 100%, 40%);">+exec_exec_test_LDADD = $(LDADD)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> # The `:;' works around a Bash 3.2 bug when the output is not writeable.</span><br><span> $(srcdir)/package.m4: $(top_srcdir)/configure.ac</span><br><span>    :;{ \</span><br><span>@@ -334,6 +339,7 @@</span><br><span>       use_count/use_count_test.ok use_count/use_count_test.err \</span><br><span>           context/context_test.ok \</span><br><span>            gsm0502/gsm0502_test.ok \</span><br><span style="color: hsl(120, 100%, 40%);">+             exec/exec_test.ok exec/exec_test.err \</span><br><span>       $(NULL)</span><br><span> </span><br><span> DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c</span><br><span>diff --git a/tests/exec/exec_test.c b/tests/exec/exec_test.c</span><br><span>new file mode 100644</span><br><span>index 0000000..5f4b460</span><br><span>--- /dev/null</span><br><span>+++ b/tests/exec/exec_test.c</span><br><span>@@ -0,0 +1,155 @@</span><br><span style="color: hsl(120, 100%, 40%);">+#include <osmocom/core/utils.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <osmocom/core/exec.h></span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#include <sys/types.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <sys/socket.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <sys/stat.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <sys/wait.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <unistd.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <errno.h></span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void env_dump(char **env)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ char **ent;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for (ent = env; *ent; ent++)</span><br><span style="color: hsl(120, 100%, 40%);">+          printf("\t%s\n", *ent);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void test_env_filter(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  char *out[256];</span><br><span style="color: hsl(120, 100%, 40%);">+       char *env_in[] = {</span><br><span style="color: hsl(120, 100%, 40%);">+            "FOO=1",</span><br><span style="color: hsl(120, 100%, 40%);">+            "BAR=2",</span><br><span style="color: hsl(120, 100%, 40%);">+            "USER=mahlzeit",</span><br><span style="color: hsl(120, 100%, 40%);">+            "BAZ=3",</span><br><span style="color: hsl(120, 100%, 40%);">+            "SHELL=/bin/sh",</span><br><span style="color: hsl(120, 100%, 40%);">+            NULL</span><br><span style="color: hsl(120, 100%, 40%);">+  };</span><br><span style="color: hsl(120, 100%, 40%);">+    const char *filter[] = {</span><br><span style="color: hsl(120, 100%, 40%);">+              "SHELL",</span><br><span style="color: hsl(120, 100%, 40%);">+            "USER",</span><br><span style="color: hsl(120, 100%, 40%);">+             NULL</span><br><span style="color: hsl(120, 100%, 40%);">+  };</span><br><span style="color: hsl(120, 100%, 40%);">+    int rc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("\n==== osmo_environment_filter ====\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  printf("Input Environment:\n");</span><br><span style="color: hsl(120, 100%, 40%);">+     env_dump(env_in);</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("Input Whitelist:\n");</span><br><span style="color: hsl(120, 100%, 40%);">+       env_dump((char **) filter);</span><br><span style="color: hsl(120, 100%, 40%);">+   rc = osmo_environment_filter(out, ARRAY_SIZE(out), env_in, filter);</span><br><span style="color: hsl(120, 100%, 40%);">+   printf("Output Environment (%d):\n", rc);</span><br><span style="color: hsl(120, 100%, 40%);">+   env_dump(out);</span><br><span style="color: hsl(120, 100%, 40%);">+        OSMO_ASSERT(rc == 3);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       printf("Testing for NULL out\n");</span><br><span style="color: hsl(120, 100%, 40%);">+   rc = osmo_environment_filter(NULL, 123, env_in, filter);</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_ASSERT(rc < 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("Testing for zero-length out\n");</span><br><span style="color: hsl(120, 100%, 40%);">+    rc = osmo_environment_filter(out, 0, env_in, filter);</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_ASSERT(rc < 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("Testing for one-length out\n");</span><br><span style="color: hsl(120, 100%, 40%);">+     rc = osmo_environment_filter(out, 1, env_in, filter);</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_ASSERT(rc == 1 && out[0] == NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("Testing for no filter\n");</span><br><span style="color: hsl(120, 100%, 40%);">+  rc = osmo_environment_filter(out, ARRAY_SIZE(out), env_in, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+     OSMO_ASSERT(rc < 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("Testing for no input\n");</span><br><span style="color: hsl(120, 100%, 40%);">+   rc = osmo_environment_filter(out, ARRAY_SIZE(out), NULL, filter);</span><br><span style="color: hsl(120, 100%, 40%);">+     OSMO_ASSERT(rc == 1 && out[0] == NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+       printf("Success!\n");</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void test_env_append(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    char *out[256] = {</span><br><span style="color: hsl(120, 100%, 40%);">+            "FOO=a",</span><br><span style="color: hsl(120, 100%, 40%);">+            "BAR=b",</span><br><span style="color: hsl(120, 100%, 40%);">+            "BAZ=c",</span><br><span style="color: hsl(120, 100%, 40%);">+            NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+ };</span><br><span style="color: hsl(120, 100%, 40%);">+    char *add[] = {</span><br><span style="color: hsl(120, 100%, 40%);">+               "MAHL=zeit",</span><br><span style="color: hsl(120, 100%, 40%);">+                "GSM=global",</span><br><span style="color: hsl(120, 100%, 40%);">+               "UMTS=universal",</span><br><span style="color: hsl(120, 100%, 40%);">+           "LTE=evolved",</span><br><span style="color: hsl(120, 100%, 40%);">+              NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+ };</span><br><span style="color: hsl(120, 100%, 40%);">+    int rc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("\n==== osmo_environment_append ====\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  printf("Input Environment:\n");</span><br><span style="color: hsl(120, 100%, 40%);">+     env_dump(out);</span><br><span style="color: hsl(120, 100%, 40%);">+        printf("Input Addition:\n");</span><br><span style="color: hsl(120, 100%, 40%);">+        env_dump(add);</span><br><span style="color: hsl(120, 100%, 40%);">+        rc = osmo_environment_append(out, ARRAY_SIZE(out), add);</span><br><span style="color: hsl(120, 100%, 40%);">+      printf("Output Environment (%d)\n", rc);</span><br><span style="color: hsl(120, 100%, 40%);">+    env_dump(out);</span><br><span style="color: hsl(120, 100%, 40%);">+        OSMO_ASSERT(rc == 8);</span><br><span style="color: hsl(120, 100%, 40%);">+ printf("Success!\n");</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void test_close_fd(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      struct stat st;</span><br><span style="color: hsl(120, 100%, 40%);">+       int fds[2];</span><br><span style="color: hsl(120, 100%, 40%);">+   int rc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("\n==== osmo_close_all_fds_above ====\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* create some extra fds */</span><br><span style="color: hsl(120, 100%, 40%);">+   rc = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);</span><br><span style="color: hsl(120, 100%, 40%);">+        OSMO_ASSERT(rc == 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       rc = fstat(fds[0], &st);</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(rc == 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       osmo_close_all_fds_above(2);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        rc = fstat(fds[0], &st);</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(rc == -1 && errno == EBADF);</span><br><span style="color: hsl(120, 100%, 40%);">+      rc = fstat(fds[1], &st);</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(rc == -1 && errno == EBADF);</span><br><span style="color: hsl(120, 100%, 40%);">+      printf("Success!\n");</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void test_system_nowait(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ char *addl_env[] = {</span><br><span style="color: hsl(120, 100%, 40%);">+          "MAHLZEIT=spaet",</span><br><span style="color: hsl(120, 100%, 40%);">+           NULL</span><br><span style="color: hsl(120, 100%, 40%);">+  };</span><br><span style="color: hsl(120, 100%, 40%);">+    int rc, pid, i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("\n==== osmo_system_nowait ====\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       pid = osmo_system_nowait("env | grep MAHLZEIT 1>&2", osmo_environment_whitelist, addl_env);</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(pid > 0);</span><br><span style="color: hsl(120, 100%, 40%);">+      for (i = 0; i < 10; i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+         sleep(1);</span><br><span style="color: hsl(120, 100%, 40%);">+             rc = waitpid(pid, NULL, WNOHANG);</span><br><span style="color: hsl(120, 100%, 40%);">+             if (rc == pid) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      printf("Success!\n");</span><br><span style="color: hsl(120, 100%, 40%);">+                       return;</span><br><span style="color: hsl(120, 100%, 40%);">+               }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("ERROR: child didn't terminate within 10s\n");</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int main(int argc, char **argv)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  test_env_filter();</span><br><span style="color: hsl(120, 100%, 40%);">+    test_env_append();</span><br><span style="color: hsl(120, 100%, 40%);">+    test_close_fd();</span><br><span style="color: hsl(120, 100%, 40%);">+      test_system_nowait();</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       exit(0);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span>diff --git a/tests/exec/exec_test.err b/tests/exec/exec_test.err</span><br><span>new file mode 100644</span><br><span>index 0000000..4edc61d</span><br><span>--- /dev/null</span><br><span>+++ b/tests/exec/exec_test.err</span><br><span>@@ -0,0 +1 @@</span><br><span style="color: hsl(120, 100%, 40%);">+MAHLZEIT=spaet</span><br><span>diff --git a/tests/exec/exec_test.ok b/tests/exec/exec_test.ok</span><br><span>new file mode 100644</span><br><span>index 0000000..45a20f0</span><br><span>--- /dev/null</span><br><span>+++ b/tests/exec/exec_test.ok</span><br><span>@@ -0,0 +1,46 @@</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+==== osmo_environment_filter ====</span><br><span style="color: hsl(120, 100%, 40%);">+Input Environment:</span><br><span style="color: hsl(120, 100%, 40%);">+  FOO=1</span><br><span style="color: hsl(120, 100%, 40%);">+ BAR=2</span><br><span style="color: hsl(120, 100%, 40%);">+ USER=mahlzeit</span><br><span style="color: hsl(120, 100%, 40%);">+ BAZ=3</span><br><span style="color: hsl(120, 100%, 40%);">+ SHELL=/bin/sh</span><br><span style="color: hsl(120, 100%, 40%);">+Input Whitelist:</span><br><span style="color: hsl(120, 100%, 40%);">+       SHELL</span><br><span style="color: hsl(120, 100%, 40%);">+ USER</span><br><span style="color: hsl(120, 100%, 40%);">+Output Environment (3):</span><br><span style="color: hsl(120, 100%, 40%);">+ USER=mahlzeit</span><br><span style="color: hsl(120, 100%, 40%);">+ SHELL=/bin/sh</span><br><span style="color: hsl(120, 100%, 40%);">+Testing for NULL out</span><br><span style="color: hsl(120, 100%, 40%);">+Testing for zero-length out</span><br><span style="color: hsl(120, 100%, 40%);">+Testing for one-length out</span><br><span style="color: hsl(120, 100%, 40%);">+Testing for no filter</span><br><span style="color: hsl(120, 100%, 40%);">+Testing for no input</span><br><span style="color: hsl(120, 100%, 40%);">+Success!</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+==== osmo_environment_append ====</span><br><span style="color: hsl(120, 100%, 40%);">+Input Environment:</span><br><span style="color: hsl(120, 100%, 40%);">+  FOO=a</span><br><span style="color: hsl(120, 100%, 40%);">+ BAR=b</span><br><span style="color: hsl(120, 100%, 40%);">+ BAZ=c</span><br><span style="color: hsl(120, 100%, 40%);">+Input Addition:</span><br><span style="color: hsl(120, 100%, 40%);">+        MAHL=zeit</span><br><span style="color: hsl(120, 100%, 40%);">+     GSM=global</span><br><span style="color: hsl(120, 100%, 40%);">+    UMTS=universal</span><br><span style="color: hsl(120, 100%, 40%);">+        LTE=evolved</span><br><span style="color: hsl(120, 100%, 40%);">+Output Environment (8)</span><br><span style="color: hsl(120, 100%, 40%);">+   FOO=a</span><br><span style="color: hsl(120, 100%, 40%);">+ BAR=b</span><br><span style="color: hsl(120, 100%, 40%);">+ BAZ=c</span><br><span style="color: hsl(120, 100%, 40%);">+ MAHL=zeit</span><br><span style="color: hsl(120, 100%, 40%);">+     GSM=global</span><br><span style="color: hsl(120, 100%, 40%);">+    UMTS=universal</span><br><span style="color: hsl(120, 100%, 40%);">+        LTE=evolved</span><br><span style="color: hsl(120, 100%, 40%);">+Success!</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+==== osmo_close_all_fds_above ====</span><br><span style="color: hsl(120, 100%, 40%);">+Success!</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+==== osmo_system_nowait ====</span><br><span style="color: hsl(120, 100%, 40%);">+Success!</span><br><span>diff --git a/tests/testsuite.at b/tests/testsuite.at</span><br><span>index c231b96..cb83ab9 100644</span><br><span>--- a/tests/testsuite.at</span><br><span>+++ b/tests/testsuite.at</span><br><span>@@ -362,3 +362,10 @@</span><br><span> cat $abs_srcdir/context/context_test.ok > expout</span><br><span> AT_CHECK([$abs_top_builddir/tests/context/context_test], [0], [expout], [ignore])</span><br><span> AT_CLEANUP</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+AT_SETUP([exec])</span><br><span style="color: hsl(120, 100%, 40%);">+AT_KEYWORDS([exec])</span><br><span style="color: hsl(120, 100%, 40%);">+cat $abs_srcdir/exec/exec_test.ok > expout</span><br><span style="color: hsl(120, 100%, 40%);">+cat $abs_srcdir/exec/exec_test.err > experr</span><br><span style="color: hsl(120, 100%, 40%);">+AT_CHECK([$abs_top_builddir/tests/exec/exec_test], [0], [expout], [experr])</span><br><span style="color: hsl(120, 100%, 40%);">+AT_CLEANUP</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/libosmocore/+/16619">change 16619</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/libosmocore/+/16619"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: libosmocore </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: Ib24ac8a083db32e55402ce496a5eabd8749cc888 </div>
<div style="display:none"> Gerrit-Change-Number: 16619 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-CC: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>