Index: libsvn_repos/hooks.c =================================================================== --- libsvn_repos/hooks.c (revision 19001) +++ libsvn_repos/hooks.c (working copy) @@ -22,6 +22,11 @@ #include #include +#ifdef AS400 +#include +#include +#endif + #include "svn_error.h" #include "svn_path.h" #include "svn_repos.h" @@ -52,6 +57,7 @@ svn_boolean_t read_errstream, apr_file_t *stdin_handle, apr_pool_t *pool) +#ifndef AS400 { apr_file_t *read_errhandle, *write_errhandle, *null_handle; apr_status_t apr_err; @@ -164,8 +170,257 @@ return err; } +#else /* Run hooks with spawn() on OS400. */ +{ + const char *script_stderr_utf8 = ""; + const char **native_args; + char buffer[20]; + int rc, fd_map[3], stderr_pipe[2], stdin_pipe[2], exitcode; + svn_stringbuf_t *script_output = svn_stringbuf_create("", pool); + pid_t child_pid, wait_rv; + apr_size_t args_arr_size = 0, i; + struct inheritance xmp_inherit = {0}; +#pragma convert(0) + /* Despite the UTF support in V5R4 a few functions still require + * EBCDIC args. */ + char *xmp_envp[2] = {"QIBM_USE_DESCRIPTOR_STDIO=Y", NULL}; + const char *dev_null_ebcdic = SVN_NULL_DEVICE_NAME; +#pragma convert(1208) + /* Find number of elements in args array. */ + while (args[args_arr_size] != NULL) + args_arr_size++; + /* Allocate memory for the native_args string array plus one for + * the ending null element. */ + native_args = apr_palloc(pool, sizeof(char *) * args_arr_size + 1); + + /* Convert UTF-8 args to EBCDIC for use by spawn(). */ + for (i = 0; args[i] != NULL; i++) + { + SVN_ERR(svn_utf_cstring_from_utf8_ex((const char**)(&(native_args[i])), + args[i], (const char *)0, + "svn-repos-utoe-xlate-handle", + pool)); + } + + /* Make the last element in the array a NULL pointer as required + * by spawn. */ + native_args[args_arr_size] = NULL; + + /* Map stdin. */ + if (stdin_handle) + { + /* This is a bit cumbersome, but spawn can't work with apr_file_t, so + * if there is stdin for the script we open another pipe to the + * script's stdin which we can later write what we read from + * stdin_handle. */ + if (pipe(stdin_pipe) != 0) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Can't create stdin pipe for hook '%s'", + cmd); + } + fd_map[0] = stdin_pipe[0]; + } + else + { + fd_map[0] = open(dev_null_ebcdic, O_RDONLY); + if (fd_map[0] == -1) + + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error opening /dev/null for hook " + "script '%s'", cmd); + } + + + /* Map stdout. */ + fd_map[1] = open(dev_null_ebcdic, O_WRONLY); + if (fd_map[1] == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error opening /dev/null for hook script '%s'", + cmd); + + /* Map stderr. */ + if (read_errstream) + { + /* Get pipe for hook's stderr. */ + if (pipe(stderr_pipe) != 0) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Can't create stderr pipe for " + "hook '%s'", cmd); + } + fd_map[2] = stderr_pipe[1]; + } + else + { + /* Just dump stderr to /dev/null if we don't want it. */ + fd_map[2] = open(dev_null_ebcdic, O_WRONLY); + if (fd_map[2] == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error opening /dev/null for hook " + "script '%s'", cmd); + } + + /* Spawn the hook command. */ + child_pid = spawn(native_args[0], 3, fd_map, &xmp_inherit, native_args, + xmp_envp); + if (child_pid == -1) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error spawning process for hook script '%s'", + cmd); + } + + /* If there is "APR stdin", read it and then write it to the hook's + * stdin pipe. */ + if (stdin_handle) + { + while (1) + { + apr_size_t bytes_read = sizeof(buffer); + int wc; + svn_error_t *err = svn_io_file_read(stdin_handle, buffer, + &bytes_read, pool); + if (err && APR_STATUS_IS_EOF(err->apr_err)) + break; + + if (err) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error piping stdin to hook " + "script '%s'", cmd); + + wc = write(stdin_pipe[1], buffer, bytes_read); + if (wc == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error piping stdin to hook " + "script '%s'", cmd); + } + + /* Close the write end of the stdin pipe. */ + if (close(stdin_pipe[1]) == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error closing write end of stdin pipe " + "to hook script '%s'", cmd); + } + + /* Close the stdout file descriptor. */ + if (close(fd_map[1]) == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error closing write end of stdout pipe to " + "hook script '%s'", cmd); + + /* Close the write end of the stderr pipe so any subsequent reads + * don't hang. */ + if (close(fd_map[2]) == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error closing write end of stderr pipe to " + "hook script '%s'", cmd); + + /* Read the hook's stderr if we care about that. */ + if (read_errstream) + { + while (1) + { + rc = read(stderr_pipe[0], buffer, sizeof(buffer)); + if (rc == -1) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error reading stderr of hook " + "script '%s'", cmd); + } + + svn_stringbuf_appendbytes(script_output, buffer, rc); + + /* If read() returned 0 then EOF was found. */ + if (rc == 0) + { + /* Close the read end of the stderr pipe. */ + if (close(stderr_pipe[0]) == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error closing read end of stderr " + "pipe to hook script '%s'", cmd); + break; + } + } + } + + /* Wait for the child process to complete. */ + wait_rv = waitpid(child_pid, &exitcode, 0); + if (wait_rv == -1) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error waiting for process completion of " + "hook script '%s'", cmd); + } + + /* Close read end of stdin pipe if it exists. */ + if (close(fd_map[0]) == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error closing read end of stdin pipe to hook " + "script '%s'", cmd); + + if (!svn_stringbuf_isempty(script_output)) + { + /* OS400 scripts produce EBCDIC stderr, so convert it. */ + SVN_ERR(svn_utf_cstring_to_utf8_ex(&script_stderr_utf8, + script_output->data, + (const char*)0, + "svn-repos-etou-xlate-handle", + pool)); + } + + if (WIFEXITED(exitcode)) + { + if (WEXITSTATUS(exitcode)) + { + if (read_errstream) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "'%s' hook failed with error " + "output:\n%s", name, + script_stderr_utf8); + } + else + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "'%s' hook failed; no error output " + "available", name); + } + } + else + /* Success! */ + return SVN_NO_ERROR; + } + else if (WIFSIGNALED(exitcode)) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Process '%s' failed because of an " + "uncaught terminating signal", cmd); + } + else if (WIFEXCEPTION(exitcode)) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Process '%s' failed unexpectedly with " + "OS400 exception %d", cmd, + WEXCEPTNUMBER(exitcode)); + } + else if (WIFSTOPPED(exitcode)) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Process '%s' stopped unexpectedly by " + "signal %d", cmd, WSTOPSIG(exitcode)); + } + else + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Process '%s' failed unexpectedly", cmd); + } +} +#endif /* AS400 */ + + /* Create a temporary file F that will automatically be deleted when it is closed. Fill it with VALUE, and leave it open and rewound, ready to be read from. */