1 /* hooks.c : running repository hooks
3 * ====================================================================
4 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
22 #include <apr_pools.h>
23 #include <apr_file_io.h>
26 #include <apr_portable.h>
31 #include "svn_error.h"
33 #include "svn_repos.h"
36 #include "svn_private_config.h"
40 /*** Hook drivers. ***/
43 /* Helper function for run_hook_cmd(). Wait for a hook to finish
44 executing and return either SVN_NO_ERROR if the hook script completed
45 without error, or an error describing the reason for failure.
47 NAME and CMD are the name and path of the hook program, CMD_PROC
48 is a pointer to the structure representing the running process,
49 and READ_ERRHANDLE is an open handle to the hook's stderr.
51 Hooks are considered to have failed if we are unable to wait for the
52 process, if we are unable to read from the hook's stderr, if the
53 process has failed to exit cleanly (due to a coredump, for example),
54 or if the process returned a non-zero return code.
56 Any error output returned by the hook's stderr will be included in an
57 error message, though the presence of output on stderr is not itself
58 a reason to fail a hook. */
60 check_hook_result(const char *name
, const char *cmd
, apr_proc_t
*cmd_proc
,
61 apr_file_t
*read_errhandle
, apr_pool_t
*pool
)
63 svn_error_t
*err
, *err2
;
64 svn_stringbuf_t
*native_stderr
, *failure_message
;
65 const char *utf8_stderr
;
67 apr_exit_why_e exitwhy
;
69 err2
= svn_stringbuf_from_aprfile(&native_stderr
, read_errhandle
, pool
);
71 err
= svn_io_wait_for_cmd(cmd_proc
, cmd
, &exitcode
, &exitwhy
, pool
);
74 svn_error_clear(err2
);
78 if (APR_PROC_CHECK_EXIT(exitwhy
) && exitcode
== 0)
80 /* The hook exited cleanly. However, if we got an error reading
81 the hook's stderr, fail the hook anyway, because this might be
82 symptomatic of a more important problem. */
85 return svn_error_createf
86 (SVN_ERR_REPOS_HOOK_FAILURE
, err2
,
87 _("'%s' hook succeeded, but error output could not be read"),
94 /* The hook script failed. */
96 /* If we got the stderr output okay, try to translate it into UTF-8.
97 Ensure there is something sensible in the UTF-8 string regardless. */
100 err2
= svn_utf_cstring_to_utf8(&utf8_stderr
, native_stderr
->data
, pool
);
102 utf8_stderr
= _("[Error output could not be translated from the "
103 "native locale to UTF-8.]");
107 utf8_stderr
= _("[Error output could not be read.]");
109 /*### It would be nice to include the text of any translation or read
110 error in the messages above before we clear it here. */
111 svn_error_clear(err2
);
113 if (!APR_PROC_CHECK_EXIT(exitwhy
))
115 failure_message
= svn_stringbuf_createf(pool
,
116 _("'%s' hook failed (did not exit cleanly: "
117 "apr_exit_why_e was %d, exitcode was %d). "),
118 name
, exitwhy
, exitcode
);
122 failure_message
= svn_stringbuf_createf(pool
,
123 _("'%s' hook failed (exited with a non-zero exitcode of %d). "),
129 svn_stringbuf_appendcstr(failure_message
,
130 _("The following error output was produced "
132 svn_stringbuf_appendcstr(failure_message
, utf8_stderr
);
136 svn_stringbuf_appendcstr(failure_message
,
137 _("No error output was produced by the "
141 return svn_error_create(SVN_ERR_REPOS_HOOK_FAILURE
, err
,
142 failure_message
->data
);
146 /* NAME, CMD and ARGS are the name, path to and arguments for the hook
147 program that is to be run. The hook's exit status will be checked,
148 and if an error occurred the hook's stderr output will be added to
151 If STDIN_HANDLE is non-null, pass it as the hook's stdin, else pass
152 no stdin to the hook. */
154 run_hook_cmd(const char *name
,
157 apr_file_t
*stdin_handle
,
161 apr_file_t
*read_errhandle
, *write_errhandle
, *null_handle
;
162 apr_status_t apr_err
;
166 /* Create a pipe to access stderr of the child. */
167 apr_err
= apr_file_pipe_create(&read_errhandle
, &write_errhandle
, pool
);
169 return svn_error_wrap_apr
170 (apr_err
, _("Can't create pipe for hook '%s'"), cmd
);
172 /* Pipes are inherited by default, but we don't want that, since
173 APR will duplicate the write end of the pipe for the child process.
174 Not closing the read end is harmless, but if the write end is inherited,
175 it will be inherited by grandchildren as well. This causes problems
176 if a hook script puts long-running jobs in the background. Even if
177 they redirect stderr to something else, the write end of our pipe will
178 still be open, causing us to block. */
179 apr_err
= apr_file_inherit_unset(read_errhandle
);
181 return svn_error_wrap_apr
182 (apr_err
, _("Can't make pipe read handle non-inherited for hook '%s'"),
185 apr_err
= apr_file_inherit_unset(write_errhandle
);
187 return svn_error_wrap_apr
188 (apr_err
, _("Can't make pipe write handle non-inherited for hook '%s'"),
192 /* Redirect stdout to the null device */
193 apr_err
= apr_file_open(&null_handle
, SVN_NULL_DEVICE_NAME
, APR_WRITE
,
194 APR_OS_DEFAULT
, pool
);
196 return svn_error_wrap_apr
197 (apr_err
, _("Can't create null stdout for hook '%s'"), cmd
);
199 err
= svn_io_start_cmd(&cmd_proc
, ".", cmd
, args
, FALSE
,
200 stdin_handle
, null_handle
, write_errhandle
, pool
);
202 /* This seems to be done automatically if we pass the third parameter of
203 apr_procattr_child_in/out_set(), but svn_io_run_cmd()'s interface does
204 not support those parameters. We need to close the write end of the
205 pipe so we don't hang on the read end later, if we need to read it. */
206 apr_err
= apr_file_close(write_errhandle
);
208 return svn_error_wrap_apr
209 (apr_err
, _("Error closing write end of stderr pipe"));
213 err
= svn_error_createf
214 (SVN_ERR_REPOS_HOOK_FAILURE
, err
, _("Failed to start '%s' hook"), cmd
);
218 err
= check_hook_result(name
, cmd
, &cmd_proc
, read_errhandle
, pool
);
221 /* Hooks are fallible, and so hook failure is "expected" to occur at
222 times. When such a failure happens we still want to close the pipe
224 apr_err
= apr_file_close(read_errhandle
);
226 return svn_error_wrap_apr
227 (apr_err
, _("Error closing read end of stderr pipe"));
229 apr_err
= apr_file_close(null_handle
);
231 return svn_error_wrap_apr(apr_err
, _("Error closing null file"));
235 #else /* Run hooks with spawn() on OS400. */
236 #define AS400_BUFFER_SIZE 256
238 const char **native_args
;
239 int fd_map
[3], stderr_pipe
[2], exitcode
;
240 svn_stringbuf_t
*script_output
;
241 pid_t child_pid
, wait_rv
;
242 apr_size_t args_arr_size
= 0, i
;
243 struct inheritance xmp_inherit
= {0};
245 /* Despite the UTF support in V5R4 a few functions still require
247 char *xmp_envp
[2] = {"QIBM_USE_DESCRIPTOR_STDIO=Y", NULL
};
248 const char *dev_null_ebcdic
= SVN_NULL_DEVICE_NAME
;
249 #pragma convert(1208)
251 /* Find number of elements in args array. */
252 while (args
[args_arr_size
] != NULL
)
255 /* Allocate memory for the native_args string array plus one for
256 * the ending null element. */
257 native_args
= apr_palloc(pool
, sizeof(char *) * args_arr_size
+ 1);
259 /* Convert UTF-8 args to EBCDIC for use by spawn(). */
260 for (i
= 0; args
[i
] != NULL
; i
++)
262 SVN_ERR(svn_utf_cstring_from_utf8_ex2((const char**)(&(native_args
[i
])),
263 args
[i
], (const char *)0,
267 /* Make the last element in the array a NULL pointer as required
269 native_args
[args_arr_size
] = NULL
;
274 /* Get OS400 file descriptor of APR stdin file and map it. */
275 if (apr_os_file_get(&fd_map
[0], stdin_handle
))
277 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
278 "Error converting APR file to OS400 "
279 "type for hook script '%s'", cmd
);
284 fd_map
[0] = open(dev_null_ebcdic
, O_RDONLY
);
287 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
288 "Error opening /dev/null for hook "
294 fd_map
[1] = open(dev_null_ebcdic
, O_WRONLY
);
296 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
297 "Error opening /dev/null for hook script '%s'",
301 /* Get pipe for hook's stderr. */
302 if (pipe(stderr_pipe
) != 0)
304 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
305 "Can't create stderr pipe for "
308 fd_map
[2] = stderr_pipe
[1];
310 /* Spawn the hook command. */
311 child_pid
= spawn(native_args
[0], 3, fd_map
, &xmp_inherit
, native_args
,
315 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
316 "Error spawning process for hook script '%s'",
320 /* Close the stdout file descriptor. */
321 if (close(fd_map
[1]) == -1)
322 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
323 "Error closing write end of stdout pipe to "
324 "hook script '%s'", cmd
);
326 /* Close the write end of the stderr pipe so any subsequent reads
328 if (close(fd_map
[2]) == -1)
329 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
330 "Error closing write end of stderr pipe to "
331 "hook script '%s'", cmd
);
333 script_output
= svn_stringbuf_create("", pool
);
339 svn_stringbuf_ensure(script_output
,
340 script_output
->len
+ AS400_BUFFER_SIZE
+ 1);
342 rc
= read(stderr_pipe
[0],
343 &(script_output
->data
[script_output
->len
]),
348 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
349 "Error reading stderr of hook "
353 script_output
->len
+= rc
;
355 /* If read() returned 0 then EOF was found and we are done reading
359 script_output
->data
[script_output
->len
] = '\0';
364 /* Close the read end of the stderr pipe. */
365 if (close(stderr_pipe
[0]) == -1)
366 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
367 "Error closing read end of stderr "
368 "pipe to hook script '%s'", cmd
);
370 /* Wait for the child process to complete. */
371 wait_rv
= waitpid(child_pid
, &exitcode
, 0);
374 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
375 "Error waiting for process completion of "
376 "hook script '%s'", cmd
);
380 if (WIFEXITED(exitcode
))
382 if (WEXITSTATUS(exitcode
))
385 const char *utf8_stderr
= NULL
;
386 svn_stringbuf_t
*failure_message
= svn_stringbuf_createf(
387 pool
, "'%s' hook failed (exited with a non-zero exitcode "
388 "of %d). ", name
, exitcode
);
390 if (!svn_stringbuf_isempty(script_output
))
392 /* OS400 scripts produce EBCDIC stderr, so convert it. */
393 err
= svn_utf_cstring_to_utf8_ex2(&utf8_stderr
,
395 (const char*)0, pool
);
398 utf8_stderr
= "[Error output could not be translated from "
399 "the native locale to UTF-8.]";
400 svn_error_clear(err
);
407 svn_stringbuf_appendcstr(failure_message
,
408 "The following error output was "
409 "produced by the hook:\n");
410 svn_stringbuf_appendcstr(failure_message
, utf8_stderr
);
414 svn_stringbuf_appendcstr(failure_message
,
415 "No error output was produced by "
418 return svn_error_create(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
419 failure_message
->data
);
425 else if (WIFSIGNALED(exitcode
))
427 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
428 "Process '%s' failed because of an "
429 "uncaught terminating signal", cmd
);
431 else if (WIFEXCEPTION(exitcode
))
433 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
434 "Process '%s' failed unexpectedly with "
435 "OS400 exception %d", cmd
,
436 WEXCEPTNUMBER(exitcode
));
438 else if (WIFSTOPPED(exitcode
))
440 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
441 "Process '%s' stopped unexpectedly by "
442 "signal %d", cmd
, WSTOPSIG(exitcode
));
446 return svn_error_createf(SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
447 "Process '%s' failed unexpectedly", cmd
);
453 /* Create a temporary file F that will automatically be deleted when it is
454 closed. Fill it with VALUE, and leave it open and rewound, ready to be
457 create_temp_file(apr_file_t
**f
, const svn_string_t
*value
, apr_pool_t
*pool
)
460 apr_off_t offset
= 0;
462 SVN_ERR(svn_io_temp_dir(&dir
, pool
));
463 SVN_ERR(svn_io_open_unique_file2(f
, NULL
,
464 svn_path_join(dir
, "hook-input", pool
),
465 "", svn_io_file_del_on_close
, pool
));
466 SVN_ERR(svn_io_file_write_full(*f
, value
->data
, value
->len
, NULL
, pool
));
467 SVN_ERR(svn_io_file_seek(*f
, APR_SET
, &offset
, pool
));
472 /* Check if the HOOK program exists and is a file or a symbolic link, using
473 POOL for temporary allocations.
475 If the hook exists but is a broken symbolic link, set *BROKEN_LINK
476 to TRUE, else if the hook program exists set *BROKEN_LINK to FALSE.
478 Return the hook program if found, else return NULL and don't touch
482 check_hook_cmd(const char *hook
, svn_boolean_t
*broken_link
, apr_pool_t
*pool
)
484 static const char* const check_extns
[] = {
486 /* For WIN32, we need to check with file name extension(s) added.
488 As Windows Scripting Host (.wsf) files can accomodate (at least)
489 JavaScript (.js) and VB Script (.vbs) code, extensions for the
490 corresponding file types need not be enumerated explicitly. */
491 ".exe", ".cmd", ".bat", ".wsf", /* ### Any other extensions? */
498 const char *const *extn
;
499 svn_error_t
*err
= NULL
;
500 svn_boolean_t is_special
;
501 for (extn
= check_extns
; *extn
; ++extn
)
503 const char *const hook_path
=
504 (**extn
? apr_pstrcat(pool
, hook
, *extn
, 0) : hook
);
506 svn_node_kind_t kind
;
507 if (!(err
= svn_io_check_resolved_path(hook_path
, &kind
, pool
))
508 && kind
== svn_node_file
)
510 *broken_link
= FALSE
;
513 svn_error_clear(err
);
514 if (!(err
= svn_io_check_special_path(hook_path
, &kind
, &is_special
,
516 && is_special
== TRUE
)
521 svn_error_clear(err
);
527 /* Return an error for the failure of HOOK due to a broken symlink. */
529 hook_symlink_error(const char *hook
)
531 return svn_error_createf
532 (SVN_ERR_REPOS_HOOK_FAILURE
, NULL
,
533 _("Failed to run '%s' hook; broken symlink"), hook
);
537 svn_repos__hooks_start_commit(svn_repos_t
*repos
,
539 apr_array_header_t
*capabilities
,
542 const char *hook
= svn_repos_start_commit_hook(repos
, pool
);
543 svn_boolean_t broken_link
;
545 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
547 return hook_symlink_error(hook
);
552 char *capabilities_string
= svn_cstring_join(capabilities
, ":", pool
);
554 /* Get rid of that annoying final colon. */
555 if (capabilities_string
[0])
556 capabilities_string
[strlen(capabilities_string
) - 1] = '\0';
559 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
560 args
[2] = user
? user
: "";
561 args
[3] = capabilities_string
;
564 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_START_COMMIT
, hook
, args
, NULL
,
573 svn_repos__hooks_pre_commit(svn_repos_t
*repos
,
574 const char *txn_name
,
577 const char *hook
= svn_repos_pre_commit_hook(repos
, pool
);
578 svn_boolean_t broken_link
;
580 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
582 return hook_symlink_error(hook
);
589 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
593 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_PRE_COMMIT
, hook
, args
, NULL
,
602 svn_repos__hooks_post_commit(svn_repos_t
*repos
,
606 const char *hook
= svn_repos_post_commit_hook(repos
, pool
);
607 svn_boolean_t broken_link
;
609 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
611 return hook_symlink_error(hook
);
618 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
619 args
[2] = apr_psprintf(pool
, "%ld", rev
);
622 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_POST_COMMIT
, hook
, args
, NULL
,
631 svn_repos__hooks_pre_revprop_change(svn_repos_t
*repos
,
635 const svn_string_t
*new_value
,
639 const char *hook
= svn_repos_pre_revprop_change_hook(repos
, pool
);
640 svn_boolean_t broken_link
;
642 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
644 return hook_symlink_error(hook
);
649 apr_file_t
*stdin_handle
= NULL
;
650 char action_string
[2];
652 /* Pass the new value as stdin to hook */
654 SVN_ERR(create_temp_file(&stdin_handle
, new_value
, pool
));
656 SVN_ERR(svn_io_file_open(&stdin_handle
, SVN_NULL_DEVICE_NAME
,
657 APR_READ
, APR_OS_DEFAULT
, pool
));
659 action_string
[0] = action
;
660 action_string
[1] = '\0';
663 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
664 args
[2] = apr_psprintf(pool
, "%ld", rev
);
665 args
[3] = author
? author
: "";
667 args
[5] = action_string
;
670 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_PRE_REVPROP_CHANGE
, hook
, args
,
671 stdin_handle
, pool
));
673 SVN_ERR(svn_io_file_close(stdin_handle
, pool
));
677 /* If the pre- hook doesn't exist at all, then default to
678 MASSIVE PARANOIA. Changing revision properties is a lossy
679 operation; so unless the repository admininstrator has
680 *deliberately* created the pre-hook, disallow all changes. */
683 (SVN_ERR_REPOS_DISABLED_FEATURE
, NULL
,
684 _("Repository has not been enabled to accept revision propchanges;\n"
685 "ask the administrator to create a pre-revprop-change hook"));
693 svn_repos__hooks_post_revprop_change(svn_repos_t
*repos
,
697 svn_string_t
*old_value
,
701 const char *hook
= svn_repos_post_revprop_change_hook(repos
, pool
);
702 svn_boolean_t broken_link
;
704 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
706 return hook_symlink_error(hook
);
711 apr_file_t
*stdin_handle
= NULL
;
712 char action_string
[2];
714 /* Pass the old value as stdin to hook */
716 SVN_ERR(create_temp_file(&stdin_handle
, old_value
, pool
));
718 SVN_ERR(svn_io_file_open(&stdin_handle
, SVN_NULL_DEVICE_NAME
,
719 APR_READ
, APR_OS_DEFAULT
, pool
));
721 action_string
[0] = action
;
722 action_string
[1] = '\0';
725 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
726 args
[2] = apr_psprintf(pool
, "%ld", rev
);
727 args
[3] = author
? author
: "";
729 args
[5] = action_string
;
732 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_POST_REVPROP_CHANGE
, hook
, args
,
733 stdin_handle
, pool
));
735 SVN_ERR(svn_io_file_close(stdin_handle
, pool
));
744 svn_repos__hooks_pre_lock(svn_repos_t
*repos
,
746 const char *username
,
749 const char *hook
= svn_repos_pre_lock_hook(repos
, pool
);
750 svn_boolean_t broken_link
;
752 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
754 return hook_symlink_error(hook
);
761 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
766 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_PRE_LOCK
, hook
, args
, NULL
, pool
));
774 svn_repos__hooks_post_lock(svn_repos_t
*repos
,
775 apr_array_header_t
*paths
,
776 const char *username
,
779 const char *hook
= svn_repos_post_lock_hook(repos
, pool
);
780 svn_boolean_t broken_link
;
782 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
784 return hook_symlink_error(hook
);
789 apr_file_t
*stdin_handle
= NULL
;
790 svn_string_t
*paths_str
= svn_string_create(svn_cstring_join
794 SVN_ERR(create_temp_file(&stdin_handle
, paths_str
, pool
));
797 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
802 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_POST_LOCK
, hook
, args
, stdin_handle
,
805 SVN_ERR(svn_io_file_close(stdin_handle
, pool
));
813 svn_repos__hooks_pre_unlock(svn_repos_t
*repos
,
815 const char *username
,
818 const char *hook
= svn_repos_pre_unlock_hook(repos
, pool
);
819 svn_boolean_t broken_link
;
821 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
823 return hook_symlink_error(hook
);
830 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
832 args
[3] = username
? username
: "";
835 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_PRE_UNLOCK
, hook
, args
, NULL
,
844 svn_repos__hooks_post_unlock(svn_repos_t
*repos
,
845 apr_array_header_t
*paths
,
846 const char *username
,
849 const char *hook
= svn_repos_post_unlock_hook(repos
, pool
);
850 svn_boolean_t broken_link
;
852 if ((hook
= check_hook_cmd(hook
, &broken_link
, pool
)) && broken_link
)
854 return hook_symlink_error(hook
);
859 apr_file_t
*stdin_handle
= NULL
;
860 svn_string_t
*paths_str
= svn_string_create(svn_cstring_join
864 SVN_ERR(create_temp_file(&stdin_handle
, paths_str
, pool
));
867 args
[1] = svn_path_local_style(svn_repos_path(repos
, pool
), pool
);
868 args
[2] = username
? username
: "";
872 SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_POST_UNLOCK
, hook
, args
,
873 stdin_handle
, pool
));
875 SVN_ERR(svn_io_file_close(stdin_handle
, pool
));
884 * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq
885 * vim:isk=a-z,A-Z,48-57,_,.,-,>
886 * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0