diff options
author | WaLyong Cho <walyong.cho@samsung.com> | 2016-11-08 18:22:55 +0900 |
---|---|---|
committer | WaLyong Cho <walyong.cho@samsung.com> | 2016-11-08 18:23:08 +0900 |
commit | e25a89806d181e40028ac3e82abec22032b9bce5 (patch) | |
tree | 2581435c0925f0846a5f8fa8846fdd253abc6d29 | |
parent | b3d97ce44cd29478fdfe13068eaf08ef57583755 (diff) | |
parent | 4809efe44e7ecee13a1c2b19fb3114621f1e3c12 (diff) | |
download | libsystem-e25a89806d181e40028ac3e82abec22032b9bce5.tar.gz libsystem-e25a89806d181e40028ac3e82abec22032b9bce5.tar.bz2 libsystem-e25a89806d181e40028ac3e82abec22032b9bce5.zip |
release: 4.0-2
[Model] Common
[BinType] AP
[Customer] N/A
[Issue] N/A
[Request] N/A
[Occurrence Version] N/A
[Problem] release: 4.0-2
[Cause & Measure] N/A
[Checking Method] N/A
[Team] SystemFW
[Developer] WaLyong Cho
[Solution company] Samsung
[Change Type] N/A
* devel/systemfw/master:
release: 4.0-2
build: run make check with build
libsystem: glib-util: wipe out _cleanup_g_xxx_
libsystem-sd: use g_auto(), g_autoptr() or g_autofree
libsystem: test: add test for exec
libsystem: exec: kill child on timeout and add kill signal selectable api
libsystem: exec: add do_fork_exec_redirect()
gitignore: add test-driver
spec: resolve rpmlint warnings and use rpmmacros
Change-Id: I2443c80bb4ca398b3d733eb57200b9241ad72e8c
Signed-off-by: WaLyong Cho <walyong.cho@samsung.com>
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | packaging/libsystem.spec | 43 | ||||
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/libsystem-sd/systemd.c | 34 | ||||
-rw-r--r-- | src/libsystem/exec.c | 97 | ||||
-rw-r--r-- | src/libsystem/glib-util.h | 85 | ||||
-rw-r--r-- | src/libsystem/libsystem.h | 110 | ||||
-rw-r--r-- | src/test/test-exec.c | 97 |
8 files changed, 326 insertions, 152 deletions
@@ -41,4 +41,5 @@ stamp-* /debugfiles.list /debuglinks.list /debugsources.list -/documentation.list
\ No newline at end of file +/documentation.list +/test-driver
\ No newline at end of file diff --git a/packaging/libsystem.spec b/packaging/libsystem.spec index 109f442..a15de97 100644 --- a/packaging/libsystem.spec +++ b/packaging/libsystem.spec @@ -1,7 +1,7 @@ Name: libsystem Summary: System Libraries Version: 4.0 -Release: 1 +Release: 2%{?release_flags} License: Apache-2.0 Group: System/Libraries Source: %{name}-%{version}.tar.gz @@ -11,8 +11,8 @@ BuildRequires: autoconf BuildRequires: automake BuildRequires: libtool BuildRequires: pkgconfig(dbus-1) -BuildRequires: pkgconfig(glib-2.0) -BuildRequires: pkgconfig(gio-2.0) +BuildRequires: pkgconfig(glib-2.0) >= 2.44 +BuildRequires: pkgconfig(gio-2.0) >= 2.44 Requires: /bin/cp @@ -20,29 +20,32 @@ Requires(post): /sbin/ldconfig Requires(postun): /sbin/ldconfig %description -System libraries. +System utility libraries. %package devel -Summary: Development header files for System Libraries +Summary: Header files for System Libraries License: Apache-2.0 +Requires: %{name} = %{version} Requires: pkgconfig(glib-2.0) %description devel -Development headers and auxiliary files. +Development header files for System Libraries. %package -n libsystem-sd Summary: Utility libraries for systemd License: Apache-2.0 +Requires: libsystem = %{version} %description -n libsystem-sd -systemd utility libraries. +A helper utility libraries for systemd. %package -n libsystem-sd-devel -Summary: Development header files for systemd util +Summary: Header files for systemd util License: Apache-2.0 +Requires: libsystem-sd = %{version} +Requires: pkgconfig(libsystem) = %{version} Requires: pkgconfig(gio-2.0) Requires: pkgconfig(dbus-1) -Requires: pkgconfig(libsystem) %description -n libsystem-sd-devel Development header files for systemd util. @@ -60,19 +63,20 @@ export CFLAGS=$(echo $CFLAGS | sed -e 's/-Wp,-D_FORTIFY_SOURCE=2 / /g') export CFLAGS="-O0 -g $CFLAGS" %endif -./autogen.sh -%configure \ - --disable-static \ - --prefix=%{_prefix} \ +%autogen +%reconfigure make %{?_smp_mflags} %install %make_install -# Remove local archives -rm -f %{buildroot}%{_libdir}/*.la +# make sure debugsources.list exist, it used by rpm macro in %file +# section. +touch debugsources.list +%check +make check %post /sbin/ldconfig @@ -82,15 +86,17 @@ rm -f %{buildroot}%{_libdir}/*.la %post -n libsystem-sd /sbin/ldconfig -%postun -n libsystem-sd -p /sbin/ldconfig +%postun -n libsystem-sd +/sbin/ldconfig %files %defattr(-,root,root,-) -%{_libdir}/libsystem.so.* %manifest %{name}.manifest +%{_libdir}/libsystem.so.* %files devel %defattr(-,root,root,-) +%manifest %{name}.manifest %{_libdir}/libsystem.so %{_includedir}/libsystem/config-parser.h %{_includedir}/libsystem/dbus-util.h @@ -100,11 +106,12 @@ rm -f %{buildroot}%{_libdir}/*.la %files -n libsystem-sd %defattr(-,root,root,-) -%{_libdir}/libsystem-sd.so.* %manifest %{name}.manifest +%{_libdir}/libsystem-sd.so.* %files -n libsystem-sd-devel %defattr(-,root,root,-) +%manifest %{name}.manifest %{_libdir}/libsystem-sd.so %{_includedir}/libsystem-sd/systemd.h %{_libdir}/pkgconfig/libsystem-sd.pc diff --git a/src/Makefile.am b/src/Makefile.am index 61fee7c..e7df956 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -84,6 +84,15 @@ test_truncate_nl_LDADD = \ tests += test-truncate_nl # ------------------------------------------------------------------------------ +test_exec_SOURCES = \ + test/test-exec.c + +test_exec_LDADD = \ + libsystem.la + +tests += test-exec + +# ------------------------------------------------------------------------------ pkgconfiglib_DATA += \ libsystem-sd/libsystem-sd.pc diff --git a/src/libsystem-sd/systemd.c b/src/libsystem-sd/systemd.c index ec53b90..cfcd5d6 100644 --- a/src/libsystem-sd/systemd.c +++ b/src/libsystem-sd/systemd.c @@ -87,7 +87,7 @@ static int systemd_call_sync(GDBusConnection *connection, NULL, &err); else { - _cleanup_g_object_unref_ GDBusProxy *proxy = NULL; + g_autofree GDBusProxy *proxy = NULL; proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, @@ -125,8 +125,8 @@ static int systemd_call_sync(GDBusConnection *connection, } int systemd_subscribe(GDBusConnection *connection, char **err_msg) { - _cleanup_g_variant_unref_ GVariant *reply = NULL; - _cleanup_g_error_free_ GError *error = NULL; + g_autofree GVariant *reply = NULL; + g_autofree GError *error = NULL; int r; r = systemd_call_sync(connection, @@ -148,8 +148,8 @@ int systemd_subscribe(GDBusConnection *connection, char **err_msg) { } int systemd_unsubscribe(GDBusConnection *connection, char **err_msg) { - _cleanup_g_variant_unref_ GVariant *reply = NULL; - _cleanup_g_error_free_ GError *error = NULL; + g_autofree GVariant *reply = NULL; + g_autofree GError *error = NULL; int r; r = systemd_call_sync(connection, @@ -175,8 +175,8 @@ int systemd_get_unit(GDBusConnection *connection, char **unit, char **err_msg) { - _cleanup_g_variant_unref_ GVariant *reply = NULL; - _cleanup_g_error_free_ GError *error = NULL; + g_autofree GVariant *reply = NULL; + g_autofree GError *error = NULL; char *obj = NULL; int r; @@ -219,8 +219,8 @@ int systemd_control_unit(GDBusConnection *connection, char **job, char **err_msg) { - _cleanup_g_variant_unref_ GVariant *reply = NULL; - _cleanup_g_error_free_ GError *error = NULL; + g_autofree GVariant *reply = NULL; + g_autofree GError *error = NULL; char *obj = NULL; int r; @@ -347,7 +347,7 @@ static int systemd_get_property(GDBusConnection *connection, GVariant **variant, char **err_msg) { - _cleanup_g_error_free_ GError *error = NULL; + g_autofree GError *error = NULL; int r; assert(name); @@ -471,7 +471,7 @@ static int systemd_get_service_property(GDBusConnection *connection, value* result, \ char** err_msg) { \ \ - _cleanup_g_variant_unref_ GVariant *var = NULL, *inner = NULL; \ + g_autofree GVariant *var = NULL, *inner = NULL; \ int r; \ \ assert(target); \ @@ -606,7 +606,7 @@ void systemd_unit_status_list_free_full(GList *status_list) { static int systemd_parse_list_units_result(GVariant *result, GList **unit_list) { char *name, *description, *load_state, *active_state, *sub_state; char *followed, *obj_path, *job_type, *job_obj_path; - _cleanup_g_variant_iter_free_ GVariantIter *iter; + g_autofree GVariantIter *iter; unsigned int job_id; GList *list = NULL; int r; @@ -728,8 +728,8 @@ on_error: } int systemd_get_units_list(GDBusConnection *conn, GList **unit_list, char **err_msg) { - _cleanup_g_variant_unref_ GVariant *reply = NULL; - _cleanup_g_error_free_ GError *error = NULL; + g_autofree GVariant *reply = NULL; + g_autofree GError *error = NULL; int r; assert(unit_list); @@ -786,7 +786,7 @@ void systemd_unit_file_status_list_free_full(GList *status_list) { } static int systemd_parse_list_unit_files_result(GVariant *result, GList **unit_files_list) { - _cleanup_g_variant_iter_free_ GVariantIter *iter; + g_autofree GVariantIter *iter; GList *list = NULL; char *name, *status; int r; @@ -843,8 +843,8 @@ on_error: } int systemd_get_unit_files_list(GDBusConnection *conn, GList **unit_files_list, char **err_msg) { - _cleanup_g_variant_unref_ GVariant *reply = NULL; - _cleanup_g_error_free_ GError *error = NULL; + g_autofree GVariant *reply = NULL; + g_autofree GError *error = NULL; int r; assert(conn); diff --git a/src/libsystem/exec.c b/src/libsystem/exec.c index 05dc89c..5fb49f4 100644 --- a/src/libsystem/exec.c +++ b/src/libsystem/exec.c @@ -29,10 +29,52 @@ #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> +#include <signal.h> #include "libsystem.h" -int do_fork_exec(char *const argv[], char * const envp[], int64_t timeout_msec) { +static int wait_child(pid_t pid, int64_t timeout_msec, int sig) { + struct timeval start, timeout; + int status; + + if (timeout_msec < 0) + return 0; + + if (timeout_msec > 0) { + msec_to_timeval((uint64_t) timeout_msec, &timeout); + + if (gettimeofday(&start, NULL) < 0) + return -errno; + } + + for (;;) { + struct timeval current, delta; + pid_t p; + + p = waitpid(pid, &status, WNOHANG); + if (p == pid) + break; + + if (timeout_msec == 0) + continue; + + if (gettimeofday(¤t, NULL) < 0) + return -errno; + + timersub(¤t, &start, &delta); + + if (timercmp(&timeout, &delta, <)) { + (void) kill(pid, sig); + return -ETIME; + } + + usleep(100000); + } + + return WEXITSTATUS(status); +} + +int do_fork_exec_kill_redirect(char *const argv[], char * const envp[], int64_t timeout_msec, int sig, int fd, int flags) { pid_t pid; assert(argv); @@ -41,50 +83,43 @@ int do_fork_exec(char *const argv[], char * const envp[], int64_t timeout_msec) if (pid < 0) return -errno; else if (pid == 0) { + + if (fd >= 0) { + if (flags & EXEC_REDIRECT_OUTPUT) + dup2(fd, STDOUT_FILENO); + + if (flags & EXEC_REDIRECT_ERROR) + dup2(fd, STDERR_FILENO); + } + if (!envp) execv(*argv, argv); else execvpe(*argv, argv, envp); _exit(EXIT_FAILURE); - } else { - struct timeval start, timeout; - int status; - - if (timeout_msec < 0) - return 0; - - if (timeout_msec > 0) { - msec_to_timeval((uint64_t) timeout_msec, &timeout); + } - if (gettimeofday(&start, NULL) < 0) - return -errno; - } + return wait_child(pid, timeout_msec, sig); +} - for (;;) { - struct timeval current, delta; - pid_t p; +int do_fork_exec_redirect(char *const argv[], char * const envp[], int64_t timeout_msec, int fd, int flags) { - p = waitpid(pid, &status, WNOHANG); - if (p == pid) - break; + assert(argv); - if (timeout_msec == 0) - continue; + return do_fork_exec_kill_redirect(argv, envp, timeout_msec, SIGTERM, fd, flags); +} - if (gettimeofday(¤t, NULL) < 0) - return -errno; +int do_fork_exec_kill(char *const argv[], char * const envp[], int64_t timeout_msec, int sig) { - timersub(¤t, &start, &delta); + assert(argv); - if (timercmp(&timeout, &delta, <)) - return -ETIME; + return do_fork_exec_kill_redirect(argv, envp, timeout_msec, sig, -1, EXEC_REDIRECT_NONE); +} - usleep(100000); - } +int do_fork_exec(char *const argv[], char * const envp[], int64_t timeout_msec) { - return WEXITSTATUS(status); - } + assert(argv); - return 0; + return do_fork_exec_kill(argv, envp, timeout_msec, SIGTERM); } diff --git a/src/libsystem/glib-util.h b/src/libsystem/glib-util.h index b87ebdf..7d3c7ad 100644 --- a/src/libsystem/glib-util.h +++ b/src/libsystem/glib-util.h @@ -38,91 +38,6 @@ extern "C" { #endif - -/** - * gchar free function. This function has to not be called - * directly. If a gchar value is declared with #_cleanup_g_free_, this - * function is called when the value goes out of scope. - */ -static inline void __g_free(gchar **p) { - if (*p) - g_free(*p); -} - -/** - * GError free function. This function has to not be called - * directly. If a GError value is declared with - * #_cleanup_g_error_free_, this function is called when the value - * goes out of scope. - */ -static inline void __g_error_free(GError **e) { - if (*e) - g_error_free(*e); -} - -/** - * GObject free function. This function has to not be called - * directly. If a GObject value is declared with - * #_cleanup_g_object_unref_, this function is called when the value - * goes out of scope. - */ -static inline void __g_object_unref(gpointer p) { - if (p) - g_object_unref(*(gpointer *) p); -} - -/** - * GVariant free function. This function has to not be called - * directly. If a GVariant value is declared with - * #_cleanup_g_variant_unref_, this function is called when the value - * goes out of scope. - */ -static inline void __g_variant_unref(GVariant **v) { - if (*v) - g_variant_unref(*v); -} - -/** - * GVariantIter free function. This function has to not be called - * directly. If a GVariantIter value is declared with - * #_cleanup_g_variant_iter_free_, this function is called when the - * value goes out of scope. - */ -static inline void __g_variant_iter_free(GVariantIter **i) { - if (*i) - g_variant_iter_free(*i); -} - -/** - * cleanup attribute for gchar to run #__g_free() when the value is - * going out of scope. - */ -#define _cleanup_g_free_ _cleanup_(__g_free) - -/** - * cleanup attribute for GError to run #__g_error_free() when the - * value is going out of scope. - */ -#define _cleanup_g_error_free_ _cleanup_(__g_error_free) - -/** - * cleanup attribute for GObject to run #__g_object_unref() when the - * value is going out of scope. - */ -#define _cleanup_g_object_unref_ _cleanup_(__g_object_unref) - -/** - * cleanup attribute for GVariant to run #__g_variant_unref() when the - * value is going out of scope. - */ -#define _cleanup_g_variant_unref_ _cleanup_(__g_variant_unref) - -/** - * cleanup attribute for GVariantIter to run #__g_variant_iter_free() - * when the value is going out of scope. - */ -#define _cleanup_g_variant_iter_free_ _cleanup_(__g_variant_iter_free) - /** * @brief Iterate for each list nodes. * diff --git a/src/libsystem/libsystem.h b/src/libsystem/libsystem.h index 7819aa7..096b849 100644 --- a/src/libsystem/libsystem.h +++ b/src/libsystem/libsystem.h @@ -809,6 +809,116 @@ bool mnt_is_mounted(const char *fsname, const char *dir, const char *type, const */ /** + * standard output/error redirect flags + */ +enum { + /** + * Do not redirect standard output/error + */ + EXEC_REDIRECT_NONE = 0x01 << 0, + /** + * Redirect standard output only + */ + EXEC_REDIRECT_OUTPUT = 0x01 << 1, + /** + * Redirect standard error only + */ + EXEC_REDIRECT_ERROR = 0x01 << 2, + /** + * Redirect standard output and error all + */ + EXEC_REDIRECT_ALL = (EXEC_REDIRECT_OUTPUT | EXEC_REDIRECT_ERROR), +}; + +/** + * @brief Traditional fork() and exec() helper. If child is not + * deactivated within given \p timeout_msec then kill it with given + * signal. And additionally redirect child process standard output or + * standard error to given fd. + * + * @param argv array of pointers to null-terminated strings that + * represent the argument list available to the new program. The first + * argument should point to the filename associated with the file + * being executed. The array of pointers must be terminated by a NULL + * pointer. + * @param envp specify the environment of the executed program via the + * argument envp. The envp argument is an array of pointers to + * null-terminated strings and must be terminated by a NULL pointer. + * @param timeout_msec timeout millisecond to prevent infinite + * waiting. If negative is given, the parent will not wait the + * child. In other word, the parent will return immediately. If 0 is + * given, parent will wait the child infinitly. And if positive value + * is given parent will wait given milliseconds and expired return + * -1. If the child is exit within the tiemout millisecond return with + * child exit code. + * @param sig signal to kill the child on timeout. + * @param fd file descriptor to redirect child standard output or + * error. + * @param flags redirect flag. This flags is able to include + * EXEC_REDIRECT_OUTPUT or EXEC_REDIRECT_ERROR. + * + * @return exit code of child. It is fully depend on the child + * process. If the child exit with 1 then this function also return 1. + * Negative errno on error. -ETIME on timer expired. + */ +int do_fork_exec_kill_redirect(char *const argv[], char * const envp[], int64_t timeout_msec, int sig, int fd, int flags); + +/** + * @brief Traditional fork() and exec() helper. And additionally + * redirect child process standard output or standard error to given fd. + * + * @param argv array of pointers to null-terminated strings that + * represent the argument list available to the new program. The first + * argument should point to the filename associated with the file + * being executed. The array of pointers must be terminated by a NULL pointer. + * @param envp specify the environment of the executed program via the + * argument envp. The envp argument is an array of pointers to + * null-terminated strings and must be terminated by a NULL pointer. + * @param timeout_msec timeout millisecond to prevent infinite + * waiting. If negative is given, the parent will not wait the + * child. In other word, the parent will return immediately. If 0 is + * given, parent will wait the child infinitly. And if positive value + * is given parent will wait given milliseconds and expired return + * -1. If the child is exit within the tiemout millisecond return with + * child exit code. + * @param fd file descriptor to redirect child standard output or error. + * @param flags redirect flag. This flags is able to include + * EXEC_REDIRECT_OUTPUT or EXEC_REDIRECT_ERROR. + * + * @return exit code of child. It is fully depend on the child + * process. If the child exit with 1 then this function also return 1. + * Negative errno on error. -ETIME on timer expired. + */ +int do_fork_exec_redirect(char *const argv[], char * const envp[], int64_t timeout_msec, int fd, int flags); + +/** + * @brief Traditional fork() and exec() helper. If child is not + * deactivated within given \p timeout_msec then kill it with given + * signal. + * + * @param argv array of pointers to null-terminated strings that + * represent the argument list available to the new program. The first + * argument should point to the filename associated with the file + * being executed. The array of pointers must be terminated by a NULL pointer. + * @param envp specify the environment of the executed program via the + * argument envp. The envp argument is an array of pointers to + * null-terminated strings and must be terminated by a NULL pointer. + * @param timeout_msec timeout millisecond to prevent infinite + * waiting. If negative is given, the parent will not wait the + * child. In other word, the parent will return immediately. If 0 is + * given, parent will wait the child infinitly. And if positive value + * is given parent will wait given milliseconds and expired return + * -1. If the child is exit within the tiemout millisecond return with + * child exit code. + * @param sig signal to kill the child on timeout. + * + * @return exit code of child. It is fully depend on the child + * process. If the child exit with 1 then this function also return 1. + * Negative errno on error. -ETIME on timer expired. + */ +int do_fork_exec_kill(char *const argv[], char * const envp[], int64_t timeout_msec, int sig); + +/** * @brief Traditional fork() and exec() helper. * * @param argv array of pointers to null-terminated strings that diff --git a/src/test/test-exec.c b/src/test/test-exec.c new file mode 100644 index 0000000..c5513cc --- /dev/null +++ b/src/test/test-exec.c @@ -0,0 +1,97 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/* + * libsystem + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <assert.h> +#include <errno.h> + +#include "libsystem/libsystem.h" + +#define TEST_EXEC_FILE "/tmp/test-exec" + +static void test_do_sleep(int argc, char *argv[]) { + + assert(argc == 2); + + sleep(atoi(argv[1])); + + exit(EXIT_SUCCESS); +} + +static void test_do_fork_exec(int argc, char *argv[]) { + char *test_argv[3] = { NULL, NULL, NULL }; + + test_argv[0] = argv[0]; + test_argv[1] = "1"; + + assert(do_fork_exec(test_argv, NULL, 500) == -ETIME); + assert(do_fork_exec(test_argv, NULL, 1500) == 0); +} + +static void test_do_write_file(int argc, char *argv[]) { + char *test1, *test2; + + assert(argc == 3); + + test1 = getenv("TEST1"); + test2 = getenv("TEST2"); + + fprintf(stdout, "%s", argv[1]); + fprintf(stdout, "%s", argv[2]); + fprintf(stdout, "%s", test1); + fprintf(stdout, "%s", test2); + + exit(EXIT_SUCCESS); +} + +static void test_do_fork_exec_redirect(int argc, char *argv[]) { + char *test_argv[4] = { NULL, "foo", "bar", NULL }; + char *test_envp[] = { "TEST1=7", "TEST2=hello", NULL }; + _cleanup_close_ int fd = -1; + _cleanup_free_ char *buf = NULL; + + fd = creat(TEST_EXEC_FILE, 0644); + /* Skip if file is not able to be opened. */ + if (fd < 0) { + fprintf(stderr, "Failed to open '" TEST_EXEC_FILE "': %m, skipping\n"); + return; + } + + test_argv[0] = argv[0]; + + assert(do_fork_exec_redirect(test_argv, test_envp, 0, fd, EXEC_REDIRECT_ALL) == 0); + assert(read_one_line_from_path(TEST_EXEC_FILE, &buf) == 0); + assert(strneq(buf, "foobar7hello", 12)); +} + +int main(int argc, char *argv[]) { + if (argc == 2) + test_do_sleep(argc, argv); + else if (argc == 3) + test_do_write_file(argc, argv); + + test_do_fork_exec(argc, argv); + test_do_fork_exec_redirect(argc, argv); + + return 0; +} |