aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2024-05-05 10:24:40 +0200
committerMattias Andrée <maandree@kth.se>2024-05-05 10:24:40 +0200
commit3dfd6c11ed5599ab8baf4a6114f4ccb34150de6e (patch)
treeb7717583b99f29028c85c3423cf43b104dfa8943
downloadlibexec-3dfd6c11ed5599ab8baf4a6114f4ccb34150de6e.tar.gz
libexec-3dfd6c11ed5599ab8baf4a6114f4ccb34150de6e.tar.bz2
libexec-3dfd6c11ed5599ab8baf4a6114f4ccb34150de6e.tar.xz
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--.gitignore16
-rw-r--r--LICENSE15
-rw-r--r--Makefile177
-rw-r--r--README14
-rw-r--r--TODO13
-rw-r--r--common.h271
-rw-r--r--config.mk8
-rw-r--r--libexec.h1633
-rw-r--r--libexec_add_output_document.c45
-rw-r--r--libexec_add_output_fd.c15
-rw-r--r--libexec_add_pluming.c25
-rw-r--r--libexec_clear_environ.c72
-rw-r--r--libexec_close.c23
-rw-r--r--libexec_construct_command.c20
-rw-r--r--libexec_copy_environ.c51
-rw-r--r--libexec_destroy_command.c36
-rw-r--r--libexec_destroy_document.c28
-rw-r--r--libexec_destroy_pluming.c31
-rw-r--r--libexec_destroy_result.c20
-rw-r--r--libexec_dup.c24
-rw-r--r--libexec_exec.c200
-rw-r--r--libexec_get_documents.c83
-rw-r--r--libexec_getenv.c122
-rw-r--r--libexec_getenv_last.c123
-rw-r--r--libexec_init_command.c43
-rw-r--r--libexec_input_data_copy.c23
-rw-r--r--libexec_input_data_gift.c25
-rw-r--r--libexec_input_text_copy.c19
-rw-r--r--libexec_input_text_gift.c19
-rw-r--r--libexec_open.c15
-rw-r--r--libexec_openat.c36
-rw-r--r--libexec_openat2.c54
-rw-r--r--libexec_pipe_commands.c20
-rw-r--r--libexec_pipe_commandsv.c22
-rw-r--r--libexec_pipe_commandsvn.c106
-rw-r--r--libexec_put_arguments.c22
-rw-r--r--libexec_put_argumentsn.c45
-rw-r--r--libexec_putenv.c144
-rw-r--r--libexec_putenv_append.c235
-rw-r--r--libexec_putenv_noclobber.c175
-rw-r--r--libexec_putenv_noreplace.c171
-rw-r--r--libexec_putenv_prepend.c235
-rw-r--r--libexec_putenv_replace.c171
-rw-r--r--libexec_putenvf.c61
-rw-r--r--libexec_putenvf_append.c240
-rw-r--r--libexec_putenvf_noclobber.c180
-rw-r--r--libexec_putenvf_noreplace.c176
-rw-r--r--libexec_putenvf_prepend.c240
-rw-r--r--libexec_putenvf_replace.c176
-rw-r--r--libexec_recv_document.c53
-rw-r--r--libexec_renumber_fd.c32
-rw-r--r--libexec_run.c20
-rw-r--r--libexec_run_pipeline.c343
-rw-r--r--libexec_send_document.c26
-rw-r--r--libexec_set_environ.c26
-rw-r--r--libexec_set_exec_fd.c21
-rw-r--r--libexec_set_exec_path.c29
-rw-r--r--libexec_set_executable.c27
-rw-r--r--libexec_set_require_path.c18
-rw-r--r--libexec_setenv.c73
-rw-r--r--libexec_setenv_append.c212
-rw-r--r--libexec_setenv_noclobber.c180
-rw-r--r--libexec_setenv_noreplace.c176
-rw-r--r--libexec_setenv_prepend.c212
-rw-r--r--libexec_setenv_replace.c176
-rw-r--r--libexec_setenvf.c61
-rw-r--r--libexec_setenvf_append.c217
-rw-r--r--libexec_setenvf_noclobber.c185
-rw-r--r--libexec_setenvf_noreplace.c181
-rw-r--r--libexec_setenvf_prepend.c217
-rw-r--r--libexec_setenvf_replace.c181
-rw-r--r--libexec_spawn.c89
-rw-r--r--libexec_unsetenv.c48
-rw-r--r--libexec_unsetenv_first.c44
-rw-r--r--libexec_unsetenv_last.c46
-rw-r--r--libexec_vconstruct_command.c882
-rw-r--r--libexec_vpipe_commands.c30
-rw-r--r--libexec_vputenvf.c39
-rw-r--r--libexec_vputenvf_append.c15
-rw-r--r--libexec_vputenvf_noclobber.c15
-rw-r--r--libexec_vputenvf_noreplace.c15
-rw-r--r--libexec_vputenvf_prepend.c15
-rw-r--r--libexec_vputenvf_replace.c15
-rw-r--r--libexec_vrun.c307
-rw-r--r--libexec_vsetenvf.c39
-rw-r--r--libexec_vsetenvf_append.c15
-rw-r--r--libexec_vsetenvf_noclobber.c15
-rw-r--r--libexec_vsetenvf_noreplace.c15
-rw-r--r--libexec_vsetenvf_prepend.c15
-rw-r--r--libexec_vsetenvf_replace.c15
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
-rw-r--r--testhelp.c366
94 files changed, 10442 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..942ebc5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.to
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+*.test
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fccd785
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2024 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d159bef
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,177 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include mk/$(OS).mk
+
+
+LIB_MAJOR = 1
+LIB_MINOR = 0
+LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+LIB_NAME = exec
+
+
+OBJ =\
+ libexec_add_pluming.o\
+ libexec_add_output_document.o\
+ libexec_add_output_fd.o\
+ libexec_clear_environ.o\
+ libexec_close.o\
+ libexec_construct_command.o\
+ libexec_copy_environ.o\
+ libexec_destroy_command.o\
+ libexec_destroy_document.o\
+ libexec_destroy_pluming.o\
+ libexec_destroy_result.o\
+ libexec_dup.o\
+ libexec_exec.o\
+ libexec_get_documents.o\
+ libexec_getenv.o\
+ libexec_getenv_last.o\
+ libexec_init_command.o\
+ libexec_input_data_copy.o\
+ libexec_input_data_gift.o\
+ libexec_input_text_copy.o\
+ libexec_input_text_gift.o\
+ libexec_open.o\
+ libexec_openat.o\
+ libexec_openat2.o\
+ libexec_pipe_commands.o\
+ libexec_pipe_commandsv.o\
+ libexec_pipe_commandsvn.o\
+ libexec_put_arguments.o\
+ libexec_put_argumentsn.o\
+ libexec_putenv.o\
+ libexec_putenv_append.o\
+ libexec_putenv_noclobber.o\
+ libexec_putenv_noreplace.o\
+ libexec_putenv_prepend.o\
+ libexec_putenv_replace.o\
+ libexec_putenvf.o\
+ libexec_putenvf_append.o\
+ libexec_putenvf_noclobber.o\
+ libexec_putenvf_noreplace.o\
+ libexec_putenvf_prepend.o\
+ libexec_putenvf_replace.o\
+ libexec_recv_document.o\
+ libexec_renumber_fd.o\
+ libexec_run.o\
+ libexec_run_pipeline.o\
+ libexec_send_document.o\
+ libexec_set_environ.o\
+ libexec_set_exec_fd.o\
+ libexec_set_exec_path.o\
+ libexec_set_executable.o\
+ libexec_set_require_path.o\
+ libexec_setenv.o\
+ libexec_setenv_append.o\
+ libexec_setenv_noclobber.o\
+ libexec_setenv_noreplace.o\
+ libexec_setenv_prepend.o\
+ libexec_setenv_replace.o\
+ libexec_setenvf.o\
+ libexec_setenvf_append.o\
+ libexec_setenvf_noclobber.o\
+ libexec_setenvf_noreplace.o\
+ libexec_setenvf_prepend.o\
+ libexec_setenvf_replace.o\
+ libexec_spawn.o\
+ libexec_unsetenv.o\
+ libexec_unsetenv_first.o\
+ libexec_unsetenv_last.o\
+ libexec_vconstruct_command.o\
+ libexec_vpipe_commands.o\
+ libexec_vputenvf.o\
+ libexec_vputenvf_append.o\
+ libexec_vputenvf_noclobber.o\
+ libexec_vputenvf_noreplace.o\
+ libexec_vputenvf_prepend.o\
+ libexec_vputenvf_replace.o\
+ libexec_vrun.o\
+ libexec_vsetenvf.o\
+ libexec_vsetenvf_append.o\
+ libexec_vsetenvf_noclobber.o\
+ libexec_vsetenvf_noreplace.o\
+ libexec_vsetenvf_prepend.o\
+ libexec_vsetenvf_replace.o
+
+HDR =\
+ libexec.h\
+ common.h
+
+LOBJ = $(OBJ:.o=.lo)
+TOBJ = $(OBJ:.o=.to)
+TEST = $(OBJ:.o=.test)
+
+
+all: libexec.a libexec.$(LIBEXT) $(TEST)
+$(OBJ): $(HDR)
+$(LOBJ): $(HDR)
+$(TOBJ): $(HDR)
+$(TEST): testhelp.o libexec.a
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.to:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -DTEST
+
+.to.test:
+ $(CC) -o $@ $< testhelp.o libexec.a $(LDFLAGS)
+
+libexec.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+ $(AR) ts $@ > /dev/null
+
+libexec.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+check: $(TEST)
+ @status=0;\
+ for t in $(TEST); do\
+ if test -z "$(CHECK_PREFIX)"; then\
+ printf './%s\n' "$$t";\
+ else\
+ printf '%s ./%s\n' "$(CHECK_PREFIX)" "$$t";\
+ fi;\
+ if ! $(CHECK_PREFIX) "./$$t"; then\
+ printf '\033[1;31m%s failed!\033[m\n' "$$t";\
+ status=1;\
+ fi;\
+ done;\
+ exit $$status
+
+install: libexec.a libexec.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ cp -- libexec.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- libexec.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libexec.$(LIBMINOREXT)"
+ $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/libexec.$(LIBMINOREXT)"
+ ln -sf -- libexec.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libexec.$(LIBMAJOREXT)"
+ ln -sf -- libexec.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libexec.$(LIBEXT)"
+ cp -- libexec.h "$(DESTDIR)$(PREFIX)/include/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libexec.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libexec.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libexec.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libexec.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/libexec.h"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.to *.su *.so *.so.* *.dll *.dylib
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.test *.$(LIBEXT)
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c .to .test
+
+.PHONY: all check install uninstall clean
diff --git a/README b/README
new file mode 100644
index 0000000..ac082e5
--- /dev/null
+++ b/README
@@ -0,0 +1,14 @@
+NAME
+ libexec - Library for running complex external utilities
+
+DESCRIPTION
+ libexec is a library that is designed to make it easy
+ to safely and correctly run external utilities, including
+ creating pipe lines.
+
+ libexec is both designed to aid shell implementations and
+ to make it easier to write programs run a lot of commands
+ but could not easily be written as a shell script.
+
+SEE ALSO
+ None.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..ec3a255
--- /dev/null
+++ b/TODO
@@ -0,0 +1,13 @@
+Test that LIBEXEC_NOREPLACE first copies the calling process's environment,
+even if nothing is modified, but also test that LIBEXEC_NOCLOBBER does not
+do this.
+
+add LIBEXEC_RUN_ADD_SHARED_OUTPUT_FD
+add LIBEXEC_RUN_ADD_SHARED_OUTPUT
+add LIBEXEC_RUN_GET_STDOUT
+add LIBEXEC_RUN_GET_STDERR
+add LIBEXEC_RUN_SET_USER_TAG
+add a way to create process substitutions in libexec_run
+add a way to add a fork of the process into a pipeline
+
+add man pages
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..66a8ded
--- /dev/null
+++ b/common.h
@@ -0,0 +1,271 @@
+/* See LICENSE file for copyright and license details. */
+#include "libexec.h"
+
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if !defined(__linux__)
+int libexec_openat2(struct libexec_command *cmd, int fd, int dirfd, const char *file, void *how, size_t size);
+#endif
+
+
+#ifdef TEST
+# define TESTED_ELSEWHERE LIBEXEC_CONST__ int main(void) { return 0; }
+
+enum assert_type {
+ ASSERT_ENUM,
+ ASSERT_UINT,
+ ASSERT_INT,
+ ASSERT_NULL,
+ ASSERT_PTR,
+ ASSERT_STR,
+ ASSERT_STR_REV
+};
+
+enum assert_how {
+ ASSERT_EQ,
+ ASSERT_NE,
+ ASSERT_LT,
+ ASSERT_LE,
+ ASSERT_GT,
+ ASSERT_GE,
+ ASSERT_CONTAINS_ALL,
+ ASSERT_CONTAINS_ANY,
+ ASSERT_CONTAINS_NOT_ALL,
+ ASSERT_CONTAINS_NOT_ANY,
+ ASSERT_IS_IN,
+ ASSERT_NOT_IN,
+ ASSERT_NOT_LT,
+ ASSERT_NOT_LE,
+ ASSERT_NOT_GT,
+ ASSERT_NOT_GE
+};
+
+void test_assert(const char *file, int line, enum assert_type type, enum assert_how how,
+ const char *have_string, const char *expect_string, const void *have, const void *expect);
+
+#define ASSERT_EQ_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_EQ, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_NE_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_NE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_LT_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_LT, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_GT_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_GT, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_LE_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_LE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_GE_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_GE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_ALL_OF_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_CONTAINS_ALL, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_SOME_OF_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_CONTAINS_ANY, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_NONE_OF_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_CONTAINS_NOT_ANY, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_NOT_ALL_OF_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_CONTAINS_NOT_ALL, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_IS_IN_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_IS_IN, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_NOT_IN_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_NOT_IN, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_EQ_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_EQ, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_NE_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_NE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_LT_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_LT, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_GT_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_GT, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_LE_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_LE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_GE_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_GE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_ALL_OF_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_CONTAINS_ALL, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_SOME_OF_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_CONTAINS_ANY, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_NONE_OF_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_CONTAINS_NOT_ANY, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_NOT_ALL_OF_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_CONTAINS_NOT_ALL, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_IS_IN_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_IS_IN, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_EQ_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_EQ, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_NE_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_NE, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_LT_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_LT, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_GT_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_GT, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_LE_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_LE, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_GE_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_GE, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_EQ_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_EQ, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_NE_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_STARTS_WITH_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_EXTENDS_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_TRUNCATES_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_BEGINNING_BOUNDED_TO_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_ENDS_WITH_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_EXTENDS_END_OF_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_TRUNCATES_END_OF_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_ENDING_BOUNDED_TO_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_STARTS_WITH_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_EXTENDS_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_TRUNCATES_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_BEGINNING_NOT_BOUNDED_TO_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_ENDS_WITH_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_NOT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_EXTENDS_END_OF_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_NOT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_TRUNCATES_END_OF_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_NOT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_ENDING_NOT_BOUNDED_TO_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_NOT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_CONTAINS_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_CONTAINS_ALL, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_CONTAIN_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_CONTAINS_NOT_ALL, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_IS_IN_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_IS_IN, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_NOT_IN_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_IN, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_IS_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_EQ, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_NOT_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_NE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_BEFORE_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_AFTER_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_BEFORE_OR_IS_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_AFTER_OR_IS_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_IS_NULL(HAVE)\
+ test_assert(__FILE__, __LINE__, ASSERT_NULL, ASSERT_EQ, #HAVE, "NULL", (HAVE), NULL)
+
+#define ASSERT_NOT_NULL(HAVE)\
+ test_assert(__FILE__, __LINE__, ASSERT_NULL, ASSERT_NE, #HAVE, "NULL", (HAVE), NULL)
+
+#define ASSERT_IS_TRUE(HAVE)\
+ ASSERT_NE_INT(HAVE, 0)
+
+#define ASSERT_IS_FALSE(HAVE)\
+ ASSERT_EQ_INT(HAVE, 0)
+
+#define ASSERT_ZERO(HAVE)\
+ ASSERT_EQ_INT(HAVE, 0)
+
+
+#endif
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..f4adf12
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS =
diff --git a/libexec.h b/libexec.h
new file mode 100644
index 0000000..5f7b2d0
--- /dev/null
+++ b/libexec.h
@@ -0,0 +1,1633 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBEXEC_H
+#define LIBEXEC_H
+
+
+#if defined(__linux__)
+# include <linux/openat2.h>
+#endif
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+
+
+/* For internal use only { */
+#if defined(__GNUC__)
+# define LIBEXEC_PURE__ __attribute__((__pure__))
+# define LIBEXEC_CONST__ __attribute__((__const__))
+# define LIBEXEC_PRINTF__(FMTIDX) __attribute__((__format__(__gnu_printf__, (FMTIDX), (FMTIDX) + 1)))
+# define LIBEXEC_VPRINTF__(FMTIDX) __attribute__((__format__(__gnu_printf__, (FMTIDX), 0)))
+#else
+# define LIBEXEC_PURE__
+# define LIBEXEC_CONST__
+# define LIBEXEC_PRINTF__(FMTIDX)
+# define LIBEXEC_VPRINTF__(FMTIDX)
+#endif
+/* } */
+
+
+#define LIBEXEC_VERSION 0U
+
+
+enum libexec_insert_mode {
+ LIBEXEC_REPLACE, /* replace existing definition if any */
+ LIBEXEC_NOREPLACE, /* do nothing if definition already exists */
+ LIBEXEC_NOCLOBBER, /* fail if definition already exists an differs */
+ LIBEXEC_APPEND, /* add, to end of list, even if definition already exists, but also keep old */
+ LIBEXEC_PREPEND /* add, to beginning of list, even if definition already exists, but also keep old */
+ /* Either LIBEXEC_APPEND or LIBEXEC_PREPEND is required to put something missing '=' in the environment */
+#define LIBEXEC_INSERT_MODE__COUNT__ 5
+};
+
+enum libexec_pluming_type {
+ LIBEXEC_PLUMING_OPENAT,
+ LIBEXEC_PLUMING_OPENAT2,
+ LIBEXEC_PLUMING_CLOSE,
+ LIBEXEC_PLUMING_DUP2,
+ LIBEXEC_PLUMING_DOCUMENT,
+ LIBEXEC_PLUMING_PIPE
+#define LIBEXEC_PLUMING_TYPE__COUNT__ 6
+};
+
+enum libexec_pipe {
+ LIBEXEC_PIPE = 0,
+ LIBEXEC_SOCK_STREAM = 1,
+ LIBEXEC_SOCK_SEQPACKET = 2,
+ LIBEXEC_SOCK_DGRAM = 3,
+ LIBEXEC_DIRECT_PIPE = 4,
+ LIBEXEC_PIPE_CIRCULARLY = 0x8000
+#define LIBEXEC_PIPE__COUNT__ 5
+};
+
+enum libexec_locate_method {
+ LIBEXEC_ALLOW_NAME,
+ LIBEXEC_REQUIRE_PATH,
+ LIBEXEC_EXEC_FD,
+ LIBEXEC_EXEC_AT
+#define LIBEXEC_LOCATE_METHOD__COUNT__ 4
+};
+
+enum libexec_run_instruction {
+ LIBEXEC_RUN_END, /* required to mark end if arguments for libexec_run */
+ LIBEXEC_RUN_END_CIRCULAR_PIPE, /* alternative to LIBEXEC_RUN_END */
+ LIBEXEC_RUN_PIPE,
+ LIBEXEC_RUN_SET_EXECUTABLE,
+ LIBEXEC_RUN_SET_REQUIRE_PATH,
+ LIBEXEC_RUN_SET_EXEC_FD,
+ LIBEXEC_RUN_SET_EXEC_PATH,
+ LIBEXEC_RUN_COPY_ENVIRON,
+ LIBEXEC_RUN_SET_ENVIRON,
+ LIBEXEC_RUN_CLEAR_ENVIRON,
+ LIBEXEC_RUN_UNSETENV,
+ LIBEXEC_RUN_PUTENV,
+ LIBEXEC_RUN_SETENV,
+ LIBEXEC_RUN_PUTENVF,
+ LIBEXEC_RUN_SETENVF,
+ LIBEXEC_RUN_OPEN,
+ LIBEXEC_RUN_OPENAT,
+ LIBEXEC_RUN_OPENAT2, /* generates ENOSYS unless running on Linux */
+ LIBEXEC_RUN_DUP,
+ LIBEXEC_RUN_CLOSE,
+ LIBEXEC_RUN_RENUMBER_FD,
+ LIBEXEC_RUN_INPUT_COPY,
+ LIBEXEC_RUN_INPUT_GIFT,
+ LIBEXEC_RUN_ADD_OUTPUT_FD, /* use LIBEXEC_RUN_SET_FD_CALLBACK first */
+ LIBEXEC_RUN_ADD_OUTPUT,
+ LIBEXEC_RUN_SET_SIGMASK, /* use once to configure how libexec_run_pipeline is invoked behind the scenes */
+ LIBEXEC_RUN_SET_FD_CALLBACK, /* ditto */
+ LIBEXEC_RUN_SET_REAPER, /* ditto */
+ LIBEXEC_RUN_SET_ATFORK, /* ditto */
+ LIBEXEC_RUN_SET_MUTEX, /* ditto */
+ LIBEXEC_RUN_SET_INTERRUPT_CALLBACK /* ditto */
+#define LIBEXEC_RUN_INSTRUCTION__COUNT__ 31
+};
+#define LIBEXEC_RUN_END() LIBEXEC_RUN_END
+#define LIBEXEC_RUN_END_CIRCULAR_PIPE(TYPE) LIBEXEC_RUN_END_CIRCULAR_PIPE, TYPE
+#define LIBEXEC_RUN_PIPE(TYPE) LIBEXEC_RUN_PIPE, TYPE
+#define LIBEXEC_RUN_SET_EXECUTABLE(FILE) LIBEXEC_RUN_SET_EXECUTABLE, FILE
+#define LIBEXEC_RUN_SET_REQUIRE_PATH(REQUIRE) LIBEXEC_RUN_SET_REQUIRE_PATH, REQUIRE
+#define LIBEXEC_RUN_SET_EXEC_FD(FD) LIBEXEC_RUN_SET_EXEC_FD, FD
+#define LIBEXEC_RUN_SET_EXEC_PATH(DIRFD, PATH) LIBEXEC_RUN_SET_EXEC_PATH, DIRFD, PATH
+#define LIBEXEC_RUN_COPY_ENVIRON(ENVIRON) LIBEXEC_RUN_COPY_ENVIRON, ENVIRON
+#define LIBEXEC_RUN_SET_ENVIRON(ENVIRON) LIBEXEC_RUN_SET_ENVIRON, ENVIRON
+#define LIBEXEC_RUN_CLEAR_ENVIRON(ENVIRON) LIBEXEC_RUN_CLEAR_ENVIRON
+#define LIBEXEC_RUN_UNSETENV(ENV) LIBEXEC_RUN_UNSETENV, ENV
+#define LIBEXEC_RUN_PUTENV(... /* STR, [HOW] */) LIBEXEC_RUN_PUTENV__(__VA_ARGS__, LIBEXEC_REPLACE,)
+#define LIBEXEC_RUN_SETENV(... /* ENV, VAL, [HOW] */) LIBEXEC_RUN_PUTENV__(__VA_ARGS__, LIBEXEC_REPLACE,)
+#define LIBEXEC_RUN_PUTENV_SAFE(HOW, STR) LIBEXEC_RUN_PUTENV, HOW, STR
+#define LIBEXEC_RUN_SETENV_SAFE(HOW, ENV, VAL) LIBEXEC_RUN_SETENV, HOW, ENV, VAL
+#define LIBEXEC_RUN_PUTENVF_SAFE(HOW, ...) LIBEXEC_RUN_PUTENV, HOW, __VA_ARGS__
+#define LIBEXEC_RUN_SETENVF_SAFE(HOW, ENV, ...) LIBEXEC_RUN_SETENV, HOW, ENV, __VA_ARGS__
+#define LIBEXEC_RUN_PUTENVF(...) LIBEXEC_RUN_PUTENVF_SAFE(LIBEXEC_REPLACE, __VA_ARGS__)
+#define LIBEXEC_RUN_SETENVF(ENV, ...) LIBEXEC_RUN_SETENVF_SAFE(LIBEXEC_REPLACE, ENV, __VA_ARGS__)
+#define LIBEXEC_RUN_OPEN(FD, FILE, ... /* FLAGS, [MODE] */) LIBEXEC_RUN_OPEN__(FD, FILE, __VA_ARGS__, 0666,)
+#define LIBEXEC_RUN_OPENAT(FD, DIRFD, FILE, ... /* FLAGS, [MODE] */) LIBEXEC_RUN_OPENAT__(FD, DIRFD, FILE, __VA_ARGS__, 0666,)
+#define LIBEXEC_RUN_OPENAT2(FD, DIRFD, FILE, ... /* HOW, [SIZE] */)\
+ LIBEXEC_RUN_OPENAT2__(FD, DIRFD, FILE, __VA_ARGS__, sizeof(LIBEXEC__FIRST__(__VA_ARGS__,)),)
+#define LIBEXEC_RUN_DUP(NEW_FD, OLD_FD) LIBEXEC_RUN_DUP, NEW_FD, OLD_FD
+#define LIBEXEC_RUN_CLOSE(FD) LIBEXEC_RUN_CLOSE, FD
+#define LIBEXEC_RUN_RENUMBER_FD(NEW_FD, OLD_FD) LIBEXEC_RUN_RENUMBER_FD, NEW_FD, OLD_FD
+#define LIBEXEC_RUN_INPUT_COPY(FD, ... /* DATA, [LEN] */)\
+ LIBEXEC_RUN_INPUT__(COPY, FD, __VA_ARGS__, strlen(LIBEXEC__FIRST__(__VA_ARGS__)),)
+#define LIBEXEC_RUN_INPUT_GIFT(FD, ... /* DATA, [LEN] */)\
+ LIBEXEC_RUN_INPUT__(GIFT, FD, __VA_ARGS__, strlen(LIBEXEC__FIRST__(__VA_ARGS__)),)
+#define LIBEXEC_RUN_ADD_OUTPUT_FD(FD, WR_FD) LIBEXEC_RUN_ADD_OUTPUT_FD, FD, WR_FD
+#define LIBEXEC_RUN_ADD_OUTPUT(FD, OUT) LIBEXEC_RUN_ADD_OUTPUT, FD, OUT
+#define LIBEXEC_RUN_SET_SIGMASK(SIGMASK) LIBEXEC_RUN_SET_SIGMASK, SIGMASK
+#define LIBEXEC_RUN_SET_FD_CALLBACK(CB, USER) LIBEXEC_RUN_SET_FD_CALLBACK, CB, USER
+#define LIBEXEC_RUN_SET_REAPER(CB, USER) LIBEXEC_RUN_SET_REAPER, CB, USER
+#define LIBEXEC_RUN_SET_ATFORK(CB, USER) LIBEXEC_RUN_SET_ATFORK, CB, USER
+#define LIBEXEC_RUN_SET_MUTEX(CB, USER) LIBEXEC_RUN_SET_MUTEX, CB, USER
+#define LIBEXEC_RUN_SET_INTERRUPT_CALLBACK(CB, USER) LIBEXEC_RUN_SET_INTERRUPT_CALLBACK, CB, USER
+
+#define LIBEXEC__FIRST__(X, ...) X
+#define LIBEXEC_RUN_PUTENV__(STR, HOW, ...) LIBEXEC_RUN_PUTENV, HOW, STR
+#define LIBEXEC_RUN_SETENV__(ENV, VAL, HOW, ...) LIBEXEC_RUN_SETENV, HOW, ENV, VAL
+#define LIBEXEC_RUN_OPEN__(FD, FILE, FLAGS, MODE, ...) LIBEXEC_RUN_OPEN, FD, FILE, FLAGS, MODE
+#define LIBEXEC_RUN_OPENAT__(FD, DIRFD, FILE, FLAGS, MODE, ...) LIBEXEC_RUN_OPENAT, FD, DIRFD, FILE, FLAGS, MODE
+#define LIBEXEC_RUN_INPUT__(INSTR, FD, DATA, LEN, ...) LIBEXEC_RUN_INPUT_##INSTR, FD, DATA, LEN
+
+struct libexec_pluming {
+ int fd;
+ enum libexec_pluming_type type;
+ union {
+ int fd;
+ struct {
+ char *file;
+ int dirfd;
+ union {
+ struct {
+ mode_t mode;
+ int flags;
+ };
+#if defined(__linux__)
+ struct {
+ struct open_how *how;
+ size_t how_size;
+ };
+#endif
+ };
+ };
+ struct {
+ char *text;
+ size_t len;
+ };
+ } target;
+};
+
+#define LIBEXEC_COMMAND_INIT\
+ ((struct libexec_command){LIBEXEC_VERSION, LIBEXEC_ALLOW_NAME, -1, NULL, NULL, 0, NULL, 0, NULL})
+struct libexec_command {
+ unsigned library_version;
+ enum libexec_locate_method exec_how;
+ int exec_fd;
+ char *executable;
+ char **arguments;
+ size_t narguments;
+ struct libexec_pluming *plumings;
+ size_t nplumings;
+ char **environ;
+};
+
+struct libexec_document {
+ unsigned long long int user;
+ int fd;
+ char *text;
+ size_t length;
+ union {
+ size_t offset; /* when used for input */
+ size_t alloc_size; /* when used for output */
+ };
+};
+
+struct libexec_result {
+ int *exit_statuses;
+ size_t proc_count;
+ int all_successful;
+};
+
+
+void libexec_init_command(struct libexec_command *cmd);
+void libexec_destroy_command(struct libexec_command *cmd);
+void libexec_destroy_pluming(struct libexec_pluming *pluming);
+int libexec_destroy_document(struct libexec_document *doc);
+void libexec_destroy_result(struct libexec_result *result);
+
+int libexec_vconstruct_command(struct libexec_command *cmd, const char *fmt, va_list args);
+int libexec_construct_command(struct libexec_command *cmd, const char *fmt, ...);
+int libexec_put_argumentsn(struct libexec_command *cmd, const char *const *args, size_t nargs);
+int libexec_put_arguments(struct libexec_command *cmd, const char *const *args);
+int libexec_set_executable(struct libexec_command *cmd, const char *executable /* NULL = use first argument in command */);
+void libexec_set_require_path(struct libexec_command *cmd, int require);
+int libexec_set_exec_fd(struct libexec_command *cmd, int fd);
+int libexec_set_exec_path(struct libexec_command *cmd, int dirfd, const char *path /* NULL = use first argument in command */);
+
+int libexec_add_pluming(struct libexec_command *cmd, const struct libexec_pluming *pluming);
+int libexec_open(struct libexec_command *cmd, int fd, const char *file, int flags, mode_t mode);
+int libexec_openat(struct libexec_command *cmd, int fd, int dirfd, const char *file, int flags, mode_t mode);
+#if defined(__linux__)
+int libexec_openat2(struct libexec_command *cmd, int fd, int dirfd, const char *file, struct open_how *how, size_t size);
+#endif
+int libexec_dup(struct libexec_command *cmd, int fd, int old_fd);
+int libexec_close(struct libexec_command *cmd, int fd);
+int libexec_renumber_fd(struct libexec_command *cmd, int fd, int old_fd);
+int libexec_input_data_copy(struct libexec_command *cmd, int fd, const char *data, size_t len);
+int libexec_input_data_gift(struct libexec_command *cmd, int fd, char *data, size_t len);
+int libexec_input_text_copy(struct libexec_command *cmd, int fd, const char *text);
+int libexec_input_text_gift(struct libexec_command *cmd, int fd, char *text);
+int libexec_add_output_fd(struct libexec_command *cmd, int fd, int wr_fd);
+int libexec_add_output_document(struct libexec_command *cmd, int fd, struct libexec_document *doc /* .fd will be set */, int flags);
+
+
+/**
+ * The the value of an environment variable in a command
+ *
+ * If command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment is inspected
+ *
+ * In the event that the environment contains multiple
+ * definitions of the same variable, the first definition
+ * is used
+ *
+ * @param cmd The command whose environment shall be inspected
+ * @param name The name of the environment variable to read
+ * @return The value of the environment variable, or NULL
+ * on failure or if the environment variable was
+ * not found
+ *
+ * @throws (unmodified) The environment variable does not exist
+ * @throws EINVAL Invalid argument input
+ */
+LIBEXEC_PURE__ const char *libexec_getenv(struct libexec_command *, const char *);
+
+/**
+ * The the value of an environment variable in a command
+ *
+ * If command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment is inspected
+ *
+ * In the event that the environment contains multiple
+ * definitions of the same variable, the last definition
+ * is used
+ *
+ * @param cmd The command whose environment shall be inspected
+ * @param name The name of the environment variable to read
+ * @return The value of the environment variable, or NULL
+ * on failure or if the environment variable was
+ * not found
+ *
+ * @throws (unmodified) The environment variable does not exist
+ * @throws EINVAL Invalid argument input
+ */
+LIBEXEC_PURE__ const char *libexec_getenv_last(struct libexec_command *, const char *);
+
+
+/**
+ * Copy a list of environment variables and used it for a command
+ *
+ * @param cmd The command whose environment shall be set
+ * @param env The environment (will be copied), if NULL, the
+ * calling process's environment will be (copied
+ * and) used for the command
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_copy_environ(struct libexec_command *, const char *const *);
+
+/**
+ * Set the command's environment to a prebuilt list of
+ * environment variables
+ *
+ * @param cmd The command whose environment shall be set
+ * @param env The environment; the ownership of the input
+ * pointer and all pointers stored in it will be
+ * transfered to `cmd`, meaning that they will be
+ * deallocated using free(3) when they are removed
+ * or when `cmd` is destructed. If NULL, the
+ * commands environment will be reset to its default
+ * state, meaning that the calling process's environment
+ * (it will not be copied into `cmd`) will be used
+ * when running the command
+ */
+void libexec_set_environ(struct libexec_command *, char **);
+
+/**
+ * Clear a command's environment, removing all it's environment variables
+ *
+ * Even if the command's environment is in its default state (to
+ * use the calling process's environment), and calling process's
+ * environment is void of variables, the command's environment
+ * will be put in a non-default state, meaning that future changes
+ * to the calling process's environments variable will not affect
+ * the command's environment
+ *
+ * @param cmd The command whose environment shall be modified
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_clear_environ(struct libexec_command *);
+
+/**
+ * Remove all occurences of an environment variable
+ * in a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will be copied and set as the
+ * command's environment once the variable is found; however
+ * if the variable is not found within the environment, the
+ * command's environment will remain in its default state
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the variable to remove from the environment
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_unsetenv(struct libexec_command *, const char *);
+
+/**
+ * Remove the first occurence of an environment variable
+ * in a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will be copied and set as the
+ * command's environment once the variable is found; however
+ * if the variable is not found within the environment, the
+ * command's environment will remain in its default state
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the variable to remove from the environment
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_unsetenv_first(struct libexec_command *, const char *);
+
+/**
+ * Remove the last occurence of an environment variable
+ * in a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will be copied and set as the
+ * command's environment once the variable is found; however
+ * if the variable is not found within the environment, the
+ * command's environment will remain in its default state
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the variable to remove from the environment
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_unsetenv_last(struct libexec_command *, const char *);
+
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param how Must be one of the following values:
+ * - LIBEXEC_REPLACE:
+ * If the environment already contains the
+ * variable, replace its first definition in
+ * the environment
+ * - LIBEXEC_NOREPLACE:
+ * Do not replacing already existing definition
+ * of the environment variable
+ * - LIBEXEC_NOCLOBBER:
+ * Fail if the environment already contains the
+ * variable, but has a different value
+ * - LIBEXEC_APPEND:
+ * Always the string to the end of the environment
+ * - LIBEXEC_PREPEND:
+ * Always the string to the beginning of the environment
+ * @param string The environment variable that should be put into
+ * the environment, unless `how` is `LIBEXEC_APPEND`
+ * or `LIBEXEC_PREPEND`, it must be in the form
+ * `"%s=%s", name, value` (where `name` does not
+ * contain an equal sign)
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * (Only if `how` is `LIBEXEC_NOCLOBBER`)
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_putenv(struct libexec_command *, enum libexec_insert_mode, const char *);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param how Must be one of the following values:
+ * - LIBEXEC_REPLACE:
+ * If the environment already contains the
+ * variable, replace its first definition in
+ * the environment
+ * - LIBEXEC_NOREPLACE:
+ * Do not replacing already existing definition
+ * of the environment variable
+ * - LIBEXEC_NOCLOBBER:
+ * Fail if the environment already contains the
+ * variable, but has a different value
+ * - LIBEXEC_APPEND:
+ * Always the string to the end of the environment
+ * - LIBEXEC_PREPEND:
+ * Always the string to the beginning of the environment
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment, unless `how` is `LIBEXEC_APPEND`
+ * or `LIBEXEC_PREPEND`, the resulting string must
+ * be in the form `"%s=%s", name, value` (where `name`
+ * does not contain an equal sign)
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * (Only if `how` is `LIBEXEC_NOCLOBBER`)
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(3) int libexec_vputenvf(struct libexec_command *, enum libexec_insert_mode, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param how Must be one of the following values:
+ * - LIBEXEC_REPLACE:
+ * If the environment already contains the
+ * variable, replace its first definition in
+ * the environment
+ * - LIBEXEC_NOREPLACE:
+ * Do not replacing already existing definition
+ * of the environment variable
+ * - LIBEXEC_NOCLOBBER:
+ * Fail if the environment already contains the
+ * variable, but has a different value
+ * - LIBEXEC_APPEND:
+ * Always the string to the end of the environment
+ * - LIBEXEC_PREPEND:
+ * Always the string to the beginning of the environment
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment, unless `how` is `LIBEXEC_APPEND`
+ * or `LIBEXEC_PREPEND`, the resulting string must
+ * be in the form `"%s=%s", name, value` (where `name`
+ * does not contain an equal sign)
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * (Only if `how` is `LIBEXEC_NOCLOBBER`)
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(3) int libexec_putenvf(struct libexec_command *, enum libexec_insert_mode, const char *, ...);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param how Must be one of the following values:
+ * - LIBEXEC_REPLACE:
+ * If the environment already contains the
+ * variable, replace its first definition in
+ * the environment
+ * - LIBEXEC_NOREPLACE:
+ * Do not replacing already existing definition
+ * of the environment variable
+ * - LIBEXEC_NOCLOBBER:
+ * Fail if the environment already contains the
+ * variable, but has a different value
+ * - LIBEXEC_APPEND:
+ * Always the string to the end of the environment
+ * - LIBEXEC_PREPEND:
+ * Always the string to the beginning of the environment
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value The value that shall be associated with the
+ * environment variable
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * (Only if `how` is `LIBEXEC_NOCLOBBER`)
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_setenv(struct libexec_command *, enum libexec_insert_mode, const char *, const char *);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param how Must be one of the following values:
+ * - LIBEXEC_REPLACE:
+ * If the environment already contains the
+ * variable, replace its first definition in
+ * the environment
+ * - LIBEXEC_NOREPLACE:
+ * Do not replacing already existing definition
+ * of the environment variable
+ * - LIBEXEC_NOCLOBBER:
+ * Fail if the environment already contains the
+ * variable, but has a different value
+ * - LIBEXEC_APPEND:
+ * Always the string to the end of the environment
+ * - LIBEXEC_PREPEND:
+ * Always the string to the beginning of the environment
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param args Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * (Only if `how` is `LIBEXEC_NOCLOBBER`)
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(4) int libexec_vsetenvf(struct libexec_command *, enum libexec_insert_mode, const char *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param how Must be one of the following values:
+ * - LIBEXEC_REPLACE:
+ * If the environment already contains the
+ * variable, replace its first definition in
+ * the environment
+ * - LIBEXEC_NOREPLACE:
+ * Do not replacing already existing definition
+ * of the environment variable
+ * - LIBEXEC_NOCLOBBER:
+ * Fail if the environment already contains the
+ * variable, but has a different value
+ * - LIBEXEC_APPEND:
+ * Always the string to the end of the environment
+ * - LIBEXEC_PREPEND:
+ * Always the string to the beginning of the environment
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param ... Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * (Only if `how` is `LIBEXEC_NOCLOBBER`)
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(4) int libexec_setenvf(struct libexec_command *, enum libexec_insert_mode, const char *, const char *, ...);
+
+
+/**
+ * Put an environment variable into a command's environment,
+ * replacing the first already existing definition of the
+ * variable if there is one
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_putenv(cmd, LIBEXEC_REPLACE, string)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param string The environment variable that should be put into the
+ * environment, it must be in the form `"%s=%s", name, value`
+ * (where `name` does not contain an equal sign)
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_putenv_replace(struct libexec_command *, const char *);
+
+/**
+ * Put an environment variable into a command's environment,
+ * replacing the first already existing definition of the
+ * variable if there is one
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_vputenvf(cmd, LIBEXEC_REPLACE, fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment, the resulting string must
+ * be in the form `"%s=%s", name, value` (where `name`
+ * does not contain an equal sign)
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(2) int libexec_vputenvf_replace(struct libexec_command *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment,
+ * replacing the first already existing definition of the
+ * variable if there is one
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_putenvf(cmd, LIBEXEC_REPLACE, fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment, the resulting string must
+ * be in the form `"%s=%s", name, value` (where `name`
+ * does not contain an equal sign)
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(2) int libexec_putenvf_replace(struct libexec_command *, const char *, ...);
+
+/**
+ * Put an environment variable into a command's environment,
+ * replacing the first already existing definition of the
+ * variable if there is one
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_setenv(cmd, LIBEXEC_REPLACE, name, value)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value The value that shall be associated with the
+ * environment variable
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_setenv_replace(struct libexec_command *, const char *, const char *);
+
+/**
+ * Put an environment variable into a command's environment,
+ * replacing the first already existing definition of the
+ * variable if there is one
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_vsetenvf(cmd, LIBEXEC_REPLACE, name, value_fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param args Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(3) int libexec_vsetenvf_replace(struct libexec_command *, const char *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment,
+ * replacing the first already existing definition of the
+ * variable if there is one
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_setenvf(cmd, LIBEXEC_REPLACE, name, value_fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param ... Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(3) int libexec_setenvf_replace(struct libexec_command *, const char *, const char *, ...);
+
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, the command's environment variable will not
+ * be modified, except that initialisation from default state
+ * as describe above will take place
+ *
+ * Equivalent to `libexec_putenv(cmd, LIBEXEC_NOREPLACE, string)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param string The environment variable that should be put into the
+ * environment, it must be in the form `"%s=%s", name, value`
+ * (where `name` does not contain an equal sign)
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_putenv_noreplace(struct libexec_command *, const char *);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, the command's environment variable will not
+ * be modified, except that initialisation from default state
+ * as describe above will take place
+ *
+ * Equivalent to `libexec_vputenvf(cmd, LIBEXEC_NOREPLACE, fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment, the resulting string must
+ * be in the form `"%s=%s", name, value` (where `name`
+ * does not contain an equal sign)
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(2) int libexec_vputenvf_noreplace(struct libexec_command *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, the command's environment variable will not
+ * be modified, except that initialisation from default state
+ * as describe above will take place
+ *
+ * Equivalent to `libexec_putenvf(cmd, LIBEXEC_NOREPLACE, fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment, the resulting string must
+ * be in the form `"%s=%s", name, value` (where `name`
+ * does not contain an equal sign)
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(2) int libexec_putenvf_noreplace(struct libexec_command *, const char *, ...);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, the command's environment variable will not
+ * be modified, except that initialisation from default state
+ * as describe above will take place
+ *
+ * Equivalent to `libexec_setenv(cmd, LIBEXEC_NOREPLACE, name, value)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value The value that shall be associated with the
+ * environment variable
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_setenv_noreplace(struct libexec_command *, const char *, const char *);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, the command's environment variable will not
+ * be modified, except that initialisation from default state
+ * as describe above will take place
+ *
+ * Equivalent to `libexec_vsetenvf(cmd, LIBEXEC_NOREPLACE, name, value_fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param args Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(3) int libexec_vsetenvf_noreplace(struct libexec_command *, const char *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, the command's environment variable will not
+ * be modified, except that initialisation from default state
+ * as describe above will take place
+ *
+ * Equivalent to `libexec_setenvf(cmd, LIBEXEC_NOREPLACE, name, value_fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param ... Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(3) int libexec_setenvf_noreplace(struct libexec_command *, const char *, const char *, ...);
+
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, but has a different value, the function will
+ * fail without side effects
+ *
+ * Equivalent to `libexec_putenv(cmd, LIBEXEC_NOCLOBBER, string)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param string The environment variable that should be put into the
+ * environment, it must be in the form `"%s=%s", name, value`
+ * (where `name` does not contain an equal sign)
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_putenv_noclobber(struct libexec_command *, const char *);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, but has a different value, the function will
+ * fail without side effects
+ *
+ * Equivalent to `libexec_vputenvf(cmd, LIBEXEC_NOCLOBBER, fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment, the resulting string must
+ * be in the form `"%s=%s", name, value` (where `name`
+ * does not contain an equal sign)
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(2) int libexec_vputenvf_noclobber(struct libexec_command *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, but has a different value, the function will
+ * fail without side effects
+ *
+ * Equivalent to `libexec_putenvf(cmd, LIBEXEC_NOCLOBBER, fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment, the resulting string must
+ * be in the form `"%s=%s", name, value` (where `name`
+ * does not contain an equal sign)
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(2) int libexec_putenvf_noclobber(struct libexec_command *, const char *, ...);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, but has a different value, the function will
+ * fail without side effects
+ *
+ * Equivalent to `libexec_setenv(cmd, LIBEXEC_NOCLOBBER, name, value)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value The value that shall be associated with the
+ * environment variable
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_setenv_noclobber(struct libexec_command *, const char *, const char *);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, but has a different value, the function will
+ * fail without side effects
+ *
+ * Equivalent to `libexec_vsetenvf(cmd, LIBEXEC_NOCLOBBER, name, value_fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param args Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(3) int libexec_vsetenvf_noclobber(struct libexec_command *, const char *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * If the environment variable already exists in the command's
+ * environment, but has a different value, the function will
+ * fail without side effects
+ *
+ * Equivalent to `libexec_setenvf(cmd, LIBEXEC_NOCLOBBER, name, value_fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param ... Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws (unmodified) The environment variable already exists
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(3) int libexec_setenvf_noclobber(struct libexec_command *, const char *, const char *, ...);
+
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the end of the environment regardless of
+ * whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_putenv(cmd, LIBEXEC_APPEND, string)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param string The environment variable that should be put into the
+ * environment; there is no requirement that it contains
+ * an equal sign
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_putenv_append(struct libexec_command *, const char *);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the end of the environment regardless of
+ * whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_vputenvf(cmd, LIBEXEC_APPEND, fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment; there is no requirement that
+ * the resulting string contains an equal sign
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(2) int libexec_vputenvf_append(struct libexec_command *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the end of the environment regardless of
+ * whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_putenvf(cmd, LIBEXEC_APPEND, fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment; there is no requirement that
+ * the resulting string contains an equal sign
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(2) int libexec_putenvf_append(struct libexec_command *, const char *, ...);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the end of the environment regardless of
+ * whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_setenv(cmd, LIBEXEC_APPEND, name, value)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value The value that shall be associated with the
+ * environment variable
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_setenv_append(struct libexec_command *, const char *, const char *);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the end of the environment regardless of
+ * whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_vsetenvf(cmd, LIBEXEC_APPEND, name, value_fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param args Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(3) int libexec_vsetenvf_append(struct libexec_command *, const char *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the end of the environment regardless of
+ * whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_setenvf(cmd, LIBEXEC_APPEND, name, value_fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param ... Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(3) int libexec_setenvf_append(struct libexec_command *, const char *, const char *, ...);
+
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the beginning of the environment regardless
+ * of whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_putenv(cmd, LIBEXEC_PREPEND, string)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param string The environment variable that should be put into the
+ * environment; there is no requirement that it contains
+ * an equal sign
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_putenv_prepend(struct libexec_command *, const char *);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the beginning of the environment regardless
+ * of whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_vputenvf(cmd, LIBEXEC_PREPEND, fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment; there is no requirement that
+ * the resulting string contains an equal sign
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(2) int libexec_vputenvf_prepend(struct libexec_command *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the beginning of the environment regardless
+ * of whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_putenvf(cmd, LIBEXEC_PREPEND, fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param fmt Formatting string, following the rules of printf(3),
+ * for the environment variable that should be put
+ * into the environment; there is no requirement that
+ * the resulting string contains an equal sign
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(2) int libexec_putenvf_prepend(struct libexec_command *, const char *, ...);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the beginning of the environment regardless
+ * of whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_setenv(cmd, LIBEXEC_PREPEND, name, value)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value The value that shall be associated with the
+ * environment variable
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ */
+int libexec_setenv_prepend(struct libexec_command *, const char *, const char *);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the beginning of the environment regardless
+ * of whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_vsetenvf(cmd, LIBEXEC_PREPEND, name, value_fmt, args)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param args Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_VPRINTF__(3) int libexec_vsetenvf_prepend(struct libexec_command *, const char *, const char *, va_list);
+
+/**
+ * Put an environment variable into a command's environment,
+ * adding it to the beginning of the environment regardless
+ * of whether the variable already exists
+ *
+ * If the command's environment is in its default state (to
+ * use the calling process's environment), the calling
+ * process's environment will first be copied and set as the
+ * command's environment
+ *
+ * Equivalent to `libexec_setenvf(cmd, LIBEXEC_PREPEND, name, value_fmt, ...)`
+ *
+ * @param cmd The command whose environment shall be modified
+ * @param name The name of the environment variable, must not
+ * contain an equal sign
+ * @param value_fmt Formatting string, following the rules of printf(3),
+ * for the environment variable's value
+ * @param ... Arguments for `value_fmt`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory to modify the command's environment
+ * @throws EILSEQ According to printf(3)
+ * @throws EOVERFLOW According to printf(3)
+ */
+LIBEXEC_PRINTF__(3) int libexec_setenvf_prepend(struct libexec_command *, const char *, const char *, ...);
+
+
+/**
+ * Put a series of commands in a pipeline, piping each's
+ * standard output to the next's (when there is one)
+ * standard input
+ *
+ * Pluming data added to the commands will prefix any
+ * already existing pluming information
+ *
+ * @param how Which type of file should be used to pipe
+ * together commands, and additional options.
+ * Must be one of the following values:
+ * - LIBEXEC_PIPE:
+ * Use a normal pipe(7)
+ * - LIBEXEC_SOCK_STREAM
+ * Use a unix(7) socketpair(3) with the type SOCK_STREAM
+ * - LIBEXEC_SOCK_SEQPACKET
+ * Use a unix(7) socketpair(3) with the type SOCK_SEQPACKET
+ * - LIBEXEC_SOCK_DGRAM
+ * Use a unix(7) socketpair(3) with the type SOCK_DGRAM
+ * - LIBEXEC_DIRECT_PIPE
+ * Use an O_DIRECT pipe(7) (see pipe2(2))
+ * Optionally, the file type may be OR'ed with
+ * LIBEXEC_PIPE_CIRCULARLY to pipe the last
+ * command's standard output to the first command's
+ * standard input.
+ * @param cmds List of commands to put in a pipeline
+ * @param n The number of elements in `cmds`
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory
+ */
+int libexec_pipe_commandsvn(enum libexec_pipe how, struct libexec_command *const *cmds, size_t n);
+
+/**
+ * Put a series of commands in a pipeline, piping each's
+ * standard output to the next's (when there is one)
+ * standard input
+ *
+ * Pluming data added to the commands will prefix any
+ * already existing pluming information
+ *
+ * @param how Which type of file should be used to pipe
+ * together commands, and additional options.
+ * Must be one of the following values:
+ * - LIBEXEC_PIPE:
+ * Use a normal pipe(7)
+ * - LIBEXEC_SOCK_STREAM
+ * Use a unix(7) socketpair(3) with the type SOCK_STREAM
+ * - LIBEXEC_SOCK_SEQPACKET
+ * Use a unix(7) socketpair(3) with the type SOCK_SEQPACKET
+ * - LIBEXEC_SOCK_DGRAM
+ * Use a unix(7) socketpair(3) with the type SOCK_DGRAM
+ * - LIBEXEC_DIRECT_PIPE
+ * Use an O_DIRECT pipe(7) (see pipe2(2))
+ * Optionally, the file type may be OR'ed with
+ * LIBEXEC_PIPE_CIRCULARLY to pipe the last
+ * command's standard output to the first command's
+ * standard input.
+ * @param cmds `NULL` terminated list of commands to put in a pipeline
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory
+ */
+int libexec_pipe_commandsv(enum libexec_pipe how, struct libexec_command *const *cmds);
+
+/**
+ * Put a series of commands in a pipeline, piping each's
+ * standard output to the next's (when there is one)
+ * standard input
+ *
+ * Pluming data added to the commands will prefix any
+ * already existing pluming information
+ *
+ * @param how Which type of file should be used to pipe
+ * together commands, and additional options.
+ * Must be one of the following values:
+ * - LIBEXEC_PIPE:
+ * Use a normal pipe(7)
+ * - LIBEXEC_SOCK_STREAM
+ * Use a unix(7) socketpair(3) with the type SOCK_STREAM
+ * - LIBEXEC_SOCK_SEQPACKET
+ * Use a unix(7) socketpair(3) with the type SOCK_SEQPACKET
+ * - LIBEXEC_SOCK_DGRAM
+ * Use a unix(7) socketpair(3) with the type SOCK_DGRAM
+ * - LIBEXEC_DIRECT_PIPE
+ * Use an O_DIRECT pipe(7) (see pipe2(2))
+ * Optionally, the file type may be OR'ed with
+ * LIBEXEC_PIPE_CIRCULARLY to pipe the last
+ * command's standard output to the first command's
+ * standard input.
+ * @param args `NULL` terminated list of commands to put in a pipeline
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory
+ */
+int libexec_vpipe_commands(enum libexec_pipe how, va_list args);
+
+/**
+ * Put a series of commands in a pipeline, piping each's
+ * standard output to the next's (when there is one)
+ * standard input
+ *
+ * Pluming data added to the commands will prefix any
+ * already existing pluming information
+ *
+ * @param how Which type of file should be used to pipe
+ * together commands, and additional options.
+ * Must be one of the following values:
+ * - LIBEXEC_PIPE:
+ * Use a normal pipe(7)
+ * - LIBEXEC_SOCK_STREAM
+ * Use a unix(7) socketpair(3) with the type SOCK_STREAM
+ * - LIBEXEC_SOCK_SEQPACKET
+ * Use a unix(7) socketpair(3) with the type SOCK_SEQPACKET
+ * - LIBEXEC_SOCK_DGRAM
+ * Use a unix(7) socketpair(3) with the type SOCK_DGRAM
+ * - LIBEXEC_DIRECT_PIPE
+ * Use an O_DIRECT pipe(7) (see pipe2(2))
+ * Optionally, the file type may be OR'ed with
+ * LIBEXEC_PIPE_CIRCULARLY to pipe the last
+ * command's standard output to the first command's
+ * standard input.
+ * @param ... `NULL` terminated list of commands to put in a pipeline
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid argument input
+ * @throws ENOMEM Failed to allocate enough memory
+ */
+int libexec_pipe_commands(enum libexec_pipe how, ...);
+
+
+int libexec_get_documents(struct libexec_command *cmd, struct libexec_document **docsp, size_t *ndocsp, int flags);
+int libexec_exec(struct libexec_command *cmd);
+int libexec_spawn(pid_t *out, struct libexec_command *cmd,
+ int (*after_fork)(struct libexec_command *cmd, int new_fd, void *user),
+ void *user, struct libexec_document **docsp, size_t *ndocsp, int doc_fd_flags);
+int libexec_send_document(struct libexec_document *doc);
+int libexec_recv_document(struct libexec_document *doc);
+
+/* TODO maybe its better to have `struct libexec_run_how` */
+int libexec_run_pipeline(int (*on_alien_epoll)(int alien_epoll, uint32_t events, void *user1), int alien_epoll, void *user1,
+ int (*on_alien_child_death)(pid_t pid, void *user2), void *user2,
+ int (*after_fork)(struct libexec_command *cmd, int new_fd, void *user3), void *user3,
+ int (*reap_mutex_control)(int action /* -1 = acquire, +1 = release */, void *user4), void *user4,
+ int (*on_interrupt)(void *user5), void *user5,
+ const sigset_t *sigmask /* nullable */,
+ struct libexec_document *const *docs, size_t ndocs, int docs_1_level,
+ struct libexec_command *const *cmds, int *exit_statuses_out /* nullable */, size_t ncmds);
+#define LIBEXEC_RUN_PIPELINE_NO_EPOLL NULL, -1, NULL
+#define LIBEXEC_RUN_PIPELINE_NO_CHILDREN NULL, NULL
+#define LIBEXEC_RUN_PIPELINE_NO_AFTER_FORK NULL, NULL
+#define LIBEXEC_RUN_PIPELINE_NO_THREADING NULL, NULL
+#define LIBEXEC_RUN_PIPELINE_NO_INTERRUPT NULL, NULL
+#define LIBEXEC_RUN_PIPELINE_NO_SIGMASK NULL
+#define LIBEXEC_RUN_PIPELINE_NO_OUTPUT NULL, 0, 0
+#define LIBEXEC_RUN_PIPELINE_ONLY_OUTPUT\
+ LIBEXEC_RUN_PIPELINE_NO_EPOLL,\
+ LIBEXEC_RUN_PIPELINE_NO_CHILDREN,\
+ LIBEXEC_RUN_PIPELINE_NO_AFTER_FORK,\
+ LIBEXEC_RUN_PIPELINE_NO_THREADING,\
+ LIBEXEC_RUN_PIPELINE_NO_INTERRUPT
+#define LIBEXEC_RUN_PIPELINE_NOTHING\
+ LIBEXEC_RUN_PIPELINE_ONLY_OUTPUT,\
+ LIBEXEC_RUN_PIPELINE_NO_OUTPUT
+
+int libexec_vrun(struct libexec_result *out, va_list args);
+int libexec_run(struct libexec_result *out, ...);
+
+
+#endif
diff --git a/libexec_add_output_document.c b/libexec_add_output_document.c
new file mode 100644
index 0000000..9eb1f8d
--- /dev/null
+++ b/libexec_add_output_document.c
@@ -0,0 +1,45 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_add_output_document(struct libexec_command *cmd, int fd, struct libexec_document *doc, int flags)
+{
+ int fd_flags, fl_flags, fds[2];
+
+ if (!cmd || fd < 0 || !doc) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd_flags = (flags & O_CLOEXEC);
+ fl_flags = (flags ^ fd_flags);
+ if (fd_flags)
+ fd_flags = FD_CLOEXEC;
+
+ if (pipe(fds))
+ return -1;
+ if (fd_flags && fcntl(fds[0], F_SETFD, fd_flags))
+ goto fail;
+ if (fl_flags && fcntl(fds[0], F_SETFL, fl_flags))
+ goto fail;
+
+ doc->fd = fds[0];
+
+ if (libexec_dup(cmd, fd, fds[1]))
+ goto fail;
+ cmd->plumings[cmd->nplumings - 1].type = LIBEXEC_PLUMING_PIPE;
+
+ return 0;
+
+fail:
+ close(fds[0]);
+ close(fds[1]);
+ return -1;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_add_output_fd.c b/libexec_add_output_fd.c
new file mode 100644
index 0000000..6eae580
--- /dev/null
+++ b/libexec_add_output_fd.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_add_output_fd(struct libexec_command *cmd, int fd, int wr_fd)
+{
+ return libexec_renumber_fd(cmd, fd, wr_fd);
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_add_pluming.c b/libexec_add_pluming.c
new file mode 100644
index 0000000..108e8e8
--- /dev/null
+++ b/libexec_add_pluming.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_add_pluming(struct libexec_command *cmd, const struct libexec_pluming *pluming)
+{
+ void *new;
+ if (!cmd || !pluming) {
+ errno = EINVAL;
+ return -1;
+ }
+ new = realloc(cmd->plumings, (cmd->nplumings + 1) * sizeof(*cmd->plumings));
+ if (!new)
+ return -1;
+ cmd->plumings = new;
+ memcpy(&cmd->plumings[cmd->nplumings++], pluming, sizeof(*pluming));
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_clear_environ.c b/libexec_clear_environ.c
new file mode 100644
index 0000000..9f09561
--- /dev/null
+++ b/libexec_clear_environ.c
@@ -0,0 +1,72 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_clear_environ(struct libexec_command *cmd)
+{
+ size_t i;
+ char **new_env;
+
+ if (!cmd) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ new_env = calloc(1, sizeof(*cmd->environ));
+ if (!new_env)
+ return -1;
+
+ if (cmd->environ) {
+ for (i = 0; cmd->environ[i]; i++)
+ free(cmd->environ[i]);
+ free(cmd->environ);
+ }
+
+ cmd->environ = new_env;
+ return 0;
+}
+
+
+#else
+
+
+static void
+check_clear_from_default(void)
+{
+ struct libexec_command cmd, ref;
+ char **old_env;
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+ ASSERT_IS_NULL(cmd.environ);
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ old_env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = old_env;
+
+ libexec_destroy_command(&cmd);
+}
+
+
+int
+main(void)
+{
+ errno = 0;
+ ASSERT_EQ_INT(libexec_clear_environ(NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ check_clear_from_default();
+ /* XXX test malloc failure */
+ /* XXX test that function deallocates */
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_close.c b/libexec_close.c
new file mode 100644
index 0000000..b1af338
--- /dev/null
+++ b/libexec_close.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_close(struct libexec_command *cmd, int fd)
+{
+ struct libexec_pluming pluming;
+ if (fd < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(&pluming, 0, sizeof(pluming));
+ pluming.fd = fd;
+ pluming.type = LIBEXEC_PLUMING_CLOSE;
+ return libexec_add_pluming(cmd, &pluming);
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_construct_command.c b/libexec_construct_command.c
new file mode 100644
index 0000000..36b18f9
--- /dev/null
+++ b/libexec_construct_command.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_construct_command(struct libexec_command *cmd, const char *fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, fmt);
+ ret = libexec_vconstruct_command(cmd, fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_copy_environ.c b/libexec_copy_environ.c
new file mode 100644
index 0000000..2c7e19f
--- /dev/null
+++ b/libexec_copy_environ.c
@@ -0,0 +1,51 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_copy_environ(struct libexec_command *cmd, const char *const *env)
+{
+ size_t i, n;
+ char **new_env;
+
+ if (!cmd) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!env)
+ env = *(const char *const **)(void *)&environ;
+
+ n = 0;
+ while (env[n])
+ n++;
+
+ new_env = calloc((n + 1), sizeof(*cmd->environ));
+ if (!new_env)
+ return -1;
+
+ for (i = 0; i < n; i++) {
+ new_env[i] = strdup(env[i]);
+ if (!new_env[i]) {
+ while (i--)
+ free(new_env[i]);
+ free(new_env);
+ return -1;
+ }
+ }
+
+ if (cmd->environ) {
+ for (i = 0; cmd->environ[i]; i++)
+ free(cmd->environ[i]);
+ free(cmd->environ);
+ }
+
+ cmd->environ = new_env;
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_destroy_command.c b/libexec_destroy_command.c
new file mode 100644
index 0000000..d5382fb
--- /dev/null
+++ b/libexec_destroy_command.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libexec_destroy_command(struct libexec_command *cmd)
+{
+ size_t i;
+
+ if (!cmd)
+ return;
+
+ free(cmd->executable);
+
+ for (i = 0; i < cmd->narguments; i++)
+ free(cmd->arguments[i]);
+ free(cmd->arguments);
+
+ for (i = 0; i < cmd->nplumings; i++)
+ libexec_destroy_pluming(&cmd->plumings[i]);
+ free(cmd->plumings);
+
+ if (cmd->environ) {
+ for (i = 0; cmd->environ[i]; i++)
+ free(cmd->environ[i]);
+ free(cmd->environ);
+ }
+
+ *cmd = LIBEXEC_COMMAND_INIT;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_destroy_document.c b/libexec_destroy_document.c
new file mode 100644
index 0000000..aee6298
--- /dev/null
+++ b/libexec_destroy_document.c
@@ -0,0 +1,28 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_destroy_document(struct libexec_document *doc)
+{
+ int ret = 0;
+
+ if (!doc)
+ return 0;
+
+ free(doc->text);
+ doc->text = NULL;
+ doc->length = 0;
+ doc->offset = 0;
+ if (doc->fd >= 0) {
+ ret = close(doc->fd);
+ doc->fd = -1;
+ }
+ return ret;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_destroy_pluming.c b/libexec_destroy_pluming.c
new file mode 100644
index 0000000..6aae3c7
--- /dev/null
+++ b/libexec_destroy_pluming.c
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libexec_destroy_pluming(struct libexec_pluming *pluming)
+{
+ if (!pluming)
+ return;
+
+ if (pluming->type == LIBEXEC_PLUMING_OPENAT ||
+ pluming->type == LIBEXEC_PLUMING_OPENAT2) {
+ free(pluming->target.file);
+ pluming->target.file = NULL;
+
+ } else if (pluming->type == LIBEXEC_PLUMING_DOCUMENT) {
+ free(pluming->target.text);
+ pluming->target.text = NULL;
+
+ } else if (pluming->type == LIBEXEC_PLUMING_PIPE) {
+ if (pluming->target.fd >= 0)
+ close(pluming->target.fd);
+ pluming->target.fd = -1;
+ }
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_destroy_result.c b/libexec_destroy_result.c
new file mode 100644
index 0000000..0587c3b
--- /dev/null
+++ b/libexec_destroy_result.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libexec_destroy_result(struct libexec_result *result)
+{
+ if (!result)
+ return;
+
+ free(result->exit_statuses);
+ result->exit_statuses = NULL;
+ result->proc_count = 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_dup.c b/libexec_dup.c
new file mode 100644
index 0000000..674acb7
--- /dev/null
+++ b/libexec_dup.c
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_dup(struct libexec_command *cmd, int fd, int old_fd)
+{
+ struct libexec_pluming pluming;
+ if (fd < 0 || old_fd < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(&pluming, 0, sizeof(pluming));
+ pluming.fd = fd;
+ pluming.type = LIBEXEC_PLUMING_DUP2;
+ pluming.target.fd = old_fd;
+ return libexec_add_pluming(cmd, &pluming);
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_exec.c b/libexec_exec.c
new file mode 100644
index 0000000..3f306d0
--- /dev/null
+++ b/libexec_exec.c
@@ -0,0 +1,200 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+static char *
+which(const char *name, char *path, char **end_colon_out)
+{
+ size_t namelen_nul, dirlen, maxlen;
+ char *p, *q, *buf;
+ int eacces_encountered = 0;
+
+ if (!path || !*path)
+ goto enoent;
+
+ maxlen = 0;
+ for (p = path; p; p = q) {
+ q = strchr(p, ':');
+ if (q)
+ dirlen = (size_t)(q++ - p);
+ else
+ dirlen = strlen(p);
+ if (dirlen > maxlen)
+ dirlen = maxlen;
+ }
+
+ namelen_nul = strlen(name) + 1;
+
+ buf = NULL;
+ if (maxlen) {
+ buf = malloc(maxlen + 1 + namelen_nul);
+ if (!buf)
+ return NULL;
+ }
+
+ for (; path; path = q) {
+ q = strchr(p, ':');
+ if (q)
+ dirlen = (size_t)(q++ - path);
+ else
+ dirlen = strlen(path);
+ p = buf;
+ if (dirlen) {
+ memcpy(p, path, dirlen);
+ p = &p[dirlen];
+ *p++ = '/';
+ }
+ memcpy(p, name, namelen_nul);
+ if (!faccessat(AT_FDCWD, buf, X_OK, AT_EACCESS)) {
+ *end_colon_out = q;
+ return buf;
+ }
+ if (errno == EACCES)
+ eacces_encountered = 1;
+ }
+
+ free(buf);
+
+ if (eacces_encountered) {
+ errno = EACCES;
+ return NULL;
+ }
+
+enoent:
+ errno = ENOENT;
+ return NULL;
+}
+
+
+static void
+exec_path(struct libexec_command *cmd)
+{
+ const char *file = cmd->executable ? cmd->executable : cmd->arguments[0];
+ execve(file, cmd->arguments, cmd->environ ? cmd->environ : environ);
+}
+
+
+static void
+exec_name(struct libexec_command *cmd)
+{
+ /* execvpe is non-standard */
+ const char *file = cmd->executable ? cmd->executable : cmd->arguments[0];
+ char *end_colon = NULL, *file_free = NULL;
+ if (!strchr(file, '/')) {
+ file = file_free = which(file, getenv("PATH"), &end_colon);
+ if (!file)
+ return;
+ }
+ execve(file, cmd->arguments, cmd->environ ? cmd->environ : environ);
+ free(file_free);
+ if (end_colon)
+ *end_colon = ':';
+}
+
+
+static void
+exec_fd(struct libexec_command *cmd)
+{
+ fexecve(cmd->exec_fd, cmd->arguments, cmd->environ ? cmd->environ : environ);
+}
+
+
+static void
+exec_at(struct libexec_command *cmd)
+{
+ const char *file = cmd->executable ? cmd->executable : cmd->arguments[0];
+ execveat(cmd->exec_fd, file, cmd->arguments, cmd->environ ? cmd->environ : environ, 0);
+}
+
+
+int
+libexec_exec(struct libexec_command *cmd)
+{
+ void (*exec_function)(struct libexec_command *cmd);
+ size_t i;
+ int fd;
+
+ if (!cmd)
+ goto einval;
+
+ if (cmd->library_version > LIBEXEC_VERSION)
+ abort();
+
+ if (!cmd->narguments)
+ goto einval;
+
+ if (cmd->exec_how == LIBEXEC_REQUIRE_PATH)
+ exec_function = &exec_path;
+ else if (cmd->exec_how == LIBEXEC_ALLOW_NAME)
+ exec_function = &exec_name;
+ else if (cmd->exec_how == LIBEXEC_EXEC_FD)
+ exec_function = &exec_fd;
+ else if (cmd->exec_how == LIBEXEC_EXEC_AT)
+ exec_function = &exec_at;
+ else
+ goto einval;
+
+ for (i = 0; i < cmd->nplumings; i++) {
+ switch (cmd->plumings[i].type) {
+ case LIBEXEC_PLUMING_CLOSE:
+ if (cmd->plumings[i].fd >= 0) {
+ close(cmd->plumings[i].fd);
+ cmd->plumings[i].fd = -1;
+ }
+ break;
+
+#if defined(__linux__)
+ case LIBEXEC_PLUMING_OPENAT2:
+ fd = (int)syscall(SYS_openat2,
+ cmd->plumings[i].target.dirfd, cmd->plumings[i].target.file,
+ cmd->plumings[i].target.how, cmd->plumings[i].target.how_size);
+ if (fd < 0)
+ return -1;
+ free(cmd->plumings[i].target.file);
+ free(cmd->plumings[i].target.how);
+ goto openned_fd;
+#endif
+
+ case LIBEXEC_PLUMING_OPENAT:
+ fd = openat(cmd->plumings[i].target.dirfd, cmd->plumings[i].target.file,
+ cmd->plumings[i].target.flags, cmd->plumings[i].target.mode);
+ if (fd < 0)
+ return -1;
+ free(cmd->plumings[i].target.file);
+#if defined(__linux__)
+ openned_fd:
+#endif
+ cmd->plumings[i].type = LIBEXEC_PLUMING_PIPE;
+ cmd->plumings[i].target.fd = fd;
+ /* fall through */
+
+ case LIBEXEC_PLUMING_DUP2:
+ case LIBEXEC_PLUMING_PIPE:
+ if (cmd->plumings[i].fd == cmd->plumings[i].target.fd)
+ break;
+ if (dup2(cmd->plumings[i].target.fd, cmd->plumings[i].fd) == -1)
+ return -1;
+ if (cmd->plumings[i].type == LIBEXEC_PLUMING_PIPE)
+ close(cmd->plumings[i].target.fd);
+ cmd->plumings[i].target.fd = cmd->plumings[i].fd;
+ break;
+
+ default:
+ case LIBEXEC_PLUMING_DOCUMENT:
+ goto einval;
+ }
+ }
+
+ (*exec_function)(cmd);
+ return -1;
+
+einval:
+ errno = EINVAL;
+ return -1;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_get_documents.c b/libexec_get_documents.c
new file mode 100644
index 0000000..2bdadf8
--- /dev/null
+++ b/libexec_get_documents.c
@@ -0,0 +1,83 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_get_documents(struct libexec_command *cmd, struct libexec_document **docsp, size_t *ndocsp, int flags)
+{
+ typedef int fdpair[2];
+ size_t i, j, n;
+ void *new;
+ fdpair *pipes;
+ int error;
+ int fd_flags, fl_flags;
+
+ if (!cmd || !docsp || !ndocsp) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd_flags = (flags & O_CLOEXEC);
+ fl_flags = (flags ^ fd_flags);
+ if (fd_flags)
+ fd_flags = FD_CLOEXEC;
+
+ n = 0;
+ for (i = 0; i < cmd->nplumings; i++)
+ if (cmd->plumings[i].type == LIBEXEC_PLUMING_DOCUMENT)
+ n += 1;
+
+ pipes = malloc(n * sizeof(*pipes));
+ if (!pipes)
+ return -1;
+
+ j = 0;
+ for (i = 0; i < cmd->nplumings; i++) {
+ if (cmd->plumings[i].type == LIBEXEC_PLUMING_DOCUMENT) {
+ if (pipe(pipes[j]))
+ goto fail;
+ j++;
+ if (fd_flags && fcntl(pipes[j - 1][1], F_SETFD, fd_flags))
+ goto fail;
+ if (fl_flags && fcntl(pipes[j - 1][1], F_SETFL, fl_flags))
+ goto fail;
+ }
+ }
+
+ new = realloc(*docsp, (*ndocsp + n) * sizeof(**docsp));
+ if (!new)
+ goto fail;
+ *docsp = new;
+
+ for (i = 0, j = 0; i < cmd->nplumings; i++) {
+ if (cmd->plumings[i].type != LIBEXEC_PLUMING_DOCUMENT)
+ continue;
+ (*docsp)[j].user = 0;
+ (*docsp)[j].fd = pipes[j][1];
+ (*docsp)[j].text = cmd->plumings[i].target.text;
+ (*docsp)[j].length = cmd->plumings[i].target.len;
+ (*docsp)[j].offset = 0;
+ cmd->plumings[i].target.fd = pipes[j][0];
+ cmd->plumings[i].type = LIBEXEC_PLUMING_PIPE;
+ j++;
+ }
+
+ free(pipes);
+ return 0;
+
+fail:
+ error = errno;
+ while (j--) {
+ close(pipes[j][0]);
+ close(pipes[j][1]);
+ }
+ free(pipes);
+ errno = error;
+ return -1;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_getenv.c b/libexec_getenv.c
new file mode 100644
index 0000000..5099b28
--- /dev/null
+++ b/libexec_getenv.c
@@ -0,0 +1,122 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+const char *
+libexec_getenv(struct libexec_command *cmd, const char *name)
+{
+ char **env;
+ size_t i, len;
+
+ if (!cmd || !name) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (strchr(name, '='))
+ return NULL;
+
+ env = cmd->environ ? cmd->environ : environ;
+ len = strlen(name);
+
+ for (i = 0; env[i]; i++)
+ if (!strncmp(env[i], name, len) && env[i][len] == '=')
+ return &env[i][len + 1];
+
+ return NULL;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ env = environ;
+ environ = calloc(12, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("AA=aa");
+ environ[1] = strdup("A=a0");
+ environ[2] = strdup("B=b0");
+ environ[3] = strdup("A=a1");
+ environ[4] = strdup("B=b1");
+ environ[5] = strdup("A=a2");
+ environ[6] = strdup("B=b2");
+ environ[7] = strdup("A=a3");
+ environ[8] = strdup("B=b3");
+ environ[9] = strdup("BB=bb");
+ environ[10] = strdup("X=Y=Z");
+ ASSERT_NOT_NULL(environ[0]);
+ ASSERT_NOT_NULL(environ[1]);
+ ASSERT_NOT_NULL(environ[2]);
+ ASSERT_NOT_NULL(environ[3]);
+ ASSERT_NOT_NULL(environ[4]);
+ ASSERT_NOT_NULL(environ[5]);
+ ASSERT_NOT_NULL(environ[6]);
+ ASSERT_NOT_NULL(environ[7]);
+ ASSERT_NOT_NULL(environ[8]);
+ ASSERT_NOT_NULL(environ[9]);
+ ASSERT_NOT_NULL(environ[10]);
+ ASSERT_IS_NULL(environ[11]);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv(&cmd, "PATH"));
+ ASSERT_IS_TRUE(errno == 0);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv(NULL, "x"));
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv(&cmd, "x"));
+ ASSERT_IS_TRUE(errno == 0);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv(&cmd, NULL));
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv(&cmd, "X=Y"));
+ ASSERT_IS_TRUE(errno == 0 || errno == EINVAL);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv(&cmd, ""));
+ ASSERT_IS_TRUE(errno == 0);
+
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "X"), "Y=Z");
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "AA"), "aa");
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "BB"), "bb");
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "A"), "a0");
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "B"), "b0");
+
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ libexec_destroy_command(&cmd);
+
+ ASSERT_IS_NULL(libexec_getenv(&cmd, "PATH"));
+ ASSERT_IS_NULL(libexec_getenv(NULL, "x"));
+ ASSERT_IS_NULL(libexec_getenv(&cmd, "x"));
+ ASSERT_IS_NULL(libexec_getenv(&cmd, NULL));
+ ASSERT_IS_NULL(libexec_getenv(&cmd, "X=Y"));
+ ASSERT_IS_NULL(libexec_getenv(&cmd, ""));
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "X"), "Y=Z");
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "AA"), "aa");
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "BB"), "bb");
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "A"), "a0");
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "B"), "b0");
+
+ libexec_set_environ(&cmd, environ);
+ environ = env;
+
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_getenv_last.c b/libexec_getenv_last.c
new file mode 100644
index 0000000..1de318e
--- /dev/null
+++ b/libexec_getenv_last.c
@@ -0,0 +1,123 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+const char *
+libexec_getenv_last(struct libexec_command *cmd, const char *name)
+{
+ char **env;
+ size_t i, len;
+ const char *ret = NULL;
+
+ if (!cmd || !name) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (strchr(name, '='))
+ return NULL;
+
+ env = cmd->environ ? cmd->environ : environ;
+ len = strlen(name);
+
+ for (i = 0; env[i]; i++)
+ if (!strncmp(env[i], name, len) && env[i][len] == '=')
+ ret = &env[i][len + 1];
+
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ env = environ;
+ environ = calloc(12, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("AA=aa");
+ environ[1] = strdup("A=a0");
+ environ[2] = strdup("B=b0");
+ environ[3] = strdup("A=a1");
+ environ[4] = strdup("B=b1");
+ environ[5] = strdup("A=a2");
+ environ[6] = strdup("B=b2");
+ environ[7] = strdup("A=a3");
+ environ[8] = strdup("B=b3");
+ environ[9] = strdup("BB=bb");
+ environ[10] = strdup("X=Y=Z");
+ ASSERT_NOT_NULL(environ[0]);
+ ASSERT_NOT_NULL(environ[1]);
+ ASSERT_NOT_NULL(environ[2]);
+ ASSERT_NOT_NULL(environ[3]);
+ ASSERT_NOT_NULL(environ[4]);
+ ASSERT_NOT_NULL(environ[5]);
+ ASSERT_NOT_NULL(environ[6]);
+ ASSERT_NOT_NULL(environ[7]);
+ ASSERT_NOT_NULL(environ[8]);
+ ASSERT_NOT_NULL(environ[9]);
+ ASSERT_NOT_NULL(environ[10]);
+ ASSERT_IS_NULL(environ[11]);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, "PATH"));
+ ASSERT_IS_TRUE(errno == 0);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv_last(NULL, "x"));
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, "x"));
+ ASSERT_IS_TRUE(errno == 0);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, NULL));
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, "X=Y"));
+ ASSERT_IS_TRUE(errno == 0 || errno == EINVAL);
+
+ errno = 0;
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, ""));
+ ASSERT_IS_TRUE(errno == 0);
+
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "X"), "Y=Z");
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "AA"), "aa");
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "BB"), "bb");
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "A"), "a3");
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "B"), "b3");
+
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ libexec_destroy_command(&cmd);
+
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, "PATH"));
+ ASSERT_IS_NULL(libexec_getenv_last(NULL, "x"));
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, "x"));
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, NULL));
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, "X=Y"));
+ ASSERT_IS_NULL(libexec_getenv_last(&cmd, ""));
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "X"), "Y=Z");
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "AA"), "aa");
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "BB"), "bb");
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "A"), "a3");
+ ASSERT_EQ_STR(libexec_getenv_last(&cmd, "B"), "b3");
+
+ libexec_set_environ(&cmd, environ);
+ environ = env;
+
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_init_command.c b/libexec_init_command.c
new file mode 100644
index 0000000..c49af27
--- /dev/null
+++ b/libexec_init_command.c
@@ -0,0 +1,43 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libexec_init_command(struct libexec_command *cmd)
+{
+ if (!cmd)
+ return;
+
+ *cmd = LIBEXEC_COMMAND_INIT;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd;
+
+ memset(&cmd, 127, sizeof(cmd));
+ libexec_init_command(&cmd);
+
+ ASSERT_EQ_INT(cmd.library_version, LIBEXEC_VERSION);
+ ASSERT_EQ_ENUM(cmd.exec_how, LIBEXEC_ALLOW_NAME);
+ ASSERT_EQ_INT(cmd.exec_fd, -1);
+ ASSERT_IS_NULL(cmd.executable);
+ ASSERT_IS_NULL(cmd.arguments);
+ ASSERT_EQ_UINT(cmd.narguments, 0);
+ ASSERT_IS_NULL(cmd.plumings);
+ ASSERT_EQ_UINT(cmd.nplumings, 0);
+ ASSERT_IS_NULL(cmd.environ);
+
+ libexec_init_command(NULL);
+
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_input_data_copy.c b/libexec_input_data_copy.c
new file mode 100644
index 0000000..bb401e0
--- /dev/null
+++ b/libexec_input_data_copy.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_input_data_copy(struct libexec_command *cmd, int fd, const char *data, size_t len)
+{
+ char *copy = malloc(len);
+ if (!copy)
+ return -1;
+ memcpy(copy, data, len);
+ if (libexec_input_data_gift(cmd, fd, copy, len)) {
+ free(copy);
+ return -1;
+ }
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_input_data_gift.c b/libexec_input_data_gift.c
new file mode 100644
index 0000000..77ee80f
--- /dev/null
+++ b/libexec_input_data_gift.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_input_data_gift(struct libexec_command *cmd, int fd, char *data, size_t len)
+{
+ struct libexec_pluming pluming;
+ if (fd < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(&pluming, 0, sizeof(pluming));
+ pluming.fd = fd;
+ pluming.type = LIBEXEC_PLUMING_DOCUMENT;
+ pluming.target.text = data;
+ pluming.target.len = len;
+ return libexec_add_pluming(cmd, &pluming);
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_input_text_copy.c b/libexec_input_text_copy.c
new file mode 100644
index 0000000..da12bfd
--- /dev/null
+++ b/libexec_input_text_copy.c
@@ -0,0 +1,19 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_input_text_copy(struct libexec_command *cmd, int fd, const char *text)
+{
+ if (!text) {
+ errno = EINVAL;
+ return -1;
+ }
+ return libexec_input_data_copy(cmd, fd, text, strlen(text));
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_input_text_gift.c b/libexec_input_text_gift.c
new file mode 100644
index 0000000..94b09c3
--- /dev/null
+++ b/libexec_input_text_gift.c
@@ -0,0 +1,19 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_input_text_gift(struct libexec_command *cmd, int fd, char *text)
+{
+ if (!text) {
+ errno = EINVAL;
+ return -1;
+ }
+ return libexec_input_data_gift(cmd, fd, text, strlen(text));
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_open.c b/libexec_open.c
new file mode 100644
index 0000000..7aa2698
--- /dev/null
+++ b/libexec_open.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_open(struct libexec_command *cmd, int fd, const char *file, int flags, mode_t mode)
+{
+ return libexec_openat(cmd, fd, AT_FDCWD, file, flags, mode);
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_openat.c b/libexec_openat.c
new file mode 100644
index 0000000..41498ba
--- /dev/null
+++ b/libexec_openat.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_openat(struct libexec_command *cmd, int fd, int dirfd, const char *file, int flags, mode_t mode)
+{
+ struct libexec_pluming pluming;
+ if (fd < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(&pluming, 0, sizeof(pluming));
+ pluming.fd = fd;
+ pluming.type = LIBEXEC_PLUMING_OPENAT;
+ pluming.target.dirfd = dirfd;
+ pluming.target.flags = flags;
+ pluming.target.mode = mode;
+ pluming.target.file = NULL;
+ if (file) {
+ pluming.target.file = strdup(file);
+ if (!pluming.target.file)
+ return -1;
+ }
+ if (libexec_add_pluming(cmd, &pluming)) {
+ free(pluming.target.file);
+ return -1;
+ }
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_openat2.c b/libexec_openat2.c
new file mode 100644
index 0000000..ff97bff
--- /dev/null
+++ b/libexec_openat2.c
@@ -0,0 +1,54 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_openat2(struct libexec_command *cmd, int fd, int dirfd, const char *file, struct open_how *how, size_t size)
+{
+#if defined(__linux__)
+
+ struct libexec_pluming pluming;
+ if (fd < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(&pluming, 0, sizeof(pluming));
+ pluming.fd = fd;
+ pluming.type = LIBEXEC_PLUMING_OPENAT2;
+ pluming.target.dirfd = dirfd;
+ pluming.target.how = NULL;
+ pluming.target.file = NULL;
+ if (file) {
+ pluming.target.file = strdup(file);
+ if (!pluming.target.file)
+ return -1;
+ }
+ if (size) {
+ pluming.target.how = malloc(size);
+ if (!pluming.target.how) {
+ free(pluming.target.file);
+ return -1;
+ }
+ memcpy(pluming.target.how, how, size);
+ pluming.target.how_size = size;
+ }
+ if (libexec_add_pluming(cmd, &pluming)) {
+ free(pluming.target.file);
+ free(pluming.target.how);
+ return -1;
+ }
+ return 0;
+
+#else
+
+ errno = ENOSYS;
+ return -1;
+
+#endif
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_pipe_commands.c b/libexec_pipe_commands.c
new file mode 100644
index 0000000..580bf86
--- /dev/null
+++ b/libexec_pipe_commands.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_pipe_commands(enum libexec_pipe how, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, how);
+ ret = libexec_vpipe_commands(how, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_pipe_commandsv.c b/libexec_pipe_commandsv.c
new file mode 100644
index 0000000..ee96b7d
--- /dev/null
+++ b/libexec_pipe_commandsv.c
@@ -0,0 +1,22 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_pipe_commandsv(enum libexec_pipe how, struct libexec_command *const *cmds)
+{
+ size_t n = 0;
+ if (!cmds) {
+ errno = EINVAL;
+ return -1;
+ }
+ while (cmds[n])
+ n++;
+ return libexec_pipe_commandsvn(how, cmds, n);
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_pipe_commandsvn.c b/libexec_pipe_commandsvn.c
new file mode 100644
index 0000000..d6aa1d5
--- /dev/null
+++ b/libexec_pipe_commandsvn.c
@@ -0,0 +1,106 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+static void
+remove_last_pluming(struct libexec_command *cmd)
+{
+ if (!cmd->nplumings || !cmd->plumings)
+ abort();
+ if (cmd->plumings[--cmd->nplumings].type != LIBEXEC_PLUMING_PIPE)
+ abort();
+ close(cmd->plumings[cmd->nplumings].target.fd);
+ if (!cmd->nplumings) {
+ free(cmd->plumings);
+ cmd->plumings = NULL;
+ }
+}
+
+
+static int
+pipe_two_commands(enum libexec_pipe how, struct libexec_command *left, struct libexec_command *right)
+{
+ int fds[2], r;
+ struct libexec_pluming tmp;
+
+ if (!left || !right) {
+ einval:
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (how == LIBEXEC_PIPE)
+ r = pipe(fds);
+ else if (how == LIBEXEC_SOCK_STREAM)
+ r = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+ else if (how == LIBEXEC_SOCK_SEQPACKET)
+ r = socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds);
+ else if (how == LIBEXEC_SOCK_DGRAM)
+ r = socketpair(PF_UNIX, SOCK_DGRAM, 0, fds);
+ else if (how == LIBEXEC_DIRECT_PIPE)
+ r = pipe2(fds, O_DIRECT);
+ else
+ goto einval;
+ if (r)
+ return -1;
+
+ if (libexec_dup(left, STDOUT_FILENO, fds[1])) {
+ close(fds[0]);
+ close(fds[1]);
+ return -1;
+ }
+ left->plumings[left->nplumings - 1].type = LIBEXEC_PLUMING_PIPE;
+
+ if (libexec_dup(right, STDIN_FILENO, fds[0])) {
+ remove_last_pluming(left);
+ close(fds[0]);
+ return -1;
+ }
+ right->plumings[right->nplumings - 1].type = LIBEXEC_PLUMING_PIPE;
+
+ /* pluming shall be at the front */
+ tmp = left->plumings[left->nplumings - 1];
+ memcpy(&left->plumings[1], &left->plumings[0], (left->nplumings - 1) * sizeof(*left->plumings));
+ left->plumings[0] = tmp;
+ tmp = right->plumings[right->nplumings - 1];
+ memcpy(&right->plumings[1], &right->plumings[0], (right->nplumings - 1) * sizeof(*right->plumings));
+ right->plumings[0] = tmp;
+
+ return 0;
+}
+
+
+int
+libexec_pipe_commandsvn(enum libexec_pipe how, struct libexec_command *const *cmds, size_t n)
+{
+ enum libexec_pipe circular;
+ size_t i;
+
+ circular = how;
+ circular &= LIBEXEC_PIPE_CIRCULARLY;
+ how ^= circular;
+
+ for (i = 1; i < n; i++)
+ if (pipe_two_commands(how, cmds[i - 1], cmds[i]))
+ goto fail;
+
+ if (circular && n)
+ if (pipe_two_commands(how, cmds[n - 1], cmds[0]))
+ goto fail;
+
+ return 0;
+
+fail:
+ i -= 1;
+ while (i) {
+ remove_last_pluming(cmds[i--]);
+ remove_last_pluming(cmds[i]);
+ }
+ return -1;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_put_arguments.c b/libexec_put_arguments.c
new file mode 100644
index 0000000..45f7f1a
--- /dev/null
+++ b/libexec_put_arguments.c
@@ -0,0 +1,22 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_put_arguments(struct libexec_command *cmd, const char *const *args)
+{
+ size_t n = 0;
+ if (!args) {
+ errno = EINVAL;
+ return -1;
+ }
+ while (args[n])
+ n += 1;
+ return libexec_put_argumentsn(cmd, args, n);
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_put_argumentsn.c b/libexec_put_argumentsn.c
new file mode 100644
index 0000000..9d09ec0
--- /dev/null
+++ b/libexec_put_argumentsn.c
@@ -0,0 +1,45 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_put_argumentsn(struct libexec_command *cmd, const char *const *args, size_t nargs)
+{
+ size_t i;
+ void *new;
+
+ if (!cmd) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ new = realloc(cmd->arguments, (cmd->narguments + nargs + 1) * sizeof(*cmd->arguments));
+ if (!new)
+ return -1;
+ cmd->arguments = new;
+
+ for (i = 0; i < nargs; i++) {
+ cmd->arguments[cmd->narguments + i] = strdup(args[i]);
+ if (!cmd->arguments[cmd->narguments + i]) {
+ while (i)
+ free(cmd->arguments[cmd->narguments + --i]);
+ if (!cmd->narguments) {
+ free(cmd->arguments);
+ cmd->arguments = NULL;
+ }
+ return -1;
+ }
+ }
+
+ cmd->arguments[cmd->narguments + nargs] = NULL;
+ cmd->narguments += nargs;
+
+ return 0;
+
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_putenv.c b/libexec_putenv.c
new file mode 100644
index 0000000..366f5fb
--- /dev/null
+++ b/libexec_putenv.c
@@ -0,0 +1,144 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenv(struct libexec_command *cmd, enum libexec_insert_mode how, const char *string)
+{
+ char **env;
+ char *copy, *eqsign;
+ size_t i, n, len;
+ int environ_copied = 0;
+ void *new;
+
+ if (!cmd || !string) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ env = cmd->environ ? cmd->environ : environ;
+
+ if (how == LIBEXEC_APPEND || how == LIBEXEC_PREPEND) {
+ n = 0;
+ while (env[n])
+ n++;
+
+ just_add:
+ copy = strdup(string);
+ if (!copy)
+ return -1;
+
+ if (!cmd->environ) {
+ if (libexec_copy_environ(cmd, NULL)) {
+ free(copy);
+ return -1;
+ }
+ environ_copied = 1;
+ }
+
+ new = realloc(cmd->environ, (n + 2) * sizeof(*cmd->environ));
+ if (!new) {
+ if (environ_copied) {
+ while (n)
+ free(cmd->environ[--n]);
+ free(cmd->environ);
+ cmd->environ = NULL;
+ }
+ free(copy);
+ return -1;
+ }
+ cmd->environ = new;
+
+ if (how == LIBEXEC_APPEND) {
+ cmd->environ[n] = copy;
+ cmd->environ[n + 1] = NULL;
+ } else {
+ memmove(&cmd->environ[1], cmd->environ, (n + 1) * sizeof(*cmd->environ));
+ cmd->environ[0] = copy;
+ }
+
+
+ } else {
+ eqsign = strchr(string, '=');
+
+ if (!eqsign || (how != LIBEXEC_REPLACE && how != LIBEXEC_NOCLOBBER && how != LIBEXEC_NOREPLACE)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ len = (size_t)(eqsign - string) + 1;
+ for (i = 0; env[i]; i++)
+ if (!strncmp(env[i], string, len))
+ break;
+
+ if (!env[i]) {
+ n = i;
+ how = LIBEXEC_APPEND;
+ goto just_add;
+ }
+
+ if (how == LIBEXEC_NOREPLACE || !strcmp(env[i], string)) {
+ if (!cmd->environ) {
+ if (libexec_copy_environ(cmd, NULL)) {
+ free(copy);
+ return -1;
+ }
+ }
+ return 0;
+ }
+ if (how == LIBEXEC_NOCLOBBER)
+ return -1; /* do not change errno */
+
+ copy = strdup(string);
+ if (!copy)
+ return -1;
+
+ if (!cmd->environ) {
+ if (libexec_copy_environ(cmd, NULL)) {
+ free(copy);
+ return -1;
+ }
+ }
+
+ free(cmd->environ[i]);
+ cmd->environ[i] = copy;
+
+ }
+ return 0;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ /* Correct usage is tested via
+ * libexec_putenv_append.c,
+ * libexec_putenv_noclobber.c,
+ * libexec_putenv_noreplace.c,
+ * libexec_putenv_prepend.c, and
+ * libexec_putenv_replace.c */
+
+ struct libexec_command cmd, ref;
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv(&cmd, (enum libexec_insert_mode)-1, "A=B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv(&cmd, (enum libexec_insert_mode)LIBEXEC_INSERT_MODE__COUNT__, "A=B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenv_append.c b/libexec_putenv_append.c
new file mode 100644
index 0000000..bb45e1c
--- /dev/null
+++ b/libexec_putenv_append.c
@@ -0,0 +1,235 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenv_append(struct libexec_command *cmd, const char *string)
+{
+ return libexec_putenv(cmd, LIBEXEC_APPEND, string);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_append(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "X"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "B=F");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "X"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ ASSERT_ZERO(libexec_putenv_append(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[7]);
+ ASSERT_EQ_STR(cmd.environ[7], "B=F");
+ ASSERT_IS_NULL(cmd.environ[8]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenv_noclobber.c b/libexec_putenv_noclobber.c
new file mode 100644
index 0000000..e9cf3ed
--- /dev/null
+++ b/libexec_putenv_noclobber.c
@@ -0,0 +1,175 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenv_noclobber(struct libexec_command *cmd, const char *string)
+{
+ return libexec_putenv(cmd, LIBEXEC_NOCLOBBER, string);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_noclobber(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_noclobber(&cmd, NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_noclobber(&cmd, "X"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_noclobber(&cmd, "A=D"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_noclobber(&cmd, "A=D"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenv_noclobber(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenv_noreplace.c b/libexec_putenv_noreplace.c
new file mode 100644
index 0000000..b33708a
--- /dev/null
+++ b/libexec_putenv_noreplace.c
@@ -0,0 +1,171 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenv_noreplace(struct libexec_command *cmd, const char *string)
+{
+ return libexec_putenv(cmd, LIBEXEC_NOREPLACE, string);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_noreplace(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_noreplace(&cmd, NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_noreplace(&cmd, "X"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenv_noreplace(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenv_prepend.c b/libexec_putenv_prepend.c
new file mode 100644
index 0000000..840978d
--- /dev/null
+++ b/libexec_putenv_prepend.c
@@ -0,0 +1,235 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenv_prepend(struct libexec_command *cmd, const char *string)
+{
+ return libexec_putenv(cmd, LIBEXEC_PREPEND, string);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_prepend(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "X"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "X");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "B=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "X");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "X=0");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "X=0");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "X=0");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X=0");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "X"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X=0");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "X");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "X=0");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ ASSERT_ZERO(libexec_putenv_prepend(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "B=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "X");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[7]);
+ ASSERT_EQ_STR(cmd.environ[7], "X=0");
+ ASSERT_IS_NULL(cmd.environ[8]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenv_replace.c b/libexec_putenv_replace.c
new file mode 100644
index 0000000..c62ad01
--- /dev/null
+++ b/libexec_putenv_replace.c
@@ -0,0 +1,171 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenv_replace(struct libexec_command *cmd, const char *string)
+{
+ return libexec_putenv(cmd, LIBEXEC_REPLACE, string);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_replace(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_replace(&cmd, NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenv_replace(&cmd, "X"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenv_replace(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenvf.c b/libexec_putenvf.c
new file mode 100644
index 0000000..7729fc4
--- /dev/null
+++ b/libexec_putenvf.c
@@ -0,0 +1,61 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenvf(struct libexec_command *cmd, enum libexec_insert_mode how, const char *fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, fmt);
+ ret = libexec_vputenvf(cmd, how, fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ /* Correct usage is tested, indirectly, via
+ * libexec_putenvf_append.c,
+ * libexec_putenvf_noclobber.c,
+ * libexec_putenvf_noreplace.c,
+ * libexec_putenvf_prepend.c, and
+ * libexec_putenvf_replace.c */
+
+ struct libexec_command cmd, ref;
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf(&cmd, (enum libexec_insert_mode)-1, "A=%s", "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf(&cmd, (enum libexec_insert_mode)LIBEXEC_INSERT_MODE__COUNT__, "A=%s", "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ libexec_clear_environ(&cmd);
+ ASSERT_ZERO(libexec_putenvf(&cmd, LIBEXEC_NOCLOBBER, "PATH=%s:%s", "test", "path"));
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "PATH"), "test:path");
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf(&cmd, LIBEXEC_NOCLOBBER, "PATH=%s", "x"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "PATH"), "test:path");
+ ASSERT_ZERO(libexec_putenvf(&cmd, LIBEXEC_REPLACE, "PATH=%s%s", "my", "path"));
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "PATH"), "mypath");
+ libexec_destroy_command(&cmd);
+
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenvf_append.c b/libexec_putenvf_append.c
new file mode 100644
index 0000000..8ceb806
--- /dev/null
+++ b/libexec_putenvf_append.c
@@ -0,0 +1,240 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenvf_append(struct libexec_command *cmd, const char *fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, fmt);
+ ret = libexec_vputenvf_append(cmd, fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_append(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "%s", "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "%s%s", "A=", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "AA%sE", "="));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "X"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "%s%s%s%s", "B", "B", "=", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "B=%s", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "B=F");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "%s=%s=%s", "A", "B", "C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "A%sD", "="));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "%s", "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "%s%s%s", "A", "A=", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "X"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ ASSERT_ZERO(libexec_putenvf_append(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[7]);
+ ASSERT_EQ_STR(cmd.environ[7], "B=F");
+ ASSERT_IS_NULL(cmd.environ[8]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenvf_noclobber.c b/libexec_putenvf_noclobber.c
new file mode 100644
index 0000000..988e381
--- /dev/null
+++ b/libexec_putenvf_noclobber.c
@@ -0,0 +1,180 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenvf_noclobber(struct libexec_command *cmd, const char *fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, fmt);
+ ret = libexec_vputenvf_noclobber(cmd, fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_noclobber(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_noclobber(&cmd, NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_noclobber(&cmd, "%s", "X"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "%s%s%s", "A", "=", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "%s=%s", "A=B", "C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_noclobber(&cmd, "%s", "A=D"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "AA%sE", "="));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "%sB=F", "B"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "%sB=F", ""));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_noclobber(&cmd, "A=D"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenvf_noclobber(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenvf_noreplace.c b/libexec_putenvf_noreplace.c
new file mode 100644
index 0000000..bcde8a2
--- /dev/null
+++ b/libexec_putenvf_noreplace.c
@@ -0,0 +1,176 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenvf_noreplace(struct libexec_command *cmd, const char *fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, fmt);
+ ret = libexec_vputenvf_noreplace(cmd, fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_noreplace(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_noreplace(&cmd, NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_noreplace(&cmd, "%s", "X"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "%s", "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "%s=%s", "A=B", "C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "A=%s", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "AA%sE", "="));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "%s", "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenvf_noreplace(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenvf_prepend.c b/libexec_putenvf_prepend.c
new file mode 100644
index 0000000..2e51a72
--- /dev/null
+++ b/libexec_putenvf_prepend.c
@@ -0,0 +1,240 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenvf_prepend(struct libexec_command *cmd, const char *fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, fmt);
+ ret = libexec_vputenvf_prepend(cmd, fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_prepend(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "%s", "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "%s%s", "A=", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "AA%sE", "="));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "X"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "%s%s%s%s", "B", "B", "=", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "X");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "B=%s", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "B=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "X");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "%s=%s=%s", "A", "B", "C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "X=0");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "A%sD", "="));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "X=0");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "%s", "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "X=0");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "%s%s%s", "A", "A=", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X=0");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "X"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X=0");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "X");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "X=0");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ ASSERT_ZERO(libexec_putenvf_prepend(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "B=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "X");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[7]);
+ ASSERT_EQ_STR(cmd.environ[7], "X=0");
+ ASSERT_IS_NULL(cmd.environ[8]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_putenvf_replace.c b/libexec_putenvf_replace.c
new file mode 100644
index 0000000..7cfd773
--- /dev/null
+++ b/libexec_putenvf_replace.c
@@ -0,0 +1,176 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_putenvf_replace(struct libexec_command *cmd, const char *fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, fmt);
+ ret = libexec_vputenvf_replace(cmd, fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_replace(NULL, "X=Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_replace(&cmd, NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_putenvf_replace(&cmd, "X"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "%s", "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "%s%s", "A=B", "=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "%s=%s", "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "%s=E", "AA"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "BB%sF", "="));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "A=B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "A=D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "AA=E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "BB=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_putenvf_replace(&cmd, "B=F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_recv_document.c b/libexec_recv_document.c
new file mode 100644
index 0000000..8eb0be6
--- /dev/null
+++ b/libexec_recv_document.c
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_recv_document(struct libexec_document *doc)
+{
+ ssize_t r;
+ void *new;
+ size_t new_size;
+
+ if (!doc) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (;;) {
+ if (doc->length == doc->alloc_size) {
+ new_size = doc->alloc_size + 8096;
+ new = realloc(doc->text, new_size);
+ if (!new)
+ return -1;
+ doc->text = new;
+ doc->alloc_size = new_size;
+ }
+ r = read(doc->fd, &doc->text[doc->length], doc->alloc_size - doc->length);
+ if (r <= 0) {
+ if (!r)
+ goto done;
+ return -1;
+ }
+ doc->length += (size_t)r;
+ }
+ return 0;
+
+done:
+ if (doc->length == doc->alloc_size) {
+ new_size = doc->alloc_size + 1;
+ new = realloc(doc->text, new_size);
+ if (!new)
+ return -1;
+ doc->text = new;
+ doc->alloc_size = new_size;
+ }
+ doc->text[doc->length] = '\0';
+ return 1;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_renumber_fd.c b/libexec_renumber_fd.c
new file mode 100644
index 0000000..d9350fa
--- /dev/null
+++ b/libexec_renumber_fd.c
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_renumber_fd(struct libexec_command *cmd, int fd, int old_fd)
+{
+ if (fd == old_fd) {
+ if (!cmd || fd < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+ }
+ if (libexec_dup(cmd, fd, old_fd))
+ return -1;
+ if (libexec_close(cmd, old_fd)) {
+ cmd->nplumings -= 1;
+ if (!cmd->nplumings) {
+ free(cmd->plumings);
+ cmd->plumings = NULL;
+ }
+ return -1;
+ }
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_run.c b/libexec_run.c
new file mode 100644
index 0000000..ca1f629
--- /dev/null
+++ b/libexec_run.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_run(struct libexec_result *out, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, out);
+ ret = libexec_vrun(out, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_run_pipeline.c b/libexec_run_pipeline.c
new file mode 100644
index 0000000..616f172
--- /dev/null
+++ b/libexec_run_pipeline.c
@@ -0,0 +1,343 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+struct reap_context {
+ pid_t *pids;
+ size_t npids;
+ size_t procs_rem;
+ int *exit_statuses_out; /* nullable */
+};
+
+
+struct reaped {
+ pid_t pid;
+ int status;
+};
+
+
+static struct reaped *reaped_elsewhere = NULL;
+static size_t nreaped_elsewhere = 0;
+static size_t reaped_elsewhere_size = 0;
+
+
+static void
+sigchld_handler(int signo)
+{
+ (void) signo;
+}
+
+
+static int
+unlist_reap(pid_t pid, int *status)
+{
+ size_t i;
+
+ for (i = 0; i < nreaped_elsewhere; i++)
+ if (reaped_elsewhere[i].pid == pid)
+ goto found;
+ return 0;
+
+found:
+ *status = reaped_elsewhere[i].status;
+ reaped_elsewhere[i] = reaped_elsewhere[--nreaped_elsewhere];
+
+ if (nreaped_elsewhere <= reaped_elsewhere_size / 4) {
+ free(reaped_elsewhere);
+ reaped_elsewhere = NULL;
+ nreaped_elsewhere = 0;
+ reaped_elsewhere_size = 0;
+ }
+
+ return 1;
+}
+
+
+static int
+reap_child(struct reap_context *ctx, pid_t pid)
+{
+ size_t i;
+ int status;
+
+ if (!ctx->procs_rem)
+ return 0;
+
+ for (i = 0; i < ctx->npids; i++) {
+ if (ctx->pids[i] == pid) {
+ if (waitpid(pid, &status, WNOHANG) != pid)
+ return -1;
+ if (ctx->exit_statuses_out)
+ ctx->exit_statuses_out[i] = status;
+ ctx->procs_rem -= 1;
+ ctx->pids[i] = -1;
+ while (ctx->npids && ctx->pids[ctx->npids - 1] == -1)
+ ctx->npids -= 1;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+reap_children(struct reap_context *reapctx, int flags,
+ int (*reap_mutex_control)(int action /* -1 = acquire, +1 = release */, void *user4), void *user4,
+ int (*on_alien_child_death)(pid_t pid, void *user2), void *user2)
+{
+ siginfo_t info;
+ int r, status;
+ void *new;
+ size_t new_size;
+ size_t i;
+
+ if (reap_mutex_control)
+ if ((*reap_mutex_control)(-1, user4))
+ goto fail;
+
+ if (nreaped_elsewhere) {
+ for (i = 0; i < reapctx->npids; i++) {
+ if (unlist_reap(reapctx->pids[i], &status)) {
+ if (reapctx->exit_statuses_out)
+ reapctx->exit_statuses_out[i] = status;
+ reapctx->procs_rem -= 1;
+ reapctx->pids[i] = -1;
+ }
+ }
+ while (reapctx->npids && reapctx->pids[reapctx->npids - 1] == -1)
+ reapctx->npids -= 1;
+ }
+
+ for (;;) {
+ info.si_pid = 0;
+ if (waitid(P_ALL, 0, &info, flags | WNOWAIT))
+ goto fail;
+ if (info.si_pid < 1)
+ break;
+
+ r = reap_child(reapctx, info.si_pid);
+ if (r < 0)
+ goto fail;
+ if (r > 0)
+ continue;
+
+ if (on_alien_child_death) {
+ r = (*on_alien_child_death)(info.si_pid, user2);
+ if (r < 0)
+ goto fail;
+ if (r > 0)
+ continue;
+ }
+
+ if (nreaped_elsewhere == reaped_elsewhere_size) {
+ new_size = reaped_elsewhere_size + 4;
+ new = realloc(reaped_elsewhere, new_size * sizeof(*reaped_elsewhere));
+ if (!new)
+ goto fail;
+ reaped_elsewhere = new;
+ reaped_elsewhere_size = new_size;
+ }
+
+ if (waitpid(info.si_pid, &status, WNOHANG) != info.si_pid) {
+ if (!nreaped_elsewhere) {
+ free(reaped_elsewhere);
+ reaped_elsewhere = NULL;
+ reaped_elsewhere_size = 0;
+ }
+ goto fail;
+ }
+
+ reaped_elsewhere[nreaped_elsewhere].pid = info.si_pid;
+ reaped_elsewhere[nreaped_elsewhere].status = status;
+ nreaped_elsewhere++;
+ }
+
+ if (reap_mutex_control)
+ if ((*reap_mutex_control)(+1, user4))
+ goto fail;
+ return 0;
+
+fail:
+ if (reap_mutex_control)
+ if ((*reap_mutex_control)(+1, user4))
+ goto fail;
+ return -1;
+}
+
+
+int
+libexec_run_pipeline(int (*on_alien_epoll)(int alien_epoll, uint32_t events, void *user1), int alien_epoll, void *user1,
+ int (*on_alien_child_death)(pid_t pid, void *user2), void *user2,
+ int (*after_fork)(struct libexec_command *cmd, int new_fd, void *user3), void *user3,
+ int (*reap_mutex_control)(int action /* -1 = acquire, +1 = release */, void *user4), void *user4,
+ int (*on_interrupt)(void *user5), void *user5,
+ const sigset_t *sigmask /* nullable */,
+ struct libexec_document *const *docs, size_t ndocs, int docs_1_level,
+ struct libexec_command *const *cmds, int *exit_statuses_out /* nullable */, size_t ncmds)
+{
+ struct libexec_document *input_docs = NULL;
+ size_t i, ninput_docs = 0, files_open;
+ struct epoll_event evs[8];
+ int epfd, n, sigchld_set = 0, error, r;
+ struct libexec_document *doc;
+ struct sigaction sigchld, old_sigchld;
+ struct reap_context reapctx = {
+ .pids = NULL,
+ .npids = 0,
+ .procs_rem = 0,
+ .exit_statuses_out = exit_statuses_out
+ };
+
+ if (!ncmds)
+ return 0;
+
+ memset(evs, 0, sizeof(evs));
+
+ epfd = epoll_create1(EPOLL_CLOEXEC);
+ if (epfd < 0)
+ return -1;
+
+ if (sigaction(SIGCHLD, NULL, &old_sigchld))
+ goto fail;
+ if (!(old_sigchld.sa_flags & SA_SIGINFO)) {
+ if (old_sigchld.sa_handler == SIG_IGN || old_sigchld.sa_handler == SIG_DFL) {
+ memset(&sigchld, 0, sizeof(sigchld));
+ sigchld.sa_handler = &sigchld_handler;
+ sigemptyset(&sigchld.sa_mask);
+ if (sigaction(SIGCHLD, &sigchld, NULL))
+ goto fail;
+ sigchld_set = 1;
+ }
+ }
+
+ if (alien_epoll >= 0) {
+ evs[0].events = EPOLLIN;
+ evs[0].data.ptr = NULL;
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, alien_epoll, &evs[0]))
+ goto fail;
+ }
+
+ reapctx.pids = calloc(ncmds, sizeof(*reapctx.pids));
+ if (!reapctx.pids)
+ goto fail;
+
+ for (i = 0; i < ncmds; i++)
+ if (libexec_spawn(&reapctx.pids[reapctx.npids++ /* may be set on failure */], cmds[i],
+ after_fork, user3, &input_docs, &ninput_docs, O_CLOEXEC | O_NONBLOCK))
+ goto fail;
+
+ for (i = 0; i < ninput_docs; i++) {
+ evs[0].events = EPOLLOUT;
+ evs[0].data.ptr = &input_docs[i];
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, input_docs[i].fd, &evs[0]))
+ goto fail;
+ }
+
+ for (i = 0; i < ndocs; i++) {
+ doc = docs_1_level ? &(*docs)[i] : docs[i];
+ evs[0].events = EPOLLIN;
+ evs[0].data.ptr = doc;
+ doc->user = 1;
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, doc->fd, &evs[0]))
+ goto fail;
+ }
+
+ reapctx.procs_rem = ncmds;
+
+ files_open = ninput_docs + ndocs + (size_t)(alien_epoll >= 0);
+ while (files_open) {
+ if (reap_children(&reapctx, WNOHANG, reap_mutex_control, user4, on_alien_child_death, user2))
+ goto fail;
+
+ n = epoll_pwait(epfd, evs, sizeof(evs) / sizeof(*evs), -1, sigmask);
+ if (n < 0) {
+ if (errno != EINTR)
+ goto fail;
+ if (on_interrupt)
+ if ((*on_interrupt)(user5))
+ goto fail;
+ continue;
+ }
+
+ for (i = 0; i < (size_t)n; i++) {
+ if (evs[i].data.ptr == NULL) {
+ r = (*on_alien_epoll)(alien_epoll, evs[i].events, user1);
+ if (r < 0) {
+ goto fail;
+ } else if (r > 0) {
+ if (epoll_ctl(epfd, EPOLL_CTL_DEL, alien_epoll, &evs[i]))
+ goto fail;
+ files_open -= 1;
+ }
+ } else {
+ doc = evs[i].data.ptr;
+ send_recv_again:
+ if (doc->user ? libexec_recv_document(doc) : libexec_send_document(doc)) {
+ if (errno == EAGAIN)
+ continue;
+ if (errno == EINTR) {
+ if (on_interrupt)
+ if ((*on_interrupt)(user5))
+ goto fail;
+ goto send_recv_again;
+ }
+ } else {
+ files_open -= 1;
+ if (doc->user) {
+ close(doc->fd);
+ doc->fd = -1;
+ } else {
+ libexec_destroy_document(doc);
+ }
+ }
+ }
+ }
+ }
+ free(input_docs);
+ close(epfd);
+
+ while (reapctx.procs_rem)
+ if (reap_children(&reapctx, 0, reap_mutex_control, user4, on_alien_child_death, user2))
+ goto fail;
+
+ free(reapctx.pids);
+ if (sigchld_set)
+ if (sigaction(SIGCHLD, &old_sigchld, NULL))
+ abort();
+
+ return 0;
+
+fail:
+ error = errno;
+ while (ninput_docs)
+ libexec_destroy_document(&input_docs[--ninput_docs]);
+ free(input_docs);
+ close(epfd);
+ if (reapctx.npids) {
+ if (reap_mutex_control)
+ if ((*reap_mutex_control)(+1, user4))
+ abort();
+ for (i = 0; i < reapctx.npids; i++)
+ if (reapctx.pids[i] >= 1)
+ if (unlist_reap(reapctx.pids[i], &(int){0}) || kill(reapctx.pids[i], SIGKILL))
+ reapctx.pids[i] = -1;
+ for (i = 0; i < reapctx.npids; i++)
+ if (reapctx.pids[i] >= 1)
+ waitpid(reapctx.pids[i], &(int){0}, 0);
+ if (reap_mutex_control)
+ if ((*reap_mutex_control)(-1, user4))
+ abort();
+ }
+ free(reapctx.pids);
+ if (sigchld_set)
+ if (sigaction(SIGCHLD, &old_sigchld, NULL))
+ abort();
+ errno = error;
+ return -1;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_send_document.c b/libexec_send_document.c
new file mode 100644
index 0000000..e69ad72
--- /dev/null
+++ b/libexec_send_document.c
@@ -0,0 +1,26 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_send_document(struct libexec_document *doc)
+{
+ ssize_t r;
+ if (!doc) {
+ errno = EINVAL;
+ return -1;
+ }
+ while (doc->offset < doc->length) {
+ r = write(doc->fd, &doc->text[doc->offset], doc->length - doc->offset);
+ if (r < 0)
+ return -1;
+ doc->offset += (size_t)r;
+ }
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_set_environ.c b/libexec_set_environ.c
new file mode 100644
index 0000000..1d344d0
--- /dev/null
+++ b/libexec_set_environ.c
@@ -0,0 +1,26 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libexec_set_environ(struct libexec_command *cmd, char **env)
+{
+ size_t i;
+
+ if (!cmd)
+ return;
+
+ if (cmd->environ) {
+ for (i = 0; cmd->environ[i]; i++)
+ free(cmd->environ[i]);
+ free(cmd->environ);
+ }
+
+ cmd->environ = env;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_set_exec_fd.c b/libexec_set_exec_fd.c
new file mode 100644
index 0000000..c185a9d
--- /dev/null
+++ b/libexec_set_exec_fd.c
@@ -0,0 +1,21 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_set_exec_fd(struct libexec_command *cmd, int fd)
+{
+ if (!cmd || fd < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ cmd->exec_how = LIBEXEC_EXEC_FD;
+ cmd->exec_fd = fd;
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_set_exec_path.c b/libexec_set_exec_path.c
new file mode 100644
index 0000000..2b5a7af
--- /dev/null
+++ b/libexec_set_exec_path.c
@@ -0,0 +1,29 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_set_exec_path(struct libexec_command *cmd, int dirfd, const char *path)
+{
+ char *s = NULL;
+ if (!cmd) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (path) {
+ s = strdup(path);
+ if (!s)
+ return -1;
+ }
+ free(cmd->executable);
+ cmd->executable = s;
+ cmd->exec_fd = dirfd;
+ cmd->exec_how = LIBEXEC_EXEC_AT;
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_set_executable.c b/libexec_set_executable.c
new file mode 100644
index 0000000..0f9b7c9
--- /dev/null
+++ b/libexec_set_executable.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_set_executable(struct libexec_command *cmd, const char *executable)
+{
+ char *s = NULL;
+ if (!cmd) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (executable) {
+ s = strdup(executable);
+ if (!s)
+ return -1;
+ }
+ free(cmd->executable);
+ cmd->executable = s;
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_set_require_path.c b/libexec_set_require_path.c
new file mode 100644
index 0000000..55dd369
--- /dev/null
+++ b/libexec_set_require_path.c
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libexec_set_require_path(struct libexec_command *cmd, int require)
+{
+ if (!cmd)
+ return;
+ cmd->exec_how = require ? LIBEXEC_REQUIRE_PATH : LIBEXEC_ALLOW_NAME;
+ cmd->exec_fd = -1;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_setenv.c b/libexec_setenv.c
new file mode 100644
index 0000000..47bc8e9
--- /dev/null
+++ b/libexec_setenv.c
@@ -0,0 +1,73 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenv(struct libexec_command *cmd, enum libexec_insert_mode how, const char *name, const char *value)
+{
+ int ret;
+ char *buf;
+ if (!name || !value || strchr(name, '=')) {
+ errno = EINVAL;
+ return -1;
+ }
+ buf = malloc(strlen(name) + strlen(value) + 2);
+ stpcpy(stpcpy(stpcpy(buf, name), "="), value);
+ ret = libexec_putenv(cmd, how, buf);
+ free(buf);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ /* Correct usage is tested via
+ * libexec_setenv_append.c,
+ * libexec_setenv_noclobber.c,
+ * libexec_setenv_noreplace.c,
+ * libexec_setenv_prepend.c, and
+ * libexec_setenv_replace.c */
+
+ struct libexec_command cmd, ref;
+ int i;
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv(&cmd, (enum libexec_insert_mode)-1, "A", "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv(&cmd, (enum libexec_insert_mode)LIBEXEC_INSERT_MODE__COUNT__, "A", "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ for (i = 0; i < LIBEXEC_INSERT_MODE__COUNT__; i++) {
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv(&cmd, (enum libexec_insert_mode)i, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv(&cmd, (enum libexec_insert_mode)i, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv(&cmd, (enum libexec_insert_mode)i, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ }
+
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenv_append.c b/libexec_setenv_append.c
new file mode 100644
index 0000000..a35207d
--- /dev/null
+++ b/libexec_setenv_append.c
@@ -0,0 +1,212 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenv_append(struct libexec_command *cmd, const char *name, const char *value)
+{
+ return libexec_setenv(cmd, LIBEXEC_APPEND, name, value);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_append(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_append(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_append(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_append(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "B=F");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_setenv_append(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "B=F");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenv_noclobber.c b/libexec_setenv_noclobber.c
new file mode 100644
index 0000000..6478a9e
--- /dev/null
+++ b/libexec_setenv_noclobber.c
@@ -0,0 +1,180 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenv_noclobber(struct libexec_command *cmd, const char *name, const char *value)
+{
+ return libexec_setenv(cmd, LIBEXEC_NOCLOBBER, name, value);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noclobber(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noclobber(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noclobber(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noclobber(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noclobber(&cmd, "A", "D"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noclobber(&cmd, "A", "D"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenv_noclobber(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenv_noreplace.c b/libexec_setenv_noreplace.c
new file mode 100644
index 0000000..26339e8
--- /dev/null
+++ b/libexec_setenv_noreplace.c
@@ -0,0 +1,176 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenv_noreplace(struct libexec_command *cmd, const char *name, const char *value)
+{
+ return libexec_setenv(cmd, LIBEXEC_NOREPLACE, name, value);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noreplace(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noreplace(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noreplace(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_noreplace(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenv_noreplace(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenv_prepend.c b/libexec_setenv_prepend.c
new file mode 100644
index 0000000..9c7a02b
--- /dev/null
+++ b/libexec_setenv_prepend.c
@@ -0,0 +1,212 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenv_prepend(struct libexec_command *cmd, const char *name, const char *value)
+{
+ return libexec_setenv(cmd, LIBEXEC_PREPEND, name, value);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_prepend(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_append(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_append(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_append(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "B=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "X=0");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "X=0");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "X=0");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X=0");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X=0");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_setenv_prepend(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "B=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "X=0");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenv_replace.c b/libexec_setenv_replace.c
new file mode 100644
index 0000000..f8191c1
--- /dev/null
+++ b/libexec_setenv_replace.c
@@ -0,0 +1,176 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenv_replace(struct libexec_command *cmd, const char *name, const char *value)
+{
+ return libexec_setenv(cmd, LIBEXEC_REPLACE, name, value);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_replace(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_replace(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_replace(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenv_replace(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "A", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenv_replace(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenvf.c b/libexec_setenvf.c
new file mode 100644
index 0000000..7589dca
--- /dev/null
+++ b/libexec_setenvf.c
@@ -0,0 +1,61 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenvf(struct libexec_command *cmd, enum libexec_insert_mode how, const char *name, const char *value_fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, value_fmt);
+ ret = libexec_vsetenvf(cmd, how, name, value_fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ /* Correct usage is tested, indirectly, via
+ * libexec_setenvf_append.c,
+ * libexec_setenvf_noclobber.c,
+ * libexec_setenvf_noreplace.c,
+ * libexec_setenvf_prepend.c, and
+ * libexec_setenvf_replace.c */
+
+ struct libexec_command cmd, ref;
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf(&cmd, (enum libexec_insert_mode)-1, "A", "%s", "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf(&cmd, (enum libexec_insert_mode)LIBEXEC_INSERT_MODE__COUNT__, "A", "%s", "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ libexec_clear_environ(&cmd);
+ ASSERT_ZERO(libexec_setenvf(&cmd, LIBEXEC_NOCLOBBER, "PATH", "%s:%s", "test", "path"));
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "PATH"), "test:path");
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf(&cmd, LIBEXEC_NOCLOBBER, "PATH", "%s", "x"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "PATH"), "test:path");
+ ASSERT_ZERO(libexec_setenvf(&cmd, LIBEXEC_REPLACE, "PATH", "%s%s", "my", "path"));
+ ASSERT_EQ_STR(libexec_getenv(&cmd, "PATH"), "mypath");
+ libexec_destroy_command(&cmd);
+
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenvf_append.c b/libexec_setenvf_append.c
new file mode 100644
index 0000000..b0b6a4e
--- /dev/null
+++ b/libexec_setenvf_append.c
@@ -0,0 +1,217 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenvf_append(struct libexec_command *cmd, const char *name, const char *value_fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, value_fmt);
+ ret = libexec_vsetenvf_append(cmd, name, value_fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_append(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_append(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_append(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_append(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "A", "%s=%s", "B", "C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "A", "%s", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "A", "%c", 'D'));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "B=F");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "A", "%s", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_setenvf_append(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "B=F");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenvf_noclobber.c b/libexec_setenvf_noclobber.c
new file mode 100644
index 0000000..f40b367
--- /dev/null
+++ b/libexec_setenvf_noclobber.c
@@ -0,0 +1,185 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenvf_noclobber(struct libexec_command *cmd, const char *name, const char *value_fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, value_fmt);
+ ret = libexec_vsetenvf_noclobber(cmd, name, value_fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noclobber(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noclobber(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noclobber(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noclobber(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "A", "%s%s", "B", "=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "A", "B%sC", "="));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noclobber(&cmd, "A", "D"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "A", "%s=%s", "B", "C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "A", "%s", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noclobber(&cmd, "A", "D"), -1);
+ ASSERT_EQ_INT(errno, 0);
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenvf_noclobber(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenvf_noreplace.c b/libexec_setenvf_noreplace.c
new file mode 100644
index 0000000..9480c7f
--- /dev/null
+++ b/libexec_setenvf_noreplace.c
@@ -0,0 +1,181 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenvf_noreplace(struct libexec_command *cmd, const char *name, const char *value_fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, value_fmt);
+ ret = libexec_vsetenvf_noreplace(cmd, name, value_fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noreplace(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noreplace(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noreplace(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_noreplace(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "A", "%s%s", "B", "=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "A", "%s", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "A", "%c", 'D'));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "A", "%s", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenvf_noreplace(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenvf_prepend.c b/libexec_setenvf_prepend.c
new file mode 100644
index 0000000..3a8a197
--- /dev/null
+++ b/libexec_setenvf_prepend.c
@@ -0,0 +1,217 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenvf_prepend(struct libexec_command *cmd, const char *name, const char *value_fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, value_fmt);
+ ret = libexec_vsetenvf_prepend(cmd, name, value_fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_prepend(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_append(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_append(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_append(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "A", "%s=%s", "B", "C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "A", "%s", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "A", "%c", 'D'));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "AA", "%i", 5));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "AA=5");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=5");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "B", "F%s", ""));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "B=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=5");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "A", "%s", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "X=0");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "A", "%s", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "X=0");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "X=0");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "X=0");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "BB", "%s%s", "", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "X=0");
+ ASSERT_IS_NULL(cmd.environ[6]);
+
+ ASSERT_ZERO(libexec_setenvf_prepend(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "B=F");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[5]);
+ ASSERT_EQ_STR(cmd.environ[5], "A=B=C");
+ ASSERT_NOT_NULL(cmd.environ[6]);
+ ASSERT_EQ_STR(cmd.environ[6], "X=0");
+ ASSERT_IS_NULL(cmd.environ[7]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_setenvf_replace.c b/libexec_setenvf_replace.c
new file mode 100644
index 0000000..9be28db
--- /dev/null
+++ b/libexec_setenvf_replace.c
@@ -0,0 +1,181 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_setenvf_replace(struct libexec_command *cmd, const char *name, const char *value_fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, value_fmt);
+ ret = libexec_vsetenvf_replace(cmd, name, value_fmt, args);
+ va_end(args);
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ struct libexec_command cmd, ref;
+ char **env;
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_replace(NULL, "X", "Y"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+
+ libexec_init_command(&cmd);
+ memcpy(&ref, &cmd, sizeof(cmd));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_replace(&cmd, "A=B", "C"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_replace(&cmd, "A", NULL), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ errno = 0;
+ ASSERT_EQ_INT(libexec_setenvf_replace(&cmd, NULL, "B"), -1);
+ ASSERT_EQ_INT(errno, EINVAL);
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+
+ ASSERT_ZERO(libexec_clear_environ(&cmd));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_IS_NULL(cmd.environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "A", "%s=%s", "B", "C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "A", "%s", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "A", "%c", 'D'));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_IS_NULL(cmd.environ[1]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "B=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ env = cmd.environ;
+ cmd.environ = NULL;
+ ASSERT_IS_TRUE(!memcmp(&cmd, &ref, sizeof(cmd)));
+ cmd.environ = env;
+
+ libexec_destroy_command(&cmd);
+
+ libexec_init_command(&cmd);
+ env = environ;
+ environ = calloc(2, sizeof(*environ));
+ ASSERT_NOT_NULL(environ);
+ environ[0] = strdup("X=0");
+ ASSERT_NOT_NULL(environ[0]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "A", "%s", "B=C"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=B=C");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "A", "D"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_IS_NULL(cmd.environ[2]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "AA", "E"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_IS_NULL(cmd.environ[3]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "BB", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_IS_NULL(cmd.environ[4]);
+
+ ASSERT_ZERO(libexec_setenvf_replace(&cmd, "B", "F"));
+ ASSERT_NOT_NULL(cmd.environ);
+ ASSERT_NOT_NULL(cmd.environ[0]);
+ ASSERT_EQ_STR(cmd.environ[0], "X=0");
+ ASSERT_NOT_NULL(cmd.environ[1]);
+ ASSERT_EQ_STR(cmd.environ[1], "A=D");
+ ASSERT_NOT_NULL(cmd.environ[2]);
+ ASSERT_EQ_STR(cmd.environ[2], "AA=E");
+ ASSERT_NOT_NULL(cmd.environ[3]);
+ ASSERT_EQ_STR(cmd.environ[3], "BB=F");
+ ASSERT_NOT_NULL(cmd.environ[4]);
+ ASSERT_EQ_STR(cmd.environ[4], "B=F");
+ ASSERT_IS_NULL(cmd.environ[5]);
+
+ libexec_destroy_command(&cmd);
+ environ = env;
+ return 0;
+}
+
+
+#endif
diff --git a/libexec_spawn.c b/libexec_spawn.c
new file mode 100644
index 0000000..f0b46e6
--- /dev/null
+++ b/libexec_spawn.c
@@ -0,0 +1,89 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_spawn(pid_t *out, struct libexec_command *cmd,
+ int (*after_fork)(struct libexec_command *cmd, int new_fd, void *user),
+ void *user, struct libexec_document **docsp, size_t *ndocsp, int doc_fd_flags)
+{
+ int fds[2], error, new_fd;
+ size_t i;
+ ssize_t r;
+
+ if (!docsp || !ndocsp || !out || !cmd) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *out = -1;
+
+ if (pipe2(fds, O_CLOEXEC))
+ return -1;
+
+ new_fd = fds[1];
+ for (i = 0; i < cmd->nplumings; i++)
+ if (cmd->plumings[i].fd >= new_fd)
+ new_fd = cmd->plumings[i].fd + 1;
+
+ if (new_fd != fds[1]) {
+ new_fd = fcntl(fds[1], F_DUPFD_CLOEXEC, new_fd);
+ if (new_fd == -1)
+ goto fail;
+ fds[1] = new_fd;
+ }
+
+ if (libexec_get_documents(cmd, docsp, ndocsp, doc_fd_flags))
+ goto fail;
+
+ *out = fork();
+ switch (*out) {
+ case -1:
+ goto fail;
+
+ case 0:
+ close(fds[0]);
+ if (after_fork)
+ if ((*after_fork)(cmd, fds[1], user))
+ goto child_fail;
+ libexec_exec(cmd);
+ child_fail:
+ error = errno;
+ write_again:
+ r = write(fds[1], &error, sizeof(error));
+ if (r <= 0) {
+ if (errno == EINTR)
+ goto write_again;
+ abort();
+ }
+ _exit(1);
+
+ default:
+ close(fds[1]);
+ read_again:
+ r = read(fds[0], &error, sizeof(error));
+ if (r < 0) {
+ if (errno == EINTR)
+ goto read_again;
+ } else if (r == 0) {
+ return 0;
+ } else if ((size_t)r >= sizeof(error)) {
+ errno = error;
+ }
+
+ return -1;
+ }
+
+fail:
+ error = errno;
+ close(fds[0]);
+ close(fds[1]);
+ errno = error;
+ return -1;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_unsetenv.c b/libexec_unsetenv.c
new file mode 100644
index 0000000..48ec645
--- /dev/null
+++ b/libexec_unsetenv.c
@@ -0,0 +1,48 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_unsetenv(struct libexec_command *cmd, const char *name)
+{
+ char **env;
+ size_t i, n, len;
+
+ if (!cmd || !name) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (strchr(name, '='))
+ return 0;
+
+ env = cmd->environ ? cmd->environ : environ;
+ len = strlen(name);
+
+ n = 0;
+ while (env[n])
+ n++;
+
+ for (i = 0; i < n;) {
+ if (!strncmp(env[i], name, len) && env[i][len] == '=') {
+ if (!cmd->environ) {
+ if (libexec_copy_environ(cmd, NULL))
+ return -1;
+ env = cmd->environ;
+ }
+ free(env);
+ memmove(&env[i], &env[i + 1], (n - i - 1) * sizeof(*env));
+ n--;
+ } else {
+ i++;
+ }
+ }
+
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_unsetenv_first.c b/libexec_unsetenv_first.c
new file mode 100644
index 0000000..9c5ea12
--- /dev/null
+++ b/libexec_unsetenv_first.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_unsetenv_first(struct libexec_command *cmd, const char *name)
+{
+ char **env;
+ size_t i, n, len;
+
+ if (!cmd || !name) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (strchr(name, '='))
+ return 0;
+
+ env = cmd->environ ? cmd->environ : environ;
+ len = strlen(name);
+
+ for (i = 0; env[i]; i++)
+ if (!strncmp(env[i], name, len) && env[i][len] == '=')
+ break;
+
+ if (env[i]) {
+ n = i;
+ while (env[n])
+ n++;
+ if (!cmd->environ)
+ if (libexec_copy_environ(cmd, NULL))
+ return -1;
+ free(cmd->environ[i]);
+ memmove(&cmd->environ[i], &cmd->environ[i + 1], (n - i - 1) * sizeof(*cmd->environ));
+ }
+
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_unsetenv_last.c b/libexec_unsetenv_last.c
new file mode 100644
index 0000000..e3fc263
--- /dev/null
+++ b/libexec_unsetenv_last.c
@@ -0,0 +1,46 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_unsetenv_last(struct libexec_command *cmd, const char *name)
+{
+ char **env;
+ size_t i, n, len;
+
+ if (!cmd || !name) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (strchr(name, '='))
+ return 0;
+
+ env = cmd->environ ? cmd->environ : environ;
+ len = strlen(name);
+
+ n = 0;
+ while (env[n])
+ n++;
+
+ for (i = n; i--;)
+ if (!strncmp(env[i], name, len) && env[i][len] == '=')
+ goto found;
+
+ return 0;
+
+found:
+ if (!cmd->environ)
+ if (libexec_copy_environ(cmd, NULL))
+ return -1;
+ free(cmd->environ[i]);
+ memmove(&cmd->environ[i], &cmd->environ[i + 1], (n - i - 1) * sizeof(*cmd->environ));
+
+ return 0;
+}
+
+
+#else
+LIBEXEC_CONST__ int main(void) {return 0;} /* TODO test */
+#endif
diff --git a/libexec_vconstruct_command.c b/libexec_vconstruct_command.c
new file mode 100644
index 0000000..bb3626f
--- /dev/null
+++ b/libexec_vconstruct_command.c
@@ -0,0 +1,882 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#define FLAG_MINUS 0x01
+#define FLAG_PLUS 0x02
+#define FLAG_HASH 0x04
+#define FLAG_ZERO 0x08
+#define FLAG_SPACE 0x10
+
+
+#define TYPESIZE_CHAR -2
+#define TYPESIZE_SHORT -1
+#define TYPESIZE_INT 0
+#define TYPESIZE_LONG +1
+#define TYPESIZE_LONG_LONG +2
+#define TYPESIZE_SIZE 3
+#define TYPESIZE_PTRDIFF 4
+#define TYPESIZE_INTPTR 5
+#define TYPESIZE_INTMAX 6
+
+
+union anyarray_const {
+ const void *v;
+ const signed char *hhi;
+ const signed short int *hi;
+ const signed int *i;
+ const signed long int *li;
+ const signed long long int *lli;
+ const ssize_t *zi;
+ const ptrdiff_t *ti;
+ const intptr_t *pi;
+ const intmax_t *ji;
+ const unsigned char *hhu;
+ const unsigned short int *hu;
+ const unsigned int *u;
+ const unsigned long int *lu;
+ const unsigned long long int *llu;
+ const size_t *zu;
+ const ptrdiff_t *tu;
+ const uintptr_t *pu;
+ const uintmax_t *ju;
+ const char *c;
+ const char *const *s;
+};
+
+union anyvalue {
+ intmax_t i;
+ uintmax_t u;
+ char c[2];
+ const char *s;
+};
+
+struct partial_string {
+ char *s;
+ size_t len;
+ size_t size;
+ int started;
+};
+
+struct argument_format {
+ size_t array_size;
+ size_t width1;
+ size_t width2;
+ int array_input;
+ int array_null_terminated;
+ int typesize;
+ int has_dot;
+ char typeclass;
+ unsigned flags;
+};
+
+struct ignore_stack {
+ char *stack;
+ size_t top;
+ size_t size;
+ size_t active;
+};
+
+
+static int
+resize_partial_string(struct partial_string *str, size_t req_size)
+{
+ void *new = realloc(str->s, req_size);
+ if (!new)
+ return -1;
+ str->s = new;
+ str->size = req_size;
+ return 0;
+}
+
+
+static int
+resize_partial_string_if_full(struct partial_string *str, size_t extra)
+{
+ if (str->len == str->size)
+ if (resize_partial_string(str, str->len + extra))
+ return -1;
+ return 0;
+}
+
+
+static char *
+make_unsigned(uintmax_t value, char fmt, const char *plus, const char *prefix, size_t minwidth)
+{
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+ const char *fmtstr;
+ char *ret, *p;
+ size_t len, zeroes, totlen;
+
+ if (fmt == 'd' || fmt == 'i' || fmt == 'u')
+ fmtstr = "%ju";
+ else if (fmt == 'o')
+ fmtstr = "%jo";
+ else if (fmt == 'x')
+ fmtstr = "%jx";
+ else if (fmt == 'X')
+ fmtstr = "%jX";
+ else
+ abort();
+
+ len = (size_t)snprintf(NULL, 0, fmtstr, value);
+ zeroes = len < minwidth ? minwidth - len : 0;
+
+ totlen = strlen(plus) + 1;
+ if (strlen(prefix) > SIZE_MAX - totlen)
+ goto enomem;
+ totlen += strlen(prefix);
+ if (zeroes + len > SIZE_MAX - totlen)
+ goto enomem;
+ totlen += zeroes + len;
+
+ ret = malloc(totlen);
+ if (!ret)
+ goto enomem;
+
+ p = stpcpy(stpcpy(ret, plus), prefix);
+ while (zeroes--)
+ *p++ = '0';
+ sprintf(p, fmtstr, value);
+
+ return ret;
+
+enomem:
+ errno = ENOMEM;
+ return NULL;
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+}
+
+
+static char *
+make_signed(intmax_t value, char fmt, const char *plus, const char *prefix, size_t minwidth)
+{
+ if (value < 0)
+ return make_unsigned((uintmax_t)-(value + 1) + 1U, fmt, "-", prefix, minwidth);
+ else
+ return make_unsigned((uintmax_t)value, fmt, plus, prefix, minwidth);
+}
+
+
+static unsigned
+get_flag(char c)
+{
+ return c == '-' ? FLAG_MINUS :
+ c == '+' ? FLAG_PLUS :
+ c == '#' ? FLAG_HASH :
+ c == '0' ? FLAG_ZERO :
+ c == ' ' ? FLAG_SPACE : 0U;
+}
+
+
+static unsigned
+remove_ignored_flags(unsigned flags, char fmt)
+{
+ if (fmt == 's' || fmt == 'c') {
+ flags &= FLAG_MINUS;
+ } else if (fmt == 'i' || fmt == 'd') {
+ if (flags & FLAG_PLUS)
+ flags &= (unsigned)~FLAG_SPACE;
+ } else {
+ flags &= (unsigned)~(FLAG_PLUS | FLAG_SPACE);
+ }
+ return flags;
+}
+
+
+static const char *
+read_size(size_t *out, const char *fmt, va_list args, int optional)
+{
+ size_t digit;
+
+ if (*fmt == '*') {
+ fmt++;
+ *out = va_arg(args, size_t);
+ } else if (!optional && !isdigit(*fmt)) {
+ goto einval;
+ } else {
+ *out = 0;
+ while (isdigit(*fmt)) {
+ digit = (size_t)(*fmt++ - '0');
+ if (*out > (SIZE_MAX - digit) / 10U)
+ goto einval;
+ *out = *out * 10U + digit;
+ }
+ }
+
+ return fmt;
+
+einval:
+ errno = EINVAL;
+ return NULL;
+}
+
+
+static const char *
+get_typesize(int *out, const char *fmt)
+{
+ *out = 0;
+
+ for (; *fmt == 'h'; fmt++)
+ if (--*out < -2)
+ goto einval;
+
+ for (; *fmt == 'l'; fmt++)
+ if (*out < 0 || ++*out > +2)
+ goto einval;
+
+ if (*fmt == 'z' || *fmt == 't' || *fmt == 'T' || *fmt == 'j') {
+ if (*out)
+ goto einval;
+ *out = *fmt == 'z' ? TYPESIZE_SIZE :
+ *fmt == 't' ? TYPESIZE_PTRDIFF :
+ *fmt == 'T' ? TYPESIZE_INTPTR : /* non-standard */
+ TYPESIZE_INTMAX;
+ fmt++;
+ }
+
+ /*
+ * TODO add =N for intN_t/uintN_t (arbitrary N)
+ * TODO add =+N for int_leastN_t/uint_leastN_t (arbitrary N)
+ * TODO add =/N for int_fastN_t/uint_fastN_t (arbitrary N)
+ * non-standard
+ */
+
+ if (*fmt == 's' || *fmt == 'c') {
+ if (*out) {
+ /* TODO %ls should be `const wchar_t *` */
+ /* TODO %lc should be `wint_t` */
+ goto einval;
+ }
+ }
+
+ return fmt;
+
+einval:
+ errno = EINVAL;
+ return NULL;
+}
+
+
+static intmax_t
+get_signed_va(va_list args, int typesize)
+{
+ if (typesize == TYPESIZE_CHAR)
+ return (intmax_t)(signed char)va_arg(args, signed int);
+ else if (typesize == TYPESIZE_SHORT)
+ return (intmax_t)(signed short int)va_arg(args, signed int);
+ else if (typesize == TYPESIZE_INT)
+ return (intmax_t)va_arg(args, signed int);
+ else if (typesize == TYPESIZE_LONG)
+ return (intmax_t)va_arg(args, signed long int);
+ else if (typesize == TYPESIZE_LONG_LONG)
+ return (intmax_t)va_arg(args, signed long long int);
+ else if (typesize == TYPESIZE_SIZE) /* TODO since ssize_t may be bounded to -1, may it be bounded above INTMAX_MAX? */
+ return (intmax_t)va_arg(args, ssize_t);
+ else if (typesize == TYPESIZE_PTRDIFF)
+ return (intmax_t)va_arg(args, ptrdiff_t);
+ else if (typesize == TYPESIZE_INTPTR)
+ return (intmax_t)va_arg(args, intptr_t);
+ else if (typesize == TYPESIZE_INTMAX)
+ return (intmax_t)va_arg(args, intmax_t);
+ else
+ abort();
+}
+
+
+static uintmax_t
+get_unsigned_va(va_list args, int typesize)
+{
+ if (typesize == TYPESIZE_CHAR)
+ return (uintmax_t)(unsigned char)va_arg(args, unsigned int);
+ else if (typesize == TYPESIZE_SHORT)
+ return (uintmax_t)(unsigned short int)va_arg(args, unsigned int);
+ else if (typesize == TYPESIZE_INT)
+ return (uintmax_t)va_arg(args, unsigned int);
+ else if (typesize == TYPESIZE_LONG)
+ return (uintmax_t)va_arg(args, unsigned long int);
+ else if (typesize == TYPESIZE_LONG_LONG)
+ return (uintmax_t)va_arg(args, unsigned long long int);
+ else if (typesize == TYPESIZE_SIZE)
+ return (uintmax_t)va_arg(args, size_t);
+ else if (typesize == TYPESIZE_PTRDIFF)
+ return (uintmax_t)va_arg(args, ptrdiff_t); /* TODO ptrdiff_t is signed */
+ else if (typesize == TYPESIZE_INTPTR)
+ return (uintmax_t)va_arg(args, uintptr_t);
+ else if (typesize == TYPESIZE_INTMAX)
+ return (uintmax_t)va_arg(args, uintmax_t);
+ else
+ abort();
+}
+
+
+static intmax_t
+get_signed_array(union anyarray_const *arrayp, int typesize)
+{
+ if (typesize == TYPESIZE_CHAR)
+ return (intmax_t)*arrayp->hhi++;
+ else if (typesize == TYPESIZE_SHORT)
+ return (intmax_t)*arrayp->hi++;
+ else if (typesize == TYPESIZE_INT)
+ return (intmax_t)*arrayp->i++;
+ else if (typesize == TYPESIZE_LONG)
+ return (intmax_t)*arrayp->li++;
+ else if (typesize == TYPESIZE_LONG_LONG)
+ return (intmax_t)*arrayp->lli++;
+ else if (typesize == TYPESIZE_SIZE)
+ return (intmax_t)*arrayp->zi++; /* TODO since ssize_t may be bounded to -1, may it be bounded above INTMAX_MAX? */
+ else if (typesize == TYPESIZE_PTRDIFF)
+ return (intmax_t)*arrayp->ti++;
+ else if (typesize == TYPESIZE_INTPTR)
+ return (intmax_t)*arrayp->pi++;
+ else if (typesize == TYPESIZE_INTMAX)
+ return (intmax_t)*arrayp->ji++;
+ else
+ abort();
+}
+
+
+static uintmax_t
+get_unsigned_array(union anyarray_const *arrayp, int typesize)
+{
+ if (typesize == TYPESIZE_CHAR)
+ return (uintmax_t)*arrayp->hhu++;
+ else if (typesize == TYPESIZE_SHORT)
+ return (uintmax_t)*arrayp->hu++;
+ else if (typesize == TYPESIZE_INT)
+ return (uintmax_t)*arrayp->u++;
+ else if (typesize == TYPESIZE_LONG)
+ return (uintmax_t)*arrayp->lu++;
+ else if (typesize == TYPESIZE_LONG_LONG)
+ return (uintmax_t)*arrayp->llu++;
+ else if (typesize == TYPESIZE_SIZE)
+ return (uintmax_t)*arrayp->zu++;
+ else if (typesize == TYPESIZE_PTRDIFF)
+ return (uintmax_t)*arrayp->tu++; /* TODO ptrdiff_t is signed */
+ else if (typesize == TYPESIZE_INTPTR)
+ return (uintmax_t)*arrayp->pu++;
+ else if (typesize == TYPESIZE_INTMAX)
+ return (uintmax_t)*arrayp->ju++;
+ else
+ abort();
+}
+
+
+static int
+get_value_va(union anyvalue *out, va_list args, char fmt, int typesize)
+{
+ if (fmt == 'i' || fmt == 'd') {
+ out->i = get_signed_va(args, typesize);
+
+ } else if (fmt == 'u' || fmt == 'o' || fmt == 'x' || fmt == 'X') {
+ out->u = get_unsigned_va(args, typesize);
+
+ } else if (fmt == 's') {
+ if (typesize)
+ abort();
+ out->s = va_arg(args, const char *);
+
+ } else if (fmt == 'c') {
+ if (typesize)
+ abort();
+ out->c[0] = (char)va_arg(args, int);
+ out->c[1] = '\0';
+
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+get_value_array(union anyvalue *out, union anyarray_const *arrayp, char fmt, int typesize)
+{
+ if (fmt == 'i' || fmt == 'd') {
+ out->i = get_signed_array(arrayp, typesize);
+ return !!out->i;
+
+ } else if (fmt == 'u' || fmt == 'o' || fmt == 'x' || fmt == 'X') {
+ out->u = get_unsigned_array(arrayp, typesize);
+ return !!out->u;
+
+ } else if (fmt == 's') {
+ if (typesize)
+ abort();
+ out->s = *arrayp->s++;
+ return !!out->s;
+
+ } else if (fmt == 'c') {
+ if (typesize)
+ abort();
+ out->c[0] = *arrayp->c++;
+ out->c[1] = '\0';
+ return !!out->c[0];
+
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+
+static char *
+create_string(const union anyvalue *value, char fmt, const char *plus, const char *prefix, int has_dot, size_t width)
+{
+ if (fmt == 'i' || fmt == 'd') {
+ return make_signed(value->i, fmt, plus, value->i ? prefix : "", width);
+
+ } else if (fmt == 'u' || fmt == 'o' || fmt == 'x' || fmt == 'X') {
+ return make_unsigned(value->u, fmt, "", value->u ? prefix : "", width);
+
+ } else if (fmt == 's') {
+ return strndup(value->s, has_dot ? width : strlen(value->s));
+
+ } else if (fmt == 'c') {
+ return strndup(value->c, has_dot ? width : 1);
+
+ } else {
+ errno = EINVAL;
+ return NULL;
+ }
+}
+
+
+static int
+pad_and_insert(struct partial_string *arg, const char *substring, size_t min_width, char pad_char, unsigned pad_before)
+{
+ size_t pad_len, substring_len;
+
+ substring_len = strlen(substring);
+ pad_len = substring_len < min_width ? min_width - substring_len : 0;
+ if (substring_len + pad_len > SIZE_MAX - arg->len) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (substring_len + pad_len + arg->len > arg->size)
+ if (resize_partial_string(arg, substring_len + pad_len + arg->len))
+ return -1;
+
+ if (pad_before) {
+ memset(&arg->s[arg->len], pad_char, pad_len);
+ arg->len += pad_len;
+ }
+
+ memcpy(&arg->s[arg->len], substring, substring_len);
+ arg->len += substring_len;
+
+ if (!pad_before) {
+ memset(&arg->s[arg->len], pad_char, pad_len);
+ arg->len += pad_len;
+ }
+
+ return 0;
+}
+
+
+static int
+insert_argument(struct partial_string *arg, const union anyvalue *value, char fmt,
+ unsigned flags, size_t width1, int has_dot, size_t width2)
+{
+ const char *plus, *prefix;
+ char *substring;
+
+ plus = (flags & FLAG_PLUS) ? "+" : (flags & FLAG_SPACE) ? " " : "";
+
+ prefix = !(flags & FLAG_HASH) ? "" :
+ fmt == 'o' ? "0" :
+ fmt == 'x' ? "0x" :
+ fmt == 'X' ? "0X" : "";
+
+ if (fmt != 's' && fmt != 'c') {
+ if (!has_dot) {
+ width2 = width1;
+ width1 = 0;
+ }
+ }
+
+ substring = create_string(value, fmt, plus, prefix, has_dot, width2);
+ if (!substring)
+ return -1;
+
+ if (pad_and_insert(arg, substring, width1, (flags & FLAG_ZERO) ? '0' : ' ', flags & FLAG_MINUS)) {
+ free(substring);
+ return -1;
+ }
+
+ free(substring);
+ return 0;
+}
+
+
+static int
+get_and_insert_arguments(struct partial_string *arg, va_list args, const struct argument_format *arg_fmt, int ignoring)
+{
+ union anyarray_const array;
+ union anyvalue value;
+ unsigned int flags;
+ size_t n;
+ int r;
+
+ flags = remove_ignored_flags(arg_fmt->flags, arg_fmt->typeclass);
+
+ if (arg_fmt->array_input) {
+ array.v = va_arg(args, const void *);
+ n = arg_fmt->array_size;
+ while (arg_fmt->array_null_terminated || n--) {
+ r = get_value_array(&value, &array, arg_fmt->typeclass, arg_fmt->typesize);
+ if (r < 0)
+ return -1;
+ else if (!r && arg_fmt->array_null_terminated)
+ break;
+ if (!ignoring)
+ if (insert_argument(arg, &value, arg_fmt->typeclass, flags,
+ arg_fmt->width1, arg_fmt->has_dot, arg_fmt->width2))
+ return -1;
+ }
+
+ } else {
+ if (get_value_va(&value, args, arg_fmt->typeclass, arg_fmt->typesize))
+ return -1;
+ if (!ignoring)
+ if (insert_argument(arg, &value, arg_fmt->typeclass, flags,
+ arg_fmt->width1, arg_fmt->has_dot, arg_fmt->width2))
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static const char *
+get_argument_format(struct argument_format *out, const char *fmt, va_list args)
+{
+ unsigned flag;
+
+ memset(out, 0, sizeof(*out));
+
+ if (*fmt == '[') {
+ fmt++;
+ out->array_input = 1;
+ if (*fmt == ']') {
+ out->array_null_terminated = 1;
+ } else {
+ fmt = read_size(&out->array_size, fmt, args, 1);
+ if (!fmt)
+ goto fail;
+ }
+ if (*fmt != ']')
+ goto einval;
+ fmt++;
+ }
+
+ while ((flag = get_flag(*fmt))) {
+ out->flags |= flag;
+ fmt++;
+ }
+
+ fmt = read_size(&out->width1, fmt, args, 0);
+ if (!fmt)
+ goto fail;
+ if (*fmt == '.') {
+ out->has_dot = 1;
+ out->flags &= (unsigned)~FLAG_ZERO;
+ fmt++;
+ fmt = read_size(&out->width2, fmt, args, 0);
+ if (!fmt)
+ goto fail;
+ }
+
+ fmt = get_typesize(&out->typesize, fmt);
+ if (!fmt)
+ goto fail;
+
+ out->typeclass = *fmt++;
+
+ return fmt;
+
+einval:
+ errno = EINVAL;
+fail:
+ return NULL;
+}
+
+
+static void
+destroy_ignore_stack(struct ignore_stack *stack)
+{
+ free(stack->stack);
+ stack->stack = NULL;
+}
+
+
+static int
+push_ignore_stack(struct ignore_stack *stack, va_list args)
+{
+ int show;
+ void *new;
+ size_t new_size;
+
+ if (stack->top == stack->size) {
+ new_size = stack->size + 8;
+ new = realloc(stack->stack, new_size * sizeof(*stack->stack));
+ if (!new)
+ return -1;
+ stack->stack = new;
+ stack->size = new_size;
+ }
+
+ show = va_arg(args, int);
+ stack->stack[stack->top++] = (char)!show;
+ stack->active += (size_t)!show;
+ return 0;
+}
+
+
+static int
+pop_ignore_stack(struct ignore_stack *stack)
+{
+ if (!stack->top) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (stack->stack[--stack->top])
+ stack->active -= 1;
+ return 0;
+}
+
+
+static int
+is_ignoring(struct ignore_stack *stack)
+{
+ return stack->active > 0;
+}
+
+
+static const char *
+format(const char *fmt, struct partial_string *arg, struct ignore_stack *ignore_stack, va_list args)
+{
+
+ struct argument_format arg_fmt;
+
+ /* TODO $< should work as <(), * or number before < to select fd (default: STDOUT_FILENO) */
+ /* TODO $> should work as >(), * or number before > to select fd (default: STDIN_FILENO) */
+ /* TODO add support for embedding via %S and %C (do not set arg->started) */
+ /* TODO add support for floating point values */
+
+ if (*fmt == '$' || *fmt == '\'' || *fmt == '"' || *fmt == '_') {
+ if (!is_ignoring(ignore_stack)) {
+ if (arg->len == arg->size)
+ if (resize_partial_string(arg, arg->size + 32))
+ goto fail;
+ arg->s[arg->len++] = *fmt == '_' ? ' ' : *fmt;
+ arg->started = 1;
+ }
+ fmt++;
+
+ } else if (*fmt == '(') {
+ fmt++;
+ if (push_ignore_stack(ignore_stack, args))
+ goto fail;
+
+ } else if (*fmt == ')') {
+ fmt++;
+ if (pop_ignore_stack(ignore_stack))
+ goto fail;
+
+ } else {
+ fmt = get_argument_format(&arg_fmt, fmt, args);
+ if (!fmt)
+ goto fail;
+ if (get_and_insert_arguments(arg, args, &arg_fmt, is_ignoring(ignore_stack)))
+ goto fail;
+ arg->started = 1;
+ }
+
+ return fmt;
+
+fail:
+ return NULL;
+}
+
+
+static char **
+format_command(const char *fmt, va_list args, size_t *narguments_out)
+{
+ size_t command_size = 0;
+ char **command = NULL;
+ struct ignore_stack ignore_stack;
+ struct partial_string arg;
+ void *new;
+
+ *narguments_out = 0;
+
+ memset(&ignore_stack, 0, sizeof(ignore_stack));
+ ignore_stack.stack = NULL;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.s = NULL;
+
+next_arg:
+ arg.started = 0;
+ arg.len = 0;
+ while (*fmt) {
+ if (*fmt == '$') {
+ fmt++;
+ fmt = format(fmt, &arg, &ignore_stack, args);
+ if (!fmt)
+ goto fail;
+
+ } else if (isspace(*fmt)) {
+ fmt++;
+ if (!arg.started || is_ignoring(&ignore_stack))
+ continue;
+ new = realloc(command, (command_size + 1) * sizeof(*command));
+ if (!new)
+ goto fail;
+ command = new;
+ if (resize_partial_string_if_full(&arg, 32))
+ goto fail;
+ arg.s[arg.len] = '\0';
+ command[command_size] = strdup(arg.s);
+ if (!command[command_size])
+ goto fail;
+ command_size++;
+ goto next_arg;
+
+ } else if (*fmt == '\'' || *fmt == '"') {
+ arg.started = 1;
+ /* TODO add support for quotes */
+ errno = EINVAL;
+ goto fail;
+
+ } else {
+ if (!is_ignoring(&ignore_stack)) {
+ if (resize_partial_string_if_full(&arg, 32))
+ goto fail;
+ arg.s[arg.len++] = *fmt;
+ arg.started = 1;
+ }
+ fmt++;
+ }
+ }
+
+ new = realloc(command, (command_size + 2) * sizeof(*command));
+ if (!new)
+ goto fail;
+ command = new;
+ if (arg.started) {
+ if (resize_partial_string_if_full(&arg, 1))
+ goto fail;
+ arg.s[arg.len] = '\0';
+ command[command_size++] = arg.s;
+ } else {
+ free(arg.s);
+ }
+ command[command_size] = NULL;
+ destroy_ignore_stack(&ignore_stack);
+
+ *narguments_out = command_size;
+ return command;
+
+fail:
+ free(arg.s);
+ destroy_ignore_stack(&ignore_stack);
+ while (command_size)
+ free(command[--command_size]);
+ free(command);
+ return NULL;
+}
+
+
+static int
+append_arguments(struct libexec_command *cmd, va_list args)
+{
+ size_t n = 0, i;
+ va_list args2;
+ void *new;
+
+ va_copy(args2, args);
+ while (va_arg(args2, const char *))
+ n += 1;
+ va_end(args2);
+
+ new = realloc(cmd->arguments, (cmd->narguments + n + 1) * sizeof(*cmd->arguments));
+ if (!new)
+ return -1;
+ cmd->arguments = new;
+
+ for (i = 0; i < n; i++) {
+ cmd->arguments[cmd->narguments + i] = strdup(va_arg(args, const char *));
+ if (!cmd->arguments[cmd->narguments + i]) {
+ while (i)
+ free(cmd->arguments[cmd->narguments + --i]);
+ if (!cmd->narguments) {
+ free(cmd->arguments);
+ cmd->arguments = NULL;
+ }
+ return -1;
+ }
+ }
+
+ cmd->arguments[cmd->narguments + n] = va_arg(args, char *); /* that's a NULL */
+ cmd->narguments += n;
+
+ return 0;
+}
+
+
+int
+libexec_vconstruct_command(struct libexec_command *cmd, const char *fmt, va_list args)
+{
+ size_t n;
+ char **command = NULL;
+ void *new;
+
+ if (!cmd) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!fmt) {
+ return append_arguments(cmd, args);
+
+ } else {
+ command = format_command(fmt, args, &n);
+ if (!command)
+ return -1;
+ if (!cmd->narguments) {
+ cmd->arguments = command;
+ cmd->narguments = n;
+ } else {
+ new = realloc(cmd->arguments, (cmd->narguments + n + 1) * sizeof(*cmd->arguments));
+ if (!new) {
+ while (n)
+ free(command[--n]);
+ free(command);
+ return -1;
+ }
+ cmd->arguments = new;
+ memcpy(&cmd->arguments[cmd->narguments], command, (n + 1) * sizeof(*command));
+ cmd->narguments += n;
+ free(command);
+ }
+ return 0;
+ }
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_construct_command.c */
+#endif
diff --git a/libexec_vpipe_commands.c b/libexec_vpipe_commands.c
new file mode 100644
index 0000000..6f1eb3f
--- /dev/null
+++ b/libexec_vpipe_commands.c
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vpipe_commands(enum libexec_pipe how, va_list args)
+{
+ struct libexec_command **cmds;
+ size_t n = 0, i;
+ int ret;
+ va_list args2;
+ va_copy(args2, args);
+ while (va_arg(args2, struct libexec_command *))
+ n += 1;
+ va_end(args2);
+ cmds = calloc(n + 1, sizeof(*cmds));
+ if (!cmds)
+ return -1;
+ for (i = 0; i < n; i++)
+ cmds[i] = va_arg(args, struct libexec_command *);
+ ret = libexec_pipe_commandsvn(how, cmds, n);
+ free(cmds);
+ return ret;
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_pipe_commands.c */
+#endif
diff --git a/libexec_vputenvf.c b/libexec_vputenvf.c
new file mode 100644
index 0000000..3d8b7f9
--- /dev/null
+++ b/libexec_vputenvf.c
@@ -0,0 +1,39 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+
+int
+libexec_vputenvf(struct libexec_command *cmd, enum libexec_insert_mode how, const char *fmt, va_list args)
+{
+ int len, ret;
+ char *buf;
+ va_list args2;
+ if (!fmt) {
+ errno = EINVAL;
+ return -1;
+ }
+ va_copy(args2, args);
+ len = vsnprintf(NULL, 0, fmt, args2);
+ va_end(args2);
+ if (len < 0)
+ return -1;
+ buf = malloc((size_t)len + 1U);
+ if (!buf)
+ return -1;
+ if (vsprintf(buf, fmt, args) != len)
+ abort();
+ ret = libexec_putenv(cmd, how, buf);
+ free(buf);
+ return ret;
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_putenvf.c */
+#endif
diff --git a/libexec_vputenvf_append.c b/libexec_vputenvf_append.c
new file mode 100644
index 0000000..ca0d564
--- /dev/null
+++ b/libexec_vputenvf_append.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vputenvf_append(struct libexec_command *cmd, const char *fmt, va_list args)
+{
+ return libexec_vputenvf(cmd, LIBEXEC_APPEND, fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_putenvf_append.c */
+#endif
diff --git a/libexec_vputenvf_noclobber.c b/libexec_vputenvf_noclobber.c
new file mode 100644
index 0000000..61a1e74
--- /dev/null
+++ b/libexec_vputenvf_noclobber.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vputenvf_noclobber(struct libexec_command *cmd, const char *fmt, va_list args)
+{
+ return libexec_vputenvf(cmd, LIBEXEC_NOCLOBBER, fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_putenvf_noclobber.c */
+#endif
diff --git a/libexec_vputenvf_noreplace.c b/libexec_vputenvf_noreplace.c
new file mode 100644
index 0000000..6998742
--- /dev/null
+++ b/libexec_vputenvf_noreplace.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vputenvf_noreplace(struct libexec_command *cmd, const char *fmt, va_list args)
+{
+ return libexec_vputenvf(cmd, LIBEXEC_NOREPLACE, fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_putenvf_noreplace.c */
+#endif
diff --git a/libexec_vputenvf_prepend.c b/libexec_vputenvf_prepend.c
new file mode 100644
index 0000000..e1b9614
--- /dev/null
+++ b/libexec_vputenvf_prepend.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vputenvf_prepend(struct libexec_command *cmd, const char *fmt, va_list args)
+{
+ return libexec_vputenvf(cmd, LIBEXEC_PREPEND, fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_putenvf_prepend.c */
+#endif
diff --git a/libexec_vputenvf_replace.c b/libexec_vputenvf_replace.c
new file mode 100644
index 0000000..10c22d2
--- /dev/null
+++ b/libexec_vputenvf_replace.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vputenvf_replace(struct libexec_command *cmd, const char *fmt, va_list args)
+{
+ return libexec_vputenvf(cmd, LIBEXEC_REPLACE, fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_putenvf_replace.c */
+#endif
diff --git a/libexec_vrun.c b/libexec_vrun.c
new file mode 100644
index 0000000..99822c2
--- /dev/null
+++ b/libexec_vrun.c
@@ -0,0 +1,307 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vrun(struct libexec_result *out, va_list args)
+{
+#define CMD (cmds[ncmds - 1])
+
+ struct libexec_document *doc, **docs = NULL;
+ struct libexec_command **cmds = NULL;
+ size_t ncmds = 0, ndocs = 0, i;
+ const char *fmt, *arg_str1, *arg_str2;
+ char *arg_str_nc;
+ int arg_int1, arg_int2, arg_int3, fd, *exit_statuses = NULL;
+ enum libexec_insert_mode insert_mode;
+ void *new, *arg_voidp1;
+ enum libexec_pipe pipe_how = 0;
+ struct epoll_event ev;
+ int alien_epoll = -1;
+ const sigset_t *sigmask;
+ int (*on_alien_epoll)(int alien_epoll, uint32_t events, void *user);
+ int (*on_alien_child_death)(pid_t pid, void *user);
+ int (*after_fork)(struct libexec_command *cmd, int new_fd, void *user);
+ int (*reap_mutex_control)(int action, void *user);
+ int (*on_interrupt)(void *user);
+ void *on_alien_epoll_user = NULL;
+ void *on_alien_child_death_user = NULL;
+ void *after_fork_user = NULL;
+ void *reap_mutex_control_user = NULL;
+ void *on_interrupt_user = NULL;
+ int ret = -1;
+
+ if (!out) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(&ev, 0, sizeof(ev));
+ memset(out, 0, sizeof(*out));
+ out->exit_statuses = NULL;
+
+new_command:
+ new = realloc(cmds, (ncmds + 1) * sizeof(*cmds));
+ if (!new)
+ goto fail;
+ cmds = new;
+ ncmds += 1;
+ CMD = malloc(sizeof(CMD));
+ if (!CMD)
+ goto fail;
+ libexec_init_command(CMD);
+ fmt = va_arg(args, const char *);
+ if (libexec_vconstruct_command(CMD, fmt, args))
+ goto fail;
+
+ for (;;) {
+ switch (va_arg(args, enum libexec_run_instruction)) {
+ case LIBEXEC_RUN_END:
+ if (ncmds > 1)
+ if (libexec_pipe_commands(pipe_how, cmds[ncmds - 2], cmds[ncmds - 1]))
+ goto fail;
+ goto constructed;
+
+ case LIBEXEC_RUN_END_CIRCULAR_PIPE:
+ if (ncmds > 1)
+ if (libexec_pipe_commands(pipe_how, cmds[ncmds - 2], cmds[ncmds - 1]))
+ goto fail;
+ pipe_how = va_arg(args, enum libexec_pipe);
+ if (libexec_pipe_commands(pipe_how, cmds[ncmds - 1], cmds[0]))
+ goto fail;
+ goto constructed;
+
+ case LIBEXEC_RUN_PIPE:
+ if (ncmds > 1)
+ if (libexec_pipe_commands(pipe_how, cmds[ncmds - 2], cmds[ncmds - 1]))
+ goto fail;
+ pipe_how = va_arg(args, enum libexec_pipe);
+ goto new_command;
+
+ case LIBEXEC_RUN_SET_EXECUTABLE:
+ if (libexec_set_executable(CMD, va_arg(args, const char *)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_SET_REQUIRE_PATH:
+ libexec_set_require_path(CMD, va_arg(args, int));
+ break;
+
+ case LIBEXEC_RUN_SET_EXEC_FD:
+ if (libexec_set_exec_fd(CMD, va_arg(args, int)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_SET_EXEC_PATH:
+ arg_int1 = va_arg(args, int);
+ arg_str1 = va_arg(args, const char *);
+ if (libexec_set_exec_path(CMD, arg_int1, arg_str1))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_COPY_ENVIRON:
+ if (libexec_copy_environ(CMD, va_arg(args, const char *const *)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_SET_ENVIRON:
+ libexec_set_environ(CMD, va_arg(args, char **));
+ break;
+
+ case LIBEXEC_RUN_CLEAR_ENVIRON:
+ if (libexec_clear_environ(CMD))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_UNSETENV:
+ if (libexec_unsetenv(CMD, va_arg(args, const char *)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_PUTENV:
+ insert_mode = va_arg(args, enum libexec_insert_mode);
+ arg_str1 = va_arg(args, const char *);
+ if (libexec_putenv(CMD, insert_mode, arg_str1))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_SETENV:
+ insert_mode = va_arg(args, enum libexec_insert_mode);
+ arg_str1 = va_arg(args, const char *);
+ arg_str2 = va_arg(args, const char *);
+ if (libexec_setenv(CMD, insert_mode, arg_str1, arg_str2))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_PUTENVF:
+ insert_mode = va_arg(args, enum libexec_insert_mode);
+ arg_str1 = va_arg(args, const char *);
+ if (libexec_vputenvf(CMD, insert_mode, arg_str1, args))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_SETENVF:
+ insert_mode = va_arg(args, enum libexec_insert_mode);
+ arg_str1 = va_arg(args, const char *);
+ arg_str2 = va_arg(args, const char *);
+ if (libexec_vsetenvf(CMD, insert_mode, arg_str1, arg_str2, args))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_OPEN:
+ arg_int1 = va_arg(args, int);
+ arg_str1 = va_arg(args, const char *);
+ arg_int2 = va_arg(args, int);
+ if (libexec_open(CMD, arg_int1, arg_str1, arg_int2, va_arg(args, mode_t)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_OPENAT:
+ arg_int1 = va_arg(args, int);
+ arg_int2 = va_arg(args, int);
+ arg_str1 = va_arg(args, const char *);
+ arg_int3 = va_arg(args, int);
+ if (libexec_openat(CMD, arg_int1, arg_int2, arg_str1, arg_int3, va_arg(args, mode_t)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_OPENAT2:
+ arg_int1 = va_arg(args, int);
+ arg_int2 = va_arg(args, int);
+ arg_str1 = va_arg(args, const char *);
+ arg_voidp1 = va_arg(args, void *);
+ if (libexec_openat2(CMD, arg_int1, arg_int2, arg_str1, arg_voidp1, va_arg(args, size_t)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_DUP:
+ arg_int1 = va_arg(args, int);
+ if (libexec_dup(CMD, arg_int1, va_arg(args, int)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_CLOSE:
+ if (libexec_close(CMD, va_arg(args, int)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_RENUMBER_FD:
+ arg_int1 = va_arg(args, int);
+ if (libexec_renumber_fd(CMD, arg_int1, va_arg(args, int)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_INPUT_COPY:
+ arg_int1 = va_arg(args, int);
+ arg_str1 = va_arg(args, const char *);
+ if (libexec_input_data_copy(CMD, arg_int1, arg_str1, va_arg(args, size_t)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_INPUT_GIFT:
+ arg_int1 = va_arg(args, int);
+ arg_str_nc = va_arg(args, char *);
+ if (libexec_input_data_gift(CMD, arg_int1, arg_str_nc, va_arg(args, size_t)))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_ADD_OUTPUT_FD:
+ arg_int1 = va_arg(args, int);
+ fd = va_arg(args, int);
+ if (libexec_add_output_fd(CMD, arg_int1, fd))
+ goto fail;
+ if (alien_epoll >= 0) {
+ ev.events = EPOLLIN;
+ ev.data.fd = fd;
+ if (epoll_ctl(alien_epoll, EPOLL_CTL_ADD, fd, &ev))
+ goto fail;
+ }
+ break;
+
+ case LIBEXEC_RUN_ADD_OUTPUT:
+ arg_int1 = va_arg(args, int);
+ doc = va_arg(args, struct libexec_document *);
+ new = realloc(docs, (ndocs + 1) * sizeof(*docs));
+ if (!new)
+ goto fail;
+ docs = new;
+ docs[ndocs++] = doc;
+ if (libexec_add_output_document(CMD, arg_int1, doc, O_CLOEXEC | O_NONBLOCK))
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_SET_SIGMASK:
+ sigmask = va_arg(args, const sigset_t *);
+ break;
+
+ case LIBEXEC_RUN_SET_FD_CALLBACK:
+ on_alien_epoll = va_arg(args, int (*)(int, uint32_t, void *));
+ on_alien_epoll_user = va_arg(args, void *);
+ alien_epoll = epoll_create1(EPOLL_CLOEXEC);
+ if (alien_epoll < 0)
+ goto fail;
+ break;
+
+ case LIBEXEC_RUN_SET_REAPER:
+ on_alien_child_death = va_arg(args, int (*)(pid_t, void *));
+ on_alien_child_death_user = va_arg(args, void *);
+ break;
+
+ case LIBEXEC_RUN_SET_ATFORK:
+ after_fork = va_arg(args, int (*)(struct libexec_command *, int, void *));
+ after_fork_user = va_arg(args, void *);
+ break;
+
+ case LIBEXEC_RUN_SET_MUTEX:
+ reap_mutex_control = va_arg(args, int (*)(int, void *));
+ reap_mutex_control_user = va_arg(args, void *);
+ break;
+
+ case LIBEXEC_RUN_SET_INTERRUPT_CALLBACK:
+ on_interrupt = va_arg(args, int (*)(void *));
+ on_interrupt_user = va_arg(args, void *);
+ break;
+
+ default:
+ errno = EINVAL;
+ goto fail;
+ }
+ }
+
+constructed:
+ ret = libexec_run_pipeline(on_alien_epoll, alien_epoll, on_alien_epoll_user,
+ on_alien_child_death, on_alien_child_death_user,
+ after_fork, after_fork_user,
+ reap_mutex_control, reap_mutex_control_user,
+ on_interrupt, on_interrupt_user,
+ sigmask, docs, ndocs, 0, cmds, exit_statuses, ncmds);
+ if (!ret) {
+ out->exit_statuses = exit_statuses;
+ out->proc_count = ncmds;
+ for (i = 0; i < ncmds; i++)
+ if (exit_statuses[i])
+ break;
+ out->all_successful = (i == ncmds);
+ exit_statuses = NULL;
+ }
+
+fail:
+ while (ncmds--) {
+ if (cmds[ncmds]) {
+ libexec_destroy_command(cmds[ncmds]);
+ free(cmds[ncmds]);
+ }
+ }
+ free(cmds);
+ free(docs);
+ if (alien_epoll >= 0)
+ close(alien_epoll);
+ free(exit_statuses);
+ return ret;
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_run.c */
+#endif
diff --git a/libexec_vsetenvf.c b/libexec_vsetenvf.c
new file mode 100644
index 0000000..b4d35c5
--- /dev/null
+++ b/libexec_vsetenvf.c
@@ -0,0 +1,39 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+
+int
+libexec_vsetenvf(struct libexec_command *cmd, enum libexec_insert_mode how, const char *name, const char *value_fmt, va_list args)
+{
+ int len, ret;
+ char *buf;
+ va_list args2;
+ if (!name || !value_fmt) {
+ errno = EINVAL;
+ return -1;
+ }
+ va_copy(args2, args);
+ len = vsnprintf(NULL, 0, value_fmt, args2);
+ va_end(args2);
+ if (len < 0)
+ return -1;
+ buf = malloc((size_t)len + 1U);
+ if (!buf)
+ return -1;
+ if (vsprintf(buf, value_fmt, args) != len)
+ abort();
+ ret = libexec_setenv(cmd, how, name, buf);
+ free(buf);
+ return ret;
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_setenvf.c */
+#endif
diff --git a/libexec_vsetenvf_append.c b/libexec_vsetenvf_append.c
new file mode 100644
index 0000000..c00f8b6
--- /dev/null
+++ b/libexec_vsetenvf_append.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vsetenvf_append(struct libexec_command *cmd, const char *name, const char *value_fmt, va_list args)
+{
+ return libexec_vsetenvf(cmd, LIBEXEC_APPEND, name, value_fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_setenvf_append.c */
+#endif
diff --git a/libexec_vsetenvf_noclobber.c b/libexec_vsetenvf_noclobber.c
new file mode 100644
index 0000000..5164e8b
--- /dev/null
+++ b/libexec_vsetenvf_noclobber.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vsetenvf_noclobber(struct libexec_command *cmd, const char *name, const char *value_fmt, va_list args)
+{
+ return libexec_vsetenvf(cmd, LIBEXEC_NOCLOBBER, name, value_fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_setenvf_noclobber.c */
+#endif
diff --git a/libexec_vsetenvf_noreplace.c b/libexec_vsetenvf_noreplace.c
new file mode 100644
index 0000000..1fb1476
--- /dev/null
+++ b/libexec_vsetenvf_noreplace.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vsetenvf_noreplace(struct libexec_command *cmd, const char *name, const char *value_fmt, va_list args)
+{
+ return libexec_vsetenvf(cmd, LIBEXEC_NOREPLACE, name, value_fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_setenvf_noreplace.c */
+#endif
diff --git a/libexec_vsetenvf_prepend.c b/libexec_vsetenvf_prepend.c
new file mode 100644
index 0000000..63daaae
--- /dev/null
+++ b/libexec_vsetenvf_prepend.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vsetenvf_prepend(struct libexec_command *cmd, const char *name, const char *value_fmt, va_list args)
+{
+ return libexec_vsetenvf(cmd, LIBEXEC_PREPEND, name, value_fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_setenvf_prepend.c */
+#endif
diff --git a/libexec_vsetenvf_replace.c b/libexec_vsetenvf_replace.c
new file mode 100644
index 0000000..d1e8788
--- /dev/null
+++ b/libexec_vsetenvf_replace.c
@@ -0,0 +1,15 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+libexec_vsetenvf_replace(struct libexec_command *cmd, const char *name, const char *value_fmt, va_list args)
+{
+ return libexec_vsetenvf(cmd, LIBEXEC_REPLACE, name, value_fmt, args);
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_setenvf_replace.c */
+#endif
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..ad58f69
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,6 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
+
+FIX_INSTALL_NAME = :
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..55d3eaa
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,6 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION)
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/libexec.$(LIBMAJOREXT)"
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..ed5ec8d
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,6 @@
+LIBEXT = dll
+LIBFLAGS = -shared
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = :
diff --git a/testhelp.c b/testhelp.c
new file mode 100644
index 0000000..74997eb
--- /dev/null
+++ b/testhelp.c
@@ -0,0 +1,366 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST
+#include "common.h"
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wswitch"
+# pragma GCC diagnostic ignored "-Wswitch-enum"
+#endif
+
+
+static char *
+quote(const char *s, char **out)
+{
+ char *ret, *p;
+ int safe = 1;
+ *out = p = ret = malloc(strlen(s) * 4 + 3);
+ if (!ret) {
+ fprintf(stderr, "ran out of memory during printing of assertion failure\n");
+ exit(1);
+ }
+ *p++ = '"';
+ for (; *s; s++) {
+ if (*s == '\r')
+ p = stpcpy(p, "\\r"), safe = 1;
+ else if (*s == '\t')
+ p = stpcpy(p, "\\t"), safe = 1;
+ else if (*s == '\a')
+ p = stpcpy(p, "\\a"), safe = 1;
+ else if (*s == '\f')
+ p = stpcpy(p, "\\f"), safe = 1;
+ else if (*s == '\v')
+ p = stpcpy(p, "\\v"), safe = 1;
+ else if (*s == '\b')
+ p = stpcpy(p, "\\b"), safe = 1;
+ else if (*s == '\n')
+ p = stpcpy(p, "\\n"), safe = 1;
+ else if (*s == '\"')
+ p = stpcpy(p, "\\\""), safe = 1;
+ else if (*s == '\\')
+ p = stpcpy(p, "\\\\"), safe = 1;
+ else if (*s < ' ' || *s >= 127)
+ p += sprintf(p, "\\x%02X", (unsigned char)*s), safe = 0;
+ else if (*s == '?' && p[-1] == '?')
+ p += sprintf(p, "\"\"?"), safe = 1;
+ else if (isxdigit(*s) && !safe)
+ p += sprintf(p, "\"\"%c", *s), safe = 1;
+ else
+ *p++ = *s, safe = 1;
+ }
+ *p++ = '"';
+ *p++ = '\0';
+ return ret;
+}
+
+
+#define CASE(HOW, OP)\
+ case HOW:\
+ if ((*have) OP (*expect))\
+ return;\
+ symbol = #OP;\
+ break
+
+
+#define CASE_LOGIC(HOW, OP1, OP2, OPERAND3_IS_EXPECT)\
+ case HOW:\
+ if (((*have) OP1 (*expect)) OP2 (OPERAND3_IS_EXPECT ? (*expect) : 0))\
+ return;\
+ symbol = #OP1;\
+ symbol2 = #OP2;\
+ operand3_is_expect = (OPERAND3_IS_EXPECT);\
+ break
+
+
+static void
+test_assert_uint(const char *file, int line, enum assert_how how, const char *have_string,
+ const char *expect_string, const uintmax_t *have, const uintmax_t *expect)
+{
+ const char *symbol;
+ const char *symbol2 = NULL;
+ int operand3_is_expect;
+ switch (how) {
+ case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
+ case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
+ case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
+ case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
+ }
+ switch (how) {
+ CASE(ASSERT_EQ, ==);
+ CASE(ASSERT_NE, !=);
+ CASE(ASSERT_LT, <);
+ CASE(ASSERT_LE, <=);
+ CASE(ASSERT_GT, >);
+ CASE(ASSERT_GE, >=);
+ CASE_LOGIC(ASSERT_CONTAINS_ALL, &, ==, 1);
+ CASE_LOGIC(ASSERT_CONTAINS_ANY, &, !=, 0);
+ CASE_LOGIC(ASSERT_CONTAINS_NOT_ALL, &, !=, 1);
+ CASE_LOGIC(ASSERT_CONTAINS_NOT_ANY, &, ==, 0);
+ CASE_LOGIC(ASSERT_IS_IN, &~, ==, 0);
+ CASE_LOGIC(ASSERT_NOT_IN, &~, !=, 0);
+ default:
+ abort();
+ }
+ if (!symbol2)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %ju %s %ju\n",
+ file, line, have_string, symbol, expect_string, *have, symbol, *expect);
+ else if (!operand3_is_expect)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting ((%s) %s (%s)) %s 0, actualised as (%#jx %s %#jx) %s 0\n",
+ file, line, have_string, symbol, expect_string, symbol2, *have, symbol, *expect, symbol2);
+ else
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting ((%s) %s (%s)) %s (%s), actualised as (%#jx %s %#jx) %s %#jx\n",
+ file, line, have_string, symbol, expect_string, symbol2, expect_string,
+ *have, symbol, *expect, symbol2, *expect);
+ exit(1);
+}
+
+
+static void
+test_assert_int(const char *file, int line, enum assert_how how, const char *have_string,
+ const char *expect_string, const intmax_t *have, const intmax_t *expect)
+{
+ const char *symbol;
+ switch (how) {
+ case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
+ case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
+ case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
+ case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
+ }
+ switch (how) {
+ CASE(ASSERT_EQ, ==);
+ CASE(ASSERT_NE, !=);
+ CASE(ASSERT_LT, <);
+ CASE(ASSERT_LE, <=);
+ CASE(ASSERT_GT, >);
+ CASE(ASSERT_GE, >=);
+ default:
+ test_assert_uint(file, line, how, have_string, expect_string,
+ (const uintmax_t *)have, (const uintmax_t *)expect);
+ return;
+ }
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %ji %s %ji\n",
+ file, line, have_string, symbol, expect_string, *have, symbol, *expect);
+ exit(1);
+}
+
+
+#define PTR_CASE(HOW, OP)\
+ case HOW:\
+ if ((have) OP (expect))\
+ return;\
+ symbol = #OP;\
+ break
+
+
+static void
+test_assert_ptr(const char *file, int line, enum assert_how how, const char *have_string,
+ const char *expect_string, const char *have, const char *expect)
+{
+ const char *symbol;
+ switch (how) {
+ case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
+ case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
+ case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
+ case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
+ }
+ switch (how) {
+ PTR_CASE(ASSERT_EQ, ==);
+ PTR_CASE(ASSERT_NE, !=);
+ PTR_CASE(ASSERT_LT, <);
+ PTR_CASE(ASSERT_LE, <=);
+ PTR_CASE(ASSERT_GT, >);
+ PTR_CASE(ASSERT_GE, >=);
+ default:
+ abort();
+ }
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %p %s %p\n",
+ file, line, have_string, symbol, expect_string, have, symbol, expect);
+ exit(1);
+}
+
+
+#define STR_CASE(HOW, FUNC)\
+ case HOW:\
+ if (FUNC(have, expect))\
+ return;\
+ function = #FUNC;\
+ break
+
+#define SWAP_STR_CASE(HOW, NEW_HOW)\
+ case HOW:\
+ test_assert_str(file, line, how, expect_string, have_string, expect, have, rev);\
+ return
+
+
+static void
+test_assert_str(const char *file, int line, enum assert_how how, const char *have_string,
+ const char *expect_string, const char *have, const char *expect, int rev)
+{
+ const char *function;
+ size_t have_len;
+ size_t expect_len;
+ char *have_quote = NULL;
+ char *expect_quote = NULL;
+
+ if (!have && !expect)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) and (%s) are NULL\n",
+ file, line, have_string, expect_string);
+ else if (!have)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) was NULL\n",
+ file, line, have_string);
+ else if (!expect)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) was NULL\n",
+ file, line, expect_string);
+ if (!have || !expect)
+ exit(1);
+
+ switch (how) {
+ STR_CASE(ASSERT_EQ, !strcmp);
+ STR_CASE(ASSERT_NE, strcmp);
+ STR_CASE(ASSERT_IS_IN, strstr);
+ STR_CASE(ASSERT_NOT_IN, !strstr);
+ SWAP_STR_CASE(ASSERT_CONTAINS_ALL, ASSERT_IS_IN);
+ SWAP_STR_CASE(ASSERT_CONTAINS_NOT_ALL, ASSERT_NOT_INT);
+ SWAP_STR_CASE(ASSERT_LE, ASSERT_GE);
+ SWAP_STR_CASE(ASSERT_LT, ASSERT_GT);
+ SWAP_STR_CASE(ASSERT_NOT_LE, ASSERT_NOT_GE);
+ SWAP_STR_CASE(ASSERT_NOT_LT, ASSERT_NOT_GT);
+ default:
+ have_len = strlen(have);
+ expect_len = strlen(expect);
+ if (rev)
+ goto assert_end;
+ else
+ goto assert_start;
+ }
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting %s(%s, %s), actualised as %s(%s, %s)\n",
+ file, line, function, have_string, expect_string,
+ function, quote(have, &have_quote), quote(expect, &expect_quote));
+free_and_exit:
+ free(have_quote);
+ free(expect_quote);
+ exit(1);
+
+assert_start:
+ switch (how) {
+ case ASSERT_GE:
+ if (have_len >= expect_len && !strncmp(have, expect, expect_len))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) >= strlen(%s) && !strncmp(%s, %s, strlen(%s)), "
+ "actualised as %zu >= %zu && !strncmp(%s, %s, %zu)\n",
+ file, line, have_string, expect_string, have_string, expect_string, expect_string,
+ have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
+ break;
+ case ASSERT_NOT_GE:
+ if (have_len < expect_len || strncmp(have, expect, expect_len))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) < strlen(%s) || strncmp(%s, %s, strlen(%s)), "
+ "actualised as %zu < %zu && !strncmp(%s, %s, %zu)\n",
+ file, line, have_string, expect_string, have_string, expect_string, expect_string,
+ have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
+ break;
+ case ASSERT_GT:
+ if (have_len > expect_len && !strncmp(have, expect, expect_len))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) > strlen(%s) && !strncmp(%s, %s, strlen(%s)), "
+ "actualised as %zu > %zu && !strncmp(%s, %s, %zu)\n",
+ file, line, have_string, expect_string, have_string, expect_string, expect_string,
+ have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
+ break;
+ case ASSERT_NOT_GT:
+ if (have_len <= expect_len || strncmp(have, expect, expect_len))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) <= strlen(%s) || strncmp(%s, %s, strlen(%s)), "
+ "actualised as %zu <= %zu || strncmp(%s, %s, %zu)\n",
+ file, line, have_string, expect_string, have_string, expect_string, expect_string,
+ have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
+ break;
+ default:
+ abort();
+ }
+ goto free_and_exit;
+
+assert_end:
+ switch (how) {
+ case ASSERT_GE:
+ if (have_len >= expect_len && !strcmp(&have[have_len - expect_len], expect))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) >= strlen(%s) && !strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
+ "actualised as %zu >= %zu && !strcmp(&%s[%ti], %s)\n",
+ file, line, have_string, expect_string, have_string, have_string, expect_string,
+ expect_string, have_len, expect_len, quote(have, &have_quote),
+ (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
+ break;
+ case ASSERT_NOT_GE:
+ if (have_len > expect_len || strcmp(&have[have_len - expect_len], expect))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) < strlen(%s) || strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
+ "actualised as %zu < %zu && !strcmp(&%s[%ti], %s)\n",
+ file, line, have_string, expect_string, have_string, have_string, expect_string,
+ expect_string, have_len, expect_len, quote(have, &have_quote),
+ (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
+ break;
+ case ASSERT_GT:
+ if (have_len > expect_len && !strcmp(&have[have_len - expect_len], expect))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) > strlen(%s) && !strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
+ "actualised as %zu > %zu && !strcmp(&%s[%ti], %s)\n",
+ file, line, have_string, expect_string, have_string, have_string, expect_string,
+ expect_string, have_len, expect_len, quote(have, &have_quote),
+ (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
+ break;
+ case ASSERT_NOT_GT:
+ if (have_len <= expect_len || strcmp(&have[have_len - expect_len], expect))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) <= strlen(%s) || strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
+ "actualised as %zu <= %zu || strcmp(&%s[%ti], %s)\n",
+ file, line, have_string, expect_string, have_string, have_string, expect_string,
+ expect_string, have_len, expect_len, quote(have, &have_quote),
+ (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
+ break;
+ default:
+ abort();
+ }
+ goto free_and_exit;
+}
+
+
+void
+test_assert(const char *file, int line, enum assert_type type, enum assert_how how,
+ const char *have_string, const char *expect_string, const void *have, const void *expect)
+{
+ switch (type) {
+ case ASSERT_ENUM:
+ case ASSERT_UINT:
+ test_assert_uint(file, line, how, have_string, expect_string, have, expect);
+ break;
+
+ case ASSERT_INT:
+ test_assert_int(file, line, how, have_string, expect_string, have, expect);
+ break;
+
+ case ASSERT_NULL:
+ case ASSERT_PTR:
+ test_assert_ptr(file, line, how, have_string, expect_string, have, expect);
+ break;
+
+ case ASSERT_STR:
+ test_assert_str(file, line, how, have_string, expect_string, have, expect, 0);
+ break;
+
+ case ASSERT_STR_REV:
+ test_assert_str(file, line, how, have_string, expect_string, have, expect, 1);
+ break;
+
+ default:
+ abort();
+ }
+}