Index: subversion/libsvn_repos/hooks.c =================================================================== --- subversion/libsvn_repos/hooks.c (revision 19091) +++ subversion/libsvn_repos/hooks.c (working copy) @@ -25,6 +25,9 @@ #ifdef AS400 #include #include +#include +#include +#include #endif #include "svn_error.h" @@ -174,8 +177,9 @@ { const char *script_stderr_utf8 = ""; const char **native_args; - char buffer[20]; - int rc, fd_map[3], stderr_pipe[2], stdin_pipe[2], exitcode; + char stdin_buffer[128]; + char stderr_buffer[128]; + int 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; @@ -186,6 +190,10 @@ char *xmp_envp[2] = {"QIBM_USE_DESCRIPTOR_STDIO=Y", NULL}; const char *dev_null_ebcdic = SVN_NULL_DEVICE_NAME; #pragma convert(1208) + svn_boolean_t more_stderr = read_errstream; + svn_boolean_t last_stdin_write_succeeded = TRUE; + apr_size_t bytes_last_read; + struct pollfd pfds[2]; /* Find number of elements in args array. */ while (args[args_arr_size] != NULL) @@ -222,6 +230,14 @@ cmd); } fd_map[0] = stdin_pipe[0]; + + /* Prevent blocking on write end of stdin pipe. */ + if (fcntl(stdin_pipe[1], F_SETFL, O_NONBLOCK) == -1) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Can't set flag for stdin pipe for " + "hook '%s'", cmd); + } } else { @@ -273,38 +289,6 @@ 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, @@ -317,35 +301,156 @@ return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, "Error closing write end of stderr pipe to " "hook script '%s'", cmd); + + /* Close read end of stdin pipe so we don't hang on the write. */ + 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); - /* Read the hook's stderr if we care about that. */ - if (read_errstream) + /* Specify the events we want to poll: + * + * 1) Can read from the script's stderr pipe + * 2) Can write to the script's stdin pipe + */ + pfds[0].fd = stderr_pipe[0]; + pfds[0].events = POLLIN; + pfds[1].fd = stdin_pipe[1]; + pfds[1].events = POLLOUT; + + while (1) { - while (1) + apr_size_t bytes_read = sizeof(stdin_buffer); + int wc; + + svn_error_t *err; + + /* Blocking poll until we are ready to write some stdin to the script + * or read it's stderr. */ + if (poll(pfds, 2, -1) == -1) { - rc = read(stderr_pipe[0], buffer, sizeof(buffer)); - if (rc == -1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error polling stderr/stdin of hook " + "script '%s'", cmd); + } + + /* Immediate poll to check if we can read stderr from the script. */ + if (poll((struct pollfd *)(&pfds[0]), 1, 0) == -1) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error polling stderr of hook " + "script '%s'", cmd); + } + + if (read_errstream && more_stderr && pfds[0].revents & POLLIN) + { + do { + int rc = read(stderr_pipe[0], stderr_buffer, + sizeof(stderr_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, stderr_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); + more_stderr = FALSE; + } + + /* Keep reading stderr if there is more. */ + if (poll((struct pollfd *)(&pfds[0]), 1, 0) == -1) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error polling stderr of hook " + "script '%s'", cmd); + } + } while(pfds[0].revents & POLLIN); + } + + /* Immediate poll to check if we can write stdin to the script. */ + if (poll((struct pollfd *)(&pfds[1]), 1, 0) == -1) + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + "Error polling stdin of hook " + "script '%s'", cmd); + } + + if (stdin_handle && pfds[1].revents & POLLOUT) + { + /* Don't lose any stdin: Use what we last read into the buffer + * if the previous write to stdin pipe failed. */ + if (last_stdin_write_succeeded) { - return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, - "Error reading stderr of hook " - "script '%s'", cmd); + err = svn_io_file_read(stdin_handle, stdin_buffer, + &bytes_read, pool); + bytes_last_read = bytes_read; } + if (err) + { + if (APR_STATUS_IS_EOF(err->apr_err)) + { + /* Make use of stdin_handle as flag to detect when we are + * done writing stdin to the script. */ + stdin_handle = NULL; - 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) + /* If there is no more stdin close the write end of the + * stdin pipe. */ + if (stdin_handle == NULL && 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); + } + else return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, - "Error closing read end of stderr " - "pipe to hook script '%s'", cmd); - break; + "Error piping stdin to hook " + "script '%s'", cmd); } + else + { + wc = write(stdin_pipe[1], stdin_buffer, bytes_last_read); + if (wc == -1) + { + if (errno == EWOULDBLOCK) + { + last_stdin_write_succeeded = FALSE; + } + else if (errno == EPIPE) + { + /* If the script exits without ever trying to read + * stdin we'll get a broken pipe error. */ + stdin_handle = NULL; + } + else + { + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, + NULL, "Error piping stdin " + "to hook script '%s'", cmd); + } + } + else + { + last_stdin_write_succeeded = TRUE; + } + } } - } + /* Are we done writing to stdin pipe and reading from stderr pipe? */ + if (!more_stderr && stdin_handle == NULL) + break; + } /* while(1) */ + /* Wait for the child process to complete. */ wait_rv = waitpid(child_pid, &exitcode, 0); if (wait_rv == -1) @@ -355,12 +460,6 @@ "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. */