{"thread":{"id":"22940","subject":"[PATCH 6/6] Dying in an async procedure should only exit the thread, not the process.","startedAt":"2010-03-06T15:40:38Z","lastAt":"2010-03-06T15:40:43Z","messageCount":6,"participants":["Johannes Sixt"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"136328","messageId":"40f4b185c3661e5826ec2e58124137cd735e7199.1267889703.git.j6t@kdbg.org","threadId":"22940","inReplyTo":"cover.1267889703.git.j6t@kdbg.org","subject":"[PATCH 1/6] Modernize t5530-upload-pack-error.","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2010-03-06T15:40:38Z","receivedAt":"2010-03-06T15:40:38Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Some tests did not use test_must_fail.\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n t/t5530-upload-pack-error.sh |   18 +++++++++---------\n 1 files changed, 9 insertions(+), 9 deletions(-)\n\ndiff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh\nindex a696b87..044603c 100755\n--- a/t/t5530-upload-pack-error.sh\n+++ b/t/t5530-upload-pack-error.sh\n@@ -32,9 +32,9 @@ test_expect_success 'fsck fails' '\n \n test_expect_success 'upload-pack fails due to error in pack-objects packing' '\n \n-\t! echo \"0032want $(git rev-parse HEAD)\n-00000009done\n-0000\" | git upload-pack . > /dev/null 2> output.err &&\n+\tprintf \"0032want %s\\n00000009done\\n0000\" \\\n+\t\t$(git rev-parse HEAD) >input &&\n+\ttest_must_fail git upload-pack . <input >/dev/null 2>output.err &&\n \tgrep \"unable to read\" output.err &&\n \tgrep \"pack-objects died\" output.err\n '\n@@ -51,9 +51,9 @@ test_expect_success 'fsck fails' '\n '\n test_expect_success 'upload-pack fails due to error in rev-list' '\n \n-\t! echo \"0032want $(git rev-parse HEAD)\n-0034shallow $(git rev-parse HEAD^)00000009done\n-0000\" | git upload-pack . > /dev/null 2> output.err &&\n+\tprintf \"0032want %s\\n0034shallow %s00000009done\\n0000\" \\\n+\t\t$(git rev-parse HEAD) $(git rev-parse HEAD^) >input &&\n+\ttest_must_fail git upload-pack . <input >/dev/null 2>output.err &&\n \t# pack-objects survived\n \tgrep \"Total.*, reused\" output.err &&\n \t# but there was an error, which must have been in rev-list\n@@ -62,9 +62,9 @@ test_expect_success 'upload-pack fails due to error in rev-list' '\n \n test_expect_success 'upload-pack fails due to error in pack-objects enumeration' '\n \n-\t! echo \"0032want $(git rev-parse HEAD)\n-00000009done\n-0000\" | git upload-pack . > /dev/null 2> output.err &&\n+\tprintf \"0032want %s\\n00000009done\\n0000\" \\\n+\t\t$(git rev-parse HEAD) >input &&\n+\ttest_must_fail git upload-pack . <input >/dev/null 2>output.err &&\n \tgrep \"bad tree object\" output.err &&\n \tgrep \"pack-objects died\" output.err\n '\n-- \n1.7.0.rc2.65.g7b13a\n"},{"id":"136319","messageId":"dd91671bd5bb1a60a314e71a265ba0068080a47e.1267889703.git.j6t@kdbg.org","threadId":"22940","inReplyTo":"cover.1267889703.git.j6t@kdbg.org","subject":"[PATCH 2/6] Make report() from usage.c public as vreportf() and use it.","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2010-03-06T15:40:39Z","receivedAt":"2010-03-06T15:40:39Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"There exist already a number of static functions named 'report', therefore,\nthe function name was changed.\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n fast-import.c     |    8 +++-----\n git-compat-util.h |    1 +\n http-backend.c    |    5 +----\n usage.c           |   10 +++++-----\n 4 files changed, 10 insertions(+), 14 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex 74f08bd..5bb3d41 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -483,14 +483,12 @@ static void dump_marks(void);\n static NORETURN void die_nicely(const char *err, va_list params)\n {\n \tstatic int zombie;\n-\tchar message[2 * PATH_MAX];\n \n-\tvsnprintf(message, sizeof(message), err, params);\n-\tfputs(\"fatal: \", stderr);\n-\tfputs(message, stderr);\n-\tfputc('\\n', stderr);\n+\tvreportf(\"fatal: \", err, params);\n \n \tif (!zombie) {\n+\t\tchar message[2 * PATH_MAX];\n+\n \t\tzombie = 1;\n \t\twrite_crash_report(message);\n \t\tend_packfile();\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex a3c4537..3cabcdd 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -192,6 +192,7 @@ extern char *gitbasename(char *);\n #include \"compat/bswap.h\"\n \n /* General helper functions */\n+extern void vreportf(const char *prefix, const char *err, va_list params);\n extern NORETURN void usage(const char *err);\n extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2)));\n extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));\ndiff --git a/http-backend.c b/http-backend.c\nindex 345c12b..8c7b7d0 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -538,14 +538,11 @@ static void service_rpc(char *service_name)\n \n static NORETURN void die_webcgi(const char *err, va_list params)\n {\n-\tchar buffer[1000];\n-\n \thttp_status(500, \"Internal Server Error\");\n \thdr_nocache();\n \tend_headers();\n \n-\tvsnprintf(buffer, sizeof(buffer), err, params);\n-\tfprintf(stderr, \"fatal: %s\\n\", buffer);\n+\tvreportf(\"fatal: \", err, params);\n \texit(0);\n }\n \ndiff --git a/usage.c b/usage.c\nindex 79856a2..ec4cf53 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -5,7 +5,7 @@\n  */\n #include \"git-compat-util.h\"\n \n-static void report(const char *prefix, const char *err, va_list params)\n+void vreportf(const char *prefix, const char *err, va_list params)\n {\n \tchar msg[4096];\n \tvsnprintf(msg, sizeof(msg), err, params);\n@@ -14,24 +14,24 @@ static void report(const char *prefix, const char *err, va_list params)\n \n static NORETURN void usage_builtin(const char *err, va_list params)\n {\n-\treport(\"usage: \", err, params);\n+\tvreportf(\"usage: \", err, params);\n \texit(129);\n }\n \n static NORETURN void die_builtin(const char *err, va_list params)\n {\n-\treport(\"fatal: \", err, params);\n+\tvreportf(\"fatal: \", err, params);\n \texit(128);\n }\n \n static void error_builtin(const char *err, va_list params)\n {\n-\treport(\"error: \", err, params);\n+\tvreportf(\"error: \", err, params);\n }\n \n static void warn_builtin(const char *warn, va_list params)\n {\n-\treport(\"warning: \", warn, params);\n+\tvreportf(\"warning: \", warn, params);\n }\n \n /* If we are in a dlopen()ed .so write to a global variable would segfault\n-- \n1.7.0.rc2.65.g7b13a\n"},{"id":"136317","messageId":"dc9be33a660733a88e1c2fd8a3d9c190a7779d5d.1267889703.git.j6t@kdbg.org","threadId":"22940","inReplyTo":"cover.1267889703.git.j6t@kdbg.org","subject":"[PATCH 3/6] Fix signature of fcntl() compatibility dummy","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2010-03-06T15:40:40Z","receivedAt":"2010-03-06T15:40:40Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Obviously, this function was never called with two arguments in Windows\ncode sections, but this will be the case in a subsequent patch.\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n compat/mingw.h |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex e254fb4..f53bcca 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -89,7 +89,7 @@ static inline int getuid()\n { return 1; }\n static inline struct passwd *getpwnam(const char *name)\n { return NULL; }\n-static inline int fcntl(int fd, int cmd, long arg)\n+static inline int fcntl(int fd, int cmd, ...)\n {\n \tif (cmd == F_GETFD || cmd == F_SETFD)\n \t\treturn 0;\n-- \n1.7.0.rc2.65.g7b13a\n"},{"id":"136334","messageId":"630b17c350a0014d8099576faeaf113d2804bde6.1267889703.git.j6t@kdbg.org","threadId":"22940","inReplyTo":"cover.1267889703.git.j6t@kdbg.org","subject":"[PATCH 4/6] Windows: more pthreads functions","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2010-03-06T15:40:41Z","receivedAt":"2010-03-06T15:40:41Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"This adds:\n\n   pthread_self\n   pthread_equal\n   pthread_exit\n   pthread_key_create\n   pthread_setspecific\n   pthread_getspecific\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n compat/win32/pthread.c |    8 ++++++++\n compat/win32/pthread.h |   25 +++++++++++++++++++++++++\n 2 files changed, 33 insertions(+), 0 deletions(-)\n\ndiff --git a/compat/win32/pthread.c b/compat/win32/pthread.c\nindex 0f949fc..010e875 100644\n--- a/compat/win32/pthread.c\n+++ b/compat/win32/pthread.c\n@@ -16,6 +16,7 @@\n static unsigned __stdcall win32_start_routine(void *arg)\n {\n \tpthread_t *thread = arg;\n+\tthread->tid = GetCurrentThreadId();\n \tthread->arg = thread->start_routine(thread->arg);\n \treturn 0;\n }\n@@ -49,6 +50,13 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr)\n \t}\n }\n \n+pthread_t pthread_self(void)\n+{\n+\tpthread_t t = { 0 };\n+\tt.tid = GetCurrentThreadId();\n+\treturn t;\n+}\n+\n int pthread_cond_init(pthread_cond_t *cond, const void *unused)\n {\n \tcond->waiters = 0;\ndiff --git a/compat/win32/pthread.h b/compat/win32/pthread.h\nindex c72f100..c7b8241 100644\n--- a/compat/win32/pthread.h\n+++ b/compat/win32/pthread.h\n@@ -52,6 +52,7 @@ typedef struct {\n \tHANDLE handle;\n \tvoid *(*start_routine)(void*);\n \tvoid *arg;\n+\tDWORD tid;\n } pthread_t;\n \n extern int pthread_create(pthread_t *thread, const void *unused,\n@@ -65,4 +66,28 @@ extern int pthread_create(pthread_t *thread, const void *unused,\n \n extern int win32_pthread_join(pthread_t *thread, void **value_ptr);\n \n+#define pthread_equal(t1, t2) ((t1).tid == (t2).tid)\n+extern pthread_t pthread_self(void);\n+\n+static inline int pthread_exit(void *ret)\n+{\n+\tExitThread((DWORD)ret);\n+}\n+\n+typedef DWORD pthread_key_t;\n+static inline int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *value))\n+{\n+\treturn (*keyp = TlsAlloc()) == TLS_OUT_OF_INDEXES ? EAGAIN : 0;\n+}\n+\n+static inline int pthread_setspecific(pthread_key_t key, const void *value)\n+{\n+\treturn TlsSetValue(key, (void *)value) ? 0 : EINVAL;\n+}\n+\n+static inline void *pthread_getspecific(pthread_key_t key)\n+{\n+\treturn TlsGetValue(key);\n+}\n+\n #endif /* PTHREAD_H */\n-- \n1.7.0.rc2.65.g7b13a\n"},{"id":"136324","messageId":"771cabb3efea5ced5049bdd34c392a218d1f3c4f.1267889703.git.j6t@kdbg.org","threadId":"22940","inReplyTo":"cover.1267889703.git.j6t@kdbg.org","subject":"[PATCH 5/6] Reimplement async procedures using pthreads","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2010-03-06T15:40:42Z","receivedAt":"2010-03-06T15:40:42Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"On Windows, async procedures have always been run in threads, and the\nimplementation used Windows specific APIs. Rewrite the code to use pthreads.\n\nA new configuration option is introduced so that the threaded implementation\ncan also be used on POSIX systems. Since this option is intended only as\nplayground on POSIX, but is mandatory on Windows, the option is not\ndocumented.\n\nOne detail is that on POSIX it is necessary to set FD_CLOEXEC on the pipe\nhandles. On Windows, this is not needed because pipe handles are not\ninherited to child processes, and the new calls to set_cloexec() are\neffectively no-ops.\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n Makefile      |    5 +++++\n run-command.c |   41 +++++++++++++++++++++++------------------\n run-command.h |    8 ++++++--\n 3 files changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex afedb54..c5f0f9b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -949,6 +949,7 @@ ifeq ($(uname_S),Windows)\n \tNO_CURL = YesPlease\n \tNO_PYTHON = YesPlease\n \tBLK_SHA1 = YesPlease\n+\tASYNC_AS_THREAD = YesPlease\n \n \tCC = compat/vcbuild/scripts/clink.pl\n \tAR = compat/vcbuild/scripts/lib.pl\n@@ -1000,6 +1001,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tNO_REGEX = YesPlease\n \tNO_PYTHON = YesPlease\n \tBLK_SHA1 = YesPlease\n+\tASYNC_AS_THREAD = YesPlease\n \tCOMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n \tCOMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \\\n@@ -1301,6 +1303,9 @@ ifdef NO_PTHREADS\n else\n \tEXTLIBS += $(PTHREAD_LIBS)\n \tLIB_OBJS += thread-utils.o\n+ifdef ASYNC_AS_THREAD\n+\tBASIC_CFLAGS += -DASYNC_AS_THREAD\n+endif\n endif\n \n ifdef DIR_HAS_BSD_GROUP_SEMANTICS\ndiff --git a/run-command.c b/run-command.c\nindex 0cd7f02..77aefff 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -82,6 +82,7 @@ static NORETURN void die_child(const char *err, va_list params)\n \twrite(child_err, \"\\n\", 1);\n \texit(128);\n }\n+#endif\n \n static inline void set_cloexec(int fd)\n {\n@@ -89,7 +90,6 @@ static inline void set_cloexec(int fd)\n \tif (flags >= 0)\n \t\tfcntl(fd, F_SETFD, flags | FD_CLOEXEC);\n }\n-#endif\n \n static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)\n {\n@@ -447,11 +447,12 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n \treturn run_command(&cmd);\n }\n \n-#ifdef WIN32\n-static unsigned __stdcall run_thread(void *data)\n+#ifdef ASYNC_AS_THREAD\n+static void *run_thread(void *data)\n {\n \tstruct async *async = data;\n-\treturn async->proc(async->proc_in, async->proc_out, async->data);\n+\tintptr_t ret = async->proc(async->proc_in, async->proc_out, async->data);\n+\treturn (void *)ret;\n }\n #endif\n \n@@ -497,7 +498,7 @@ int start_async(struct async *async)\n \telse\n \t\tproc_out = -1;\n \n-#ifndef WIN32\n+#ifndef ASYNC_AS_THREAD\n \t/* Flush stdio before fork() to avoid cloning buffers */\n \tfflush(NULL);\n \n@@ -524,12 +525,18 @@ int start_async(struct async *async)\n \telse if (async->out)\n \t\tclose(async->out);\n #else\n+\tif (proc_in >= 0)\n+\t\tset_cloexec(proc_in);\n+\tif (proc_out >= 0)\n+\t\tset_cloexec(proc_out);\n \tasync->proc_in = proc_in;\n \tasync->proc_out = proc_out;\n-\tasync->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL);\n-\tif (!async->tid) {\n-\t\terror(\"cannot create thread: %s\", strerror(errno));\n-\t\tgoto error;\n+\t{\n+\t\tint err = pthread_create(&async->tid, NULL, run_thread, async);\n+\t\tif (err) {\n+\t\t\terror(\"cannot create thread: %s\", strerror(err));\n+\t\t\tgoto error;\n+\t\t}\n \t}\n #endif\n \treturn 0;\n@@ -549,17 +556,15 @@ error:\n \n int finish_async(struct async *async)\n {\n-#ifndef WIN32\n-\tint ret = wait_or_whine(async->pid, \"child process\", 0);\n+#ifndef ASYNC_AS_THREAD\n+\treturn wait_or_whine(async->pid, \"child process\", 0);\n #else\n-\tDWORD ret = 0;\n-\tif (WaitForSingleObject(async->tid, INFINITE) != WAIT_OBJECT_0)\n-\t\tret = error(\"waiting for thread failed: %lu\", GetLastError());\n-\telse if (!GetExitCodeThread(async->tid, &ret))\n-\t\tret = error(\"cannot get thread exit code: %lu\", GetLastError());\n-\tCloseHandle(async->tid);\n+\tvoid *ret = (void *)(intptr_t)(-1);\n+\n+\tif (pthread_join(async->tid, &ret))\n+\t\terror(\"pthread_join failed\");\n+\treturn (int)(intptr_t)ret;\n #endif\n-\treturn ret;\n }\n \n int run_hook(const char *index_file, const char *name, ...)\ndiff --git a/run-command.h b/run-command.h\nindex 94619f5..40db39c 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -1,6 +1,10 @@\n #ifndef RUN_COMMAND_H\n #define RUN_COMMAND_H\n \n+#ifdef ASYNC_AS_THREAD\n+#include <pthread.h>\n+#endif\n+\n struct child_process {\n \tconst char **argv;\n \tpid_t pid;\n@@ -74,10 +78,10 @@ struct async {\n \tvoid *data;\n \tint in;\t\t/* caller writes here and closes it */\n \tint out;\t/* caller reads from here and closes it */\n-#ifndef WIN32\n+#ifndef ASYNC_AS_THREAD\n \tpid_t pid;\n #else\n-\tHANDLE tid;\n+\tpthread_t tid;\n \tint proc_in;\n \tint proc_out;\n #endif\n-- \n1.7.0.rc2.65.g7b13a\n"},{"id":"136315","messageId":"66dbacb267415a0e2d71c82467093245c06e84e5.1267889703.git.j6t@kdbg.org","threadId":"22940","inReplyTo":"cover.1267889703.git.j6t@kdbg.org","subject":"[PATCH 6/6] Dying in an async procedure should only exit the thread, not the process.","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2010-03-06T15:40:43Z","receivedAt":"2010-03-06T15:40:43Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Async procedures are intended as helpers that perform a very restricted\ntask, and the caller usually has to manage them in a larger context.\nConceptually, the async procedure is not concerned with the \"bigger\npicture\" in whose context it is run. When it dies, it is not supposed\nto destroy this \"bigger picture\", but rather only its own limit view\nof the world. On POSIX, the async procedure is run in its own process,\nand exiting this process naturally had only these limited effects.\n\nOn Windows (or when ASYNC_AS_THREAD is set), calling die() exited the\nwhole process, destroying the caller (the \"big picture\") as well.\nThis fixes it to exit only the thread.\n\nWithout ASYNC_AS_THREAD, one particular effect of exiting the async\nprocedure process is that it automatically closes file descriptors, most\nnotably the writable end of the pipe that the async procedure writes to.\n\nThe async API already requires that the async procedure closes the pipe\nends when it exits normally. But for calls to die() no requirements are\nimposed. In the non-threaded case the pipe ends are closed implicitly\nby the exiting process, but in the threaded case, the die routine must\ntake care of closing them.\n\nNow t5530-upload-pack-error.sh passes on Windows.\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n run-command.c |   34 ++++++++++++++++++++++++++++++++++\n 1 files changed, 34 insertions(+), 0 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex 77aefff..66cc4bf 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -448,12 +448,35 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n }\n \n #ifdef ASYNC_AS_THREAD\n+static pthread_t main_thread;\n+static int main_thread_set;\n+static pthread_key_t async_key;\n+\n static void *run_thread(void *data)\n {\n \tstruct async *async = data;\n+\n+\tpthread_setspecific(async_key, async);\n+\n \tintptr_t ret = async->proc(async->proc_in, async->proc_out, async->data);\n \treturn (void *)ret;\n }\n+\n+static NORETURN void die_async(const char *err, va_list params)\n+{\n+\tvreportf(\"fatal: \", err, params);\n+\n+\tif (!pthread_equal(main_thread, pthread_self())) {\n+\t\tstruct async *async = pthread_getspecific(async_key);\n+\t\tif (async->proc_in >= 0)\n+\t\t\tclose(async->proc_in);\n+\t\tif (async->proc_out >= 0)\n+\t\t\tclose(async->proc_out);\n+\t\tpthread_exit((void *)128);\n+\t}\n+\n+\texit(128);\n+}\n #endif\n \n int start_async(struct async *async)\n@@ -525,6 +548,17 @@ int start_async(struct async *async)\n \telse if (async->out)\n \t\tclose(async->out);\n #else\n+\tif (!main_thread_set) {\n+\t\t/*\n+\t\t * We assume that the first time that start_async is called\n+\t\t * it is from the main thread.\n+\t\t */\n+\t\tmain_thread_set = 1;\n+\t\tmain_thread = pthread_self();\n+\t\tpthread_key_create(&async_key, NULL);\n+\t\tset_die_routine(die_async);\n+\t}\n+\n \tif (proc_in >= 0)\n \t\tset_cloexec(proc_in);\n \tif (proc_out >= 0)\n-- \n1.7.0.rc2.65.g7b13a\n"}]}