
Config file is written to a temp file and then it is renamed to the original config file. However, it is possible that the rename operation will be commited to storage while file data will be still in cache causing original config file to be empty or partially written in case of a system reboot without a clean shutdown. Make this less likely to occur by forcing the data to be written to the storage device before renaming the file. Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
787 lines
13 KiB
C
787 lines
13 KiB
C
/*
|
|
* OS specific functions for UNIX/POSIX systems
|
|
* Copyright (c) 2005-2009, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include <time.h>
|
|
#include <sys/wait.h>
|
|
|
|
#ifdef ANDROID
|
|
#include <sys/capability.h>
|
|
#include <sys/prctl.h>
|
|
#include <private/android_filesystem_config.h>
|
|
#endif /* ANDROID */
|
|
|
|
#include "os.h"
|
|
#include "common.h"
|
|
|
|
#ifdef WPA_TRACE
|
|
|
|
#include "wpa_debug.h"
|
|
#include "trace.h"
|
|
#include "list.h"
|
|
|
|
static struct dl_list alloc_list = DL_LIST_HEAD_INIT(alloc_list);
|
|
|
|
#define ALLOC_MAGIC 0xa84ef1b2
|
|
#define FREED_MAGIC 0x67fd487a
|
|
|
|
struct os_alloc_trace {
|
|
unsigned int magic;
|
|
struct dl_list list;
|
|
size_t len;
|
|
WPA_TRACE_INFO
|
|
} __attribute__((aligned(16)));
|
|
|
|
#endif /* WPA_TRACE */
|
|
|
|
|
|
void os_sleep(os_time_t sec, os_time_t usec)
|
|
{
|
|
if (sec)
|
|
sleep(sec);
|
|
if (usec)
|
|
usleep(usec);
|
|
}
|
|
|
|
|
|
int os_get_time(struct os_time *t)
|
|
{
|
|
int res;
|
|
struct timeval tv;
|
|
res = gettimeofday(&tv, NULL);
|
|
t->sec = tv.tv_sec;
|
|
t->usec = tv.tv_usec;
|
|
return res;
|
|
}
|
|
|
|
|
|
int os_get_reltime(struct os_reltime *t)
|
|
{
|
|
#if defined(CLOCK_BOOTTIME)
|
|
static clockid_t clock_id = CLOCK_BOOTTIME;
|
|
#elif defined(CLOCK_MONOTONIC)
|
|
static clockid_t clock_id = CLOCK_MONOTONIC;
|
|
#else
|
|
static clockid_t clock_id = CLOCK_REALTIME;
|
|
#endif
|
|
struct timespec ts;
|
|
int res;
|
|
|
|
while (1) {
|
|
res = clock_gettime(clock_id, &ts);
|
|
if (res == 0) {
|
|
t->sec = ts.tv_sec;
|
|
t->usec = ts.tv_nsec / 1000;
|
|
return 0;
|
|
}
|
|
switch (clock_id) {
|
|
#ifdef CLOCK_BOOTTIME
|
|
case CLOCK_BOOTTIME:
|
|
clock_id = CLOCK_MONOTONIC;
|
|
break;
|
|
#endif
|
|
#ifdef CLOCK_MONOTONIC
|
|
case CLOCK_MONOTONIC:
|
|
clock_id = CLOCK_REALTIME;
|
|
break;
|
|
#endif
|
|
case CLOCK_REALTIME:
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int os_mktime(int year, int month, int day, int hour, int min, int sec,
|
|
os_time_t *t)
|
|
{
|
|
struct tm tm, *tm1;
|
|
time_t t_local, t1, t2;
|
|
os_time_t tz_offset;
|
|
|
|
if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 ||
|
|
hour < 0 || hour > 23 || min < 0 || min > 59 || sec < 0 ||
|
|
sec > 60)
|
|
return -1;
|
|
|
|
memset(&tm, 0, sizeof(tm));
|
|
tm.tm_year = year - 1900;
|
|
tm.tm_mon = month - 1;
|
|
tm.tm_mday = day;
|
|
tm.tm_hour = hour;
|
|
tm.tm_min = min;
|
|
tm.tm_sec = sec;
|
|
|
|
t_local = mktime(&tm);
|
|
|
|
/* figure out offset to UTC */
|
|
tm1 = localtime(&t_local);
|
|
if (tm1) {
|
|
t1 = mktime(tm1);
|
|
tm1 = gmtime(&t_local);
|
|
if (tm1) {
|
|
t2 = mktime(tm1);
|
|
tz_offset = t2 - t1;
|
|
} else
|
|
tz_offset = 0;
|
|
} else
|
|
tz_offset = 0;
|
|
|
|
*t = (os_time_t) t_local - tz_offset;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int os_gmtime(os_time_t t, struct os_tm *tm)
|
|
{
|
|
struct tm *tm2;
|
|
time_t t2 = t;
|
|
|
|
tm2 = gmtime(&t2);
|
|
if (tm2 == NULL)
|
|
return -1;
|
|
tm->sec = tm2->tm_sec;
|
|
tm->min = tm2->tm_min;
|
|
tm->hour = tm2->tm_hour;
|
|
tm->day = tm2->tm_mday;
|
|
tm->month = tm2->tm_mon + 1;
|
|
tm->year = tm2->tm_year + 1900;
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef __APPLE__
|
|
#include <fcntl.h>
|
|
static int os_daemon(int nochdir, int noclose)
|
|
{
|
|
int devnull;
|
|
|
|
if (chdir("/") < 0)
|
|
return -1;
|
|
|
|
devnull = open("/dev/null", O_RDWR);
|
|
if (devnull < 0)
|
|
return -1;
|
|
|
|
if (dup2(devnull, STDIN_FILENO) < 0) {
|
|
close(devnull);
|
|
return -1;
|
|
}
|
|
|
|
if (dup2(devnull, STDOUT_FILENO) < 0) {
|
|
close(devnull);
|
|
return -1;
|
|
}
|
|
|
|
if (dup2(devnull, STDERR_FILENO) < 0) {
|
|
close(devnull);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else /* __APPLE__ */
|
|
#define os_daemon daemon
|
|
#endif /* __APPLE__ */
|
|
|
|
|
|
int os_daemonize(const char *pid_file)
|
|
{
|
|
#if defined(__uClinux__) || defined(__sun__)
|
|
return -1;
|
|
#else /* defined(__uClinux__) || defined(__sun__) */
|
|
if (os_daemon(0, 0)) {
|
|
perror("daemon");
|
|
return -1;
|
|
}
|
|
|
|
if (pid_file) {
|
|
FILE *f = fopen(pid_file, "w");
|
|
if (f) {
|
|
fprintf(f, "%u\n", getpid());
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
return -0;
|
|
#endif /* defined(__uClinux__) || defined(__sun__) */
|
|
}
|
|
|
|
|
|
void os_daemonize_terminate(const char *pid_file)
|
|
{
|
|
if (pid_file)
|
|
unlink(pid_file);
|
|
}
|
|
|
|
|
|
int os_get_random(unsigned char *buf, size_t len)
|
|
{
|
|
FILE *f;
|
|
size_t rc;
|
|
|
|
if (TEST_FAIL())
|
|
return -1;
|
|
|
|
f = fopen("/dev/urandom", "rb");
|
|
if (f == NULL) {
|
|
printf("Could not open /dev/urandom.\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = fread(buf, 1, len, f);
|
|
fclose(f);
|
|
|
|
return rc != len ? -1 : 0;
|
|
}
|
|
|
|
|
|
unsigned long os_random(void)
|
|
{
|
|
return random();
|
|
}
|
|
|
|
|
|
char * os_rel2abs_path(const char *rel_path)
|
|
{
|
|
char *buf = NULL, *cwd, *ret;
|
|
size_t len = 128, cwd_len, rel_len, ret_len;
|
|
int last_errno;
|
|
|
|
if (!rel_path)
|
|
return NULL;
|
|
|
|
if (rel_path[0] == '/')
|
|
return os_strdup(rel_path);
|
|
|
|
for (;;) {
|
|
buf = os_malloc(len);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
cwd = getcwd(buf, len);
|
|
if (cwd == NULL) {
|
|
last_errno = errno;
|
|
os_free(buf);
|
|
if (last_errno != ERANGE)
|
|
return NULL;
|
|
len *= 2;
|
|
if (len > 2000)
|
|
return NULL;
|
|
} else {
|
|
buf[len - 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
cwd_len = os_strlen(cwd);
|
|
rel_len = os_strlen(rel_path);
|
|
ret_len = cwd_len + 1 + rel_len + 1;
|
|
ret = os_malloc(ret_len);
|
|
if (ret) {
|
|
os_memcpy(ret, cwd, cwd_len);
|
|
ret[cwd_len] = '/';
|
|
os_memcpy(ret + cwd_len + 1, rel_path, rel_len);
|
|
ret[ret_len - 1] = '\0';
|
|
}
|
|
os_free(buf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int os_program_init(void)
|
|
{
|
|
#ifdef ANDROID
|
|
/*
|
|
* We ignore errors here since errors are normal if we
|
|
* are already running as non-root.
|
|
*/
|
|
#ifdef ANDROID_SETGROUPS_OVERRIDE
|
|
gid_t groups[] = { ANDROID_SETGROUPS_OVERRIDE };
|
|
#else /* ANDROID_SETGROUPS_OVERRIDE */
|
|
gid_t groups[] = { AID_INET, AID_WIFI, AID_KEYSTORE };
|
|
#endif /* ANDROID_SETGROUPS_OVERRIDE */
|
|
struct __user_cap_header_struct header;
|
|
struct __user_cap_data_struct cap;
|
|
|
|
setgroups(ARRAY_SIZE(groups), groups);
|
|
|
|
prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
|
|
|
|
setgid(AID_WIFI);
|
|
setuid(AID_WIFI);
|
|
|
|
header.version = _LINUX_CAPABILITY_VERSION;
|
|
header.pid = 0;
|
|
cap.effective = cap.permitted =
|
|
(1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW);
|
|
cap.inheritable = 0;
|
|
capset(&header, &cap);
|
|
#endif /* ANDROID */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void os_program_deinit(void)
|
|
{
|
|
#ifdef WPA_TRACE
|
|
struct os_alloc_trace *a;
|
|
unsigned long total = 0;
|
|
dl_list_for_each(a, &alloc_list, struct os_alloc_trace, list) {
|
|
total += a->len;
|
|
if (a->magic != ALLOC_MAGIC) {
|
|
wpa_printf(MSG_INFO, "MEMLEAK[%p]: invalid magic 0x%x "
|
|
"len %lu",
|
|
a, a->magic, (unsigned long) a->len);
|
|
continue;
|
|
}
|
|
wpa_printf(MSG_INFO, "MEMLEAK[%p]: len %lu",
|
|
a, (unsigned long) a->len);
|
|
wpa_trace_dump("memleak", a);
|
|
}
|
|
if (total)
|
|
wpa_printf(MSG_INFO, "MEMLEAK: total %lu bytes",
|
|
(unsigned long) total);
|
|
#endif /* WPA_TRACE */
|
|
}
|
|
|
|
|
|
int os_setenv(const char *name, const char *value, int overwrite)
|
|
{
|
|
return setenv(name, value, overwrite);
|
|
}
|
|
|
|
|
|
int os_unsetenv(const char *name)
|
|
{
|
|
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || \
|
|
defined(__OpenBSD__)
|
|
unsetenv(name);
|
|
return 0;
|
|
#else
|
|
return unsetenv(name);
|
|
#endif
|
|
}
|
|
|
|
|
|
char * os_readfile(const char *name, size_t *len)
|
|
{
|
|
FILE *f;
|
|
char *buf;
|
|
long pos;
|
|
|
|
f = fopen(name, "rb");
|
|
if (f == NULL)
|
|
return NULL;
|
|
|
|
if (fseek(f, 0, SEEK_END) < 0 || (pos = ftell(f)) < 0) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
*len = pos;
|
|
if (fseek(f, 0, SEEK_SET) < 0) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
buf = os_malloc(*len);
|
|
if (buf == NULL) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
if (fread(buf, 1, *len, f) != *len) {
|
|
fclose(f);
|
|
os_free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
int os_file_exists(const char *fname)
|
|
{
|
|
FILE *f = fopen(fname, "rb");
|
|
if (f == NULL)
|
|
return 0;
|
|
fclose(f);
|
|
return 1;
|
|
}
|
|
|
|
|
|
int os_fdatasync(FILE *stream)
|
|
{
|
|
if (!fflush(stream))
|
|
return fdatasync(fileno(stream));
|
|
return -1;
|
|
}
|
|
|
|
|
|
#ifndef WPA_TRACE
|
|
void * os_zalloc(size_t size)
|
|
{
|
|
return calloc(1, size);
|
|
}
|
|
#endif /* WPA_TRACE */
|
|
|
|
|
|
size_t os_strlcpy(char *dest, const char *src, size_t siz)
|
|
{
|
|
const char *s = src;
|
|
size_t left = siz;
|
|
|
|
if (left) {
|
|
/* Copy string up to the maximum size of the dest buffer */
|
|
while (--left != 0) {
|
|
if ((*dest++ = *s++) == '\0')
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (left == 0) {
|
|
/* Not enough room for the string; force NUL-termination */
|
|
if (siz != 0)
|
|
*dest = '\0';
|
|
while (*s++)
|
|
; /* determine total src string length */
|
|
}
|
|
|
|
return s - src - 1;
|
|
}
|
|
|
|
|
|
int os_memcmp_const(const void *a, const void *b, size_t len)
|
|
{
|
|
const u8 *aa = a;
|
|
const u8 *bb = b;
|
|
size_t i;
|
|
u8 res;
|
|
|
|
for (res = 0, i = 0; i < len; i++)
|
|
res |= aa[i] ^ bb[i];
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
#ifdef WPA_TRACE
|
|
|
|
#if defined(WPA_TRACE_BFD) && defined(CONFIG_TESTING_OPTIONS)
|
|
char wpa_trace_fail_func[256] = { 0 };
|
|
unsigned int wpa_trace_fail_after;
|
|
|
|
static int testing_fail_alloc(void)
|
|
{
|
|
const char *func[WPA_TRACE_LEN];
|
|
size_t i, res, len;
|
|
char *pos, *next;
|
|
int match;
|
|
|
|
if (!wpa_trace_fail_after)
|
|
return 0;
|
|
|
|
res = wpa_trace_calling_func(func, WPA_TRACE_LEN);
|
|
i = 0;
|
|
if (i < res && os_strcmp(func[i], __func__) == 0)
|
|
i++;
|
|
if (i < res && os_strcmp(func[i], "os_malloc") == 0)
|
|
i++;
|
|
if (i < res && os_strcmp(func[i], "os_zalloc") == 0)
|
|
i++;
|
|
if (i < res && os_strcmp(func[i], "os_calloc") == 0)
|
|
i++;
|
|
if (i < res && os_strcmp(func[i], "os_realloc") == 0)
|
|
i++;
|
|
if (i < res && os_strcmp(func[i], "os_realloc_array") == 0)
|
|
i++;
|
|
if (i < res && os_strcmp(func[i], "os_strdup") == 0)
|
|
i++;
|
|
|
|
pos = wpa_trace_fail_func;
|
|
|
|
match = 0;
|
|
while (i < res) {
|
|
int allow_skip = 1;
|
|
int maybe = 0;
|
|
|
|
if (*pos == '=') {
|
|
allow_skip = 0;
|
|
pos++;
|
|
} else if (*pos == '?') {
|
|
maybe = 1;
|
|
pos++;
|
|
}
|
|
next = os_strchr(pos, ';');
|
|
if (next)
|
|
len = next - pos;
|
|
else
|
|
len = os_strlen(pos);
|
|
if (os_memcmp(pos, func[i], len) != 0) {
|
|
if (maybe && next) {
|
|
pos = next + 1;
|
|
continue;
|
|
}
|
|
if (allow_skip) {
|
|
i++;
|
|
continue;
|
|
}
|
|
return 0;
|
|
}
|
|
if (!next) {
|
|
match = 1;
|
|
break;
|
|
}
|
|
pos = next + 1;
|
|
i++;
|
|
}
|
|
if (!match)
|
|
return 0;
|
|
|
|
wpa_trace_fail_after--;
|
|
if (wpa_trace_fail_after == 0) {
|
|
wpa_printf(MSG_INFO, "TESTING: fail allocation at %s",
|
|
wpa_trace_fail_func);
|
|
for (i = 0; i < res; i++)
|
|
wpa_printf(MSG_INFO, "backtrace[%d] = %s",
|
|
(int) i, func[i]);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
char wpa_trace_test_fail_func[256] = { 0 };
|
|
unsigned int wpa_trace_test_fail_after;
|
|
|
|
int testing_test_fail(void)
|
|
{
|
|
const char *func[WPA_TRACE_LEN];
|
|
size_t i, res, len;
|
|
char *pos, *next;
|
|
int match;
|
|
|
|
if (!wpa_trace_test_fail_after)
|
|
return 0;
|
|
|
|
res = wpa_trace_calling_func(func, WPA_TRACE_LEN);
|
|
i = 0;
|
|
if (i < res && os_strcmp(func[i], __func__) == 0)
|
|
i++;
|
|
|
|
pos = wpa_trace_test_fail_func;
|
|
|
|
match = 0;
|
|
while (i < res) {
|
|
int allow_skip = 1;
|
|
int maybe = 0;
|
|
|
|
if (*pos == '=') {
|
|
allow_skip = 0;
|
|
pos++;
|
|
} else if (*pos == '?') {
|
|
maybe = 1;
|
|
pos++;
|
|
}
|
|
next = os_strchr(pos, ';');
|
|
if (next)
|
|
len = next - pos;
|
|
else
|
|
len = os_strlen(pos);
|
|
if (os_memcmp(pos, func[i], len) != 0) {
|
|
if (maybe && next) {
|
|
pos = next + 1;
|
|
continue;
|
|
}
|
|
if (allow_skip) {
|
|
i++;
|
|
continue;
|
|
}
|
|
return 0;
|
|
}
|
|
if (!next) {
|
|
match = 1;
|
|
break;
|
|
}
|
|
pos = next + 1;
|
|
i++;
|
|
}
|
|
if (!match)
|
|
return 0;
|
|
|
|
wpa_trace_test_fail_after--;
|
|
if (wpa_trace_test_fail_after == 0) {
|
|
wpa_printf(MSG_INFO, "TESTING: fail at %s",
|
|
wpa_trace_test_fail_func);
|
|
for (i = 0; i < res; i++)
|
|
wpa_printf(MSG_INFO, "backtrace[%d] = %s",
|
|
(int) i, func[i]);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
static inline int testing_fail_alloc(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void * os_malloc(size_t size)
|
|
{
|
|
struct os_alloc_trace *a;
|
|
|
|
if (testing_fail_alloc())
|
|
return NULL;
|
|
|
|
a = malloc(sizeof(*a) + size);
|
|
if (a == NULL)
|
|
return NULL;
|
|
a->magic = ALLOC_MAGIC;
|
|
dl_list_add(&alloc_list, &a->list);
|
|
a->len = size;
|
|
wpa_trace_record(a);
|
|
return a + 1;
|
|
}
|
|
|
|
|
|
void * os_realloc(void *ptr, size_t size)
|
|
{
|
|
struct os_alloc_trace *a;
|
|
size_t copy_len;
|
|
void *n;
|
|
|
|
if (ptr == NULL)
|
|
return os_malloc(size);
|
|
|
|
a = (struct os_alloc_trace *) ptr - 1;
|
|
if (a->magic != ALLOC_MAGIC) {
|
|
wpa_printf(MSG_INFO, "REALLOC[%p]: invalid magic 0x%x%s",
|
|
a, a->magic,
|
|
a->magic == FREED_MAGIC ? " (already freed)" : "");
|
|
wpa_trace_show("Invalid os_realloc() call");
|
|
abort();
|
|
}
|
|
n = os_malloc(size);
|
|
if (n == NULL)
|
|
return NULL;
|
|
copy_len = a->len;
|
|
if (copy_len > size)
|
|
copy_len = size;
|
|
os_memcpy(n, a + 1, copy_len);
|
|
os_free(ptr);
|
|
return n;
|
|
}
|
|
|
|
|
|
void os_free(void *ptr)
|
|
{
|
|
struct os_alloc_trace *a;
|
|
|
|
if (ptr == NULL)
|
|
return;
|
|
a = (struct os_alloc_trace *) ptr - 1;
|
|
if (a->magic != ALLOC_MAGIC) {
|
|
wpa_printf(MSG_INFO, "FREE[%p]: invalid magic 0x%x%s",
|
|
a, a->magic,
|
|
a->magic == FREED_MAGIC ? " (already freed)" : "");
|
|
wpa_trace_show("Invalid os_free() call");
|
|
abort();
|
|
}
|
|
dl_list_del(&a->list);
|
|
a->magic = FREED_MAGIC;
|
|
|
|
wpa_trace_check_ref(ptr);
|
|
free(a);
|
|
}
|
|
|
|
|
|
void * os_zalloc(size_t size)
|
|
{
|
|
void *ptr = os_malloc(size);
|
|
if (ptr)
|
|
os_memset(ptr, 0, size);
|
|
return ptr;
|
|
}
|
|
|
|
|
|
char * os_strdup(const char *s)
|
|
{
|
|
size_t len;
|
|
char *d;
|
|
len = os_strlen(s);
|
|
d = os_malloc(len + 1);
|
|
if (d == NULL)
|
|
return NULL;
|
|
os_memcpy(d, s, len);
|
|
d[len] = '\0';
|
|
return d;
|
|
}
|
|
|
|
#endif /* WPA_TRACE */
|
|
|
|
|
|
int os_exec(const char *program, const char *arg, int wait_completion)
|
|
{
|
|
pid_t pid;
|
|
int pid_status;
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
perror("fork");
|
|
return -1;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
/* run the external command in the child process */
|
|
const int MAX_ARG = 30;
|
|
char *_program, *_arg, *pos;
|
|
char *argv[MAX_ARG + 1];
|
|
int i;
|
|
|
|
_program = os_strdup(program);
|
|
_arg = os_strdup(arg);
|
|
|
|
argv[0] = _program;
|
|
|
|
i = 1;
|
|
pos = _arg;
|
|
while (i < MAX_ARG && pos && *pos) {
|
|
while (*pos == ' ')
|
|
pos++;
|
|
if (*pos == '\0')
|
|
break;
|
|
argv[i++] = pos;
|
|
pos = os_strchr(pos, ' ');
|
|
if (pos)
|
|
*pos++ = '\0';
|
|
}
|
|
argv[i] = NULL;
|
|
|
|
execv(program, argv);
|
|
perror("execv");
|
|
os_free(_program);
|
|
os_free(_arg);
|
|
exit(0);
|
|
return -1;
|
|
}
|
|
|
|
if (wait_completion) {
|
|
/* wait for the child process to complete in the parent */
|
|
waitpid(pid, &pid_status, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|