Index: subversion/include/svn_ra_svn.h =================================================================== --- subversion/include/svn_ra_svn.h (revision 29387) +++ subversion/include/svn_ra_svn.h (working copy) @@ -172,6 +172,10 @@ svn_error_t *svn_ra_svn_set_capabilities svn_boolean_t svn_ra_svn_has_capability(svn_ra_svn_conn_t *conn, const char *capability); +/** Returns the remote address of the connection as a string, if known, + * or NULL if inapplicable. */ +const char *svn_ra_svn_conn_remote_host(svn_ra_svn_conn_t *conn); + /** Write a number over the net. * * Writes will be buffered until the next read or flush. Index: subversion/libsvn_ra_svn/marshal.c =================================================================== --- subversion/libsvn_ra_svn/marshal.c (revision 29387) +++ subversion/libsvn_ra_svn/marshal.c (working copy) @@ -62,9 +62,18 @@ svn_ra_svn_conn_t *svn_ra_svn_create_con conn->pool = pool; if (sock != NULL) - conn->stream = svn_ra_svn__stream_from_sock(sock, pool); + { + apr_sockaddr_t *sa; + conn->stream = svn_ra_svn__stream_from_sock(sock, pool); + /* ### TODO: error checking */ + apr_socket_addr_get(&sa, APR_REMOTE, sock); + apr_sockaddr_ip_get(&conn->remote_ip, sa); + } else - conn->stream = svn_ra_svn__stream_from_files(in_file, out_file, pool); + { + conn->stream = svn_ra_svn__stream_from_files(in_file, out_file, pool); + conn->remote_ip = NULL; + } return conn; } @@ -95,6 +104,11 @@ svn_boolean_t svn_ra_svn_has_capability( APR_HASH_KEY_STRING) != NULL); } +const char *svn_ra_svn_conn_remote_host(svn_ra_svn_conn_t *conn) +{ + return conn->remote_ip; +} + void svn_ra_svn__set_block_handler(svn_ra_svn_conn_t *conn, ra_svn_block_handler_t handler, Index: subversion/libsvn_ra_svn/ra_svn.h =================================================================== --- subversion/libsvn_ra_svn/ra_svn.h (revision 29387) +++ subversion/libsvn_ra_svn/ra_svn.h (working copy) @@ -80,6 +80,7 @@ struct svn_ra_svn_conn_st { ra_svn_block_handler_t block_handler; void *block_baton; apr_hash_t *capabilities; + char *remote_ip; apr_pool_t *pool; }; Index: subversion/svnserve/main.c =================================================================== --- subversion/svnserve/main.c (revision 29387) +++ subversion/svnserve/main.c (working copy) @@ -136,6 +136,7 @@ void winservice_notify_stop(void) #define SVNSERVE_OPT_PID_FILE 261 #define SVNSERVE_OPT_SERVICE 262 #define SVNSERVE_OPT_CONFIG_FILE 263 +#define SVNSERVE_OPT_LOG_FILE 264 static const apr_getopt_option_t svnserve__options[] = { @@ -164,6 +165,8 @@ static const apr_getopt_option_t svnserv N_("read configuration from file ARG")}, {"pid-file", SVNSERVE_OPT_PID_FILE, 1, N_("write server process ID to file ARG")}, + {"log-file", SVNSERVE_OPT_LOG_FILE, 1, + N_("svnserve log file")}, #ifdef WIN32 {"service", SVNSERVE_OPT_SERVICE, 0, N_("run as a windows service (SCM only)")}, @@ -329,6 +332,7 @@ int main(int argc, const char *argv[]) int mode_opt_count = 0; const char *config_filename = NULL; const char *pid_filename = NULL; + const char *log_filename = NULL; svn_node_kind_t kind; /* Initialize the app. */ @@ -363,6 +367,7 @@ int main(int argc, const char *argv[]) params.cfg = NULL; params.pwdb = NULL; params.authzdb = NULL; + params.log_file = NULL; while (1) { @@ -482,6 +487,13 @@ int main(int argc, const char *argv[]) pool)); break; + case SVNSERVE_OPT_LOG_FILE: + SVN_INT_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool)); + log_filename = svn_path_internal_style(log_filename, pool); + SVN_INT_ERR(svn_path_get_absolute(&log_filename, log_filename, + pool)); + break; + } } if (os->ind != argc) @@ -503,6 +515,11 @@ int main(int argc, const char *argv[]) svn_path_dirname(config_filename, pool), pool)); + if (log_filename) + SVN_INT_ERR(svn_io_file_open(¶ms.log_file, log_filename, + APR_WRITE | APR_CREATE | APR_APPEND, + APR_OS_DEFAULT, pool)); + if (params.tunnel_user && run_mode != run_mode_tunnel) { svn_error_clear Index: subversion/svnserve/serve.c =================================================================== --- subversion/svnserve/serve.c (revision 29387) +++ subversion/svnserve/serve.c (working copy) @@ -46,6 +46,10 @@ #include "private/svn_mergeinfo_private.h" +#ifdef HAVE_UNISTD_H +#include /* For getpid() */ +#endif + #include "server.h" typedef struct { @@ -148,6 +152,31 @@ static svn_error_t *get_fs_path(const ch return SVN_NO_ERROR; } +static svn_error_t *svnserve_log(server_baton_t *b, + svn_ra_svn_conn_t *conn, + const char *command, + apr_pool_t *pool) +{ + const char *buf, *remote_host, *timestr; + + if (b->log_file == NULL) + return SVN_NO_ERROR; + + remote_host = svn_ra_svn_conn_remote_host(conn); + timestr = svn_time_to_cstring(apr_time_now(), pool); + + buf = apr_psprintf(pool, "%" APR_PID_T_FMT + " %s %s %s %s\n", + getpid(), + (remote_host ? remote_host : "-"), + (b->user ? b->user : "-"), timestr, + command); + + return svn_io_file_write_full(b->log_file, buf, strlen(buf), NULL, pool); +} + +#define SLOG(command) SVN_ERR(svnserve_log(baton, conn, (command), pool)) + /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */ /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to @@ -760,6 +789,8 @@ static svn_error_t *reparent(svn_ra_svn_ const char *url; const char *fs_path; + SLOG("reparent"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &url)); url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool); SVN_ERR(trivial_auth_request(conn, pool, b)); @@ -776,6 +807,8 @@ static svn_error_t *get_latest_rev(svn_r server_baton_t *b = baton; svn_revnum_t rev; + SLOG("get-latest-rev"); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev)); @@ -790,6 +823,8 @@ static svn_error_t *get_dated_rev(svn_ra apr_time_t tm; const char *timestr; + SLOG("get-dated-rev"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", ×tr)); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool)); @@ -806,6 +841,8 @@ static svn_error_t *change_rev_prop(svn_ const char *name; svn_string_t *value; + SLOG("change-rev-prop"); + /* Because the revprop value was at one time mandatory, the usual optional element pattern "(?s)" isn't used. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value)); @@ -825,6 +862,8 @@ static svn_error_t *rev_proplist(svn_ra_ svn_revnum_t rev; apr_hash_t *props; + SLOG("rev-proplist"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev)); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev, @@ -844,6 +883,8 @@ static svn_error_t *rev_prop(svn_ra_svn_ const char *name; svn_string_t *value; + SLOG("rev-prop"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name)); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name, @@ -987,6 +1028,8 @@ static svn_error_t *commit(svn_ra_svn_co commit_callback_baton_t ccb; svn_revnum_t new_rev; + SLOG("commit"); + if (params->nelts == 1) { /* Clients before 1.2 don't send lock-tokens, keep-locks, @@ -1087,6 +1130,8 @@ static svn_error_t *get_file(svn_ra_svn_ unsigned char digest[APR_MD5_DIGESTSIZE]; svn_error_t *err, *write_err; + SLOG("get-file"); + /* Parse arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev, &want_props, &want_contents)); @@ -1171,6 +1216,8 @@ static svn_error_t *get_dir(svn_ra_svn_c svn_ra_svn_item_t *elt; int i; + SLOG("get-dir"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb?l", &path, &rev, &want_props, &want_contents, &dirent_fields_list)); @@ -1329,6 +1376,8 @@ static svn_error_t *update(svn_ra_svn_co handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; + SLOG("update"); + /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb?wB", &rev, &target, &recurse, &depth_word, &send_copyfrom_param)); @@ -1365,6 +1414,8 @@ static svn_error_t *switch_cmd(svn_ra_sv handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; + SLOG("switch"); + /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbc?w", &rev, &target, &recurse, &switch_url, &depth_word)); @@ -1400,6 +1451,8 @@ static svn_error_t *status(svn_ra_svn_co handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; + SLOG("status"); + /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cb?(?r)?w", &target, &recurse, &rev, &depth_word)); @@ -1430,6 +1483,8 @@ static svn_error_t *diff(svn_ra_svn_conn handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; + SLOG("diff"); + /* Parse the arguments. */ if (params->nelts == 5) { @@ -1486,6 +1541,8 @@ static svn_error_t *get_mergeinfo(svn_ra svn_boolean_t include_descendants; apr_pool_t *iterpool; + SLOG("get-mergeinfo"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)wb", &paths, &rev, &inherit_word, &include_descendants)); inherit = svn_inheritance_from_word(inherit_word); @@ -1621,6 +1678,8 @@ static svn_error_t *log_cmd(svn_ra_svn_c apr_uint64_t limit, include_merged_revs_param; log_baton_t lb; + SLOG("log"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths, &start_rev, &end_rev, &changed_paths, &strict_node, &limit, @@ -1710,6 +1769,8 @@ static svn_error_t *check_path(svn_ra_sv svn_fs_root_t *root; svn_node_kind_t kind; + SLOG("check-path"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev)); full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, pool), pool); @@ -1736,6 +1797,8 @@ static svn_error_t *stat(svn_ra_svn_conn svn_fs_root_t *root; svn_dirent_t *dirent; + SLOG("stat"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev)); full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, pool), pool); @@ -1787,6 +1850,8 @@ static svn_error_t *get_locations(svn_ra const void *iter_key; void *iter_value; + SLOG("get-locations"); + /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crl", &relative_path, &peg_revision, @@ -1870,6 +1935,8 @@ static svn_error_t *get_location_segment const char *relative_path; const char *abs_path; + SLOG("get-location-segments"); + /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)(?r)", &relative_path, &peg_revision, @@ -1993,6 +2060,8 @@ static svn_error_t *get_file_revs(svn_ra apr_uint64_t include_merged_revs_param; svn_boolean_t include_merged_revisions; + SLOG("get-file-revs"); + /* Parse arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)?B", &path, &start_rev, &end_rev, @@ -2036,6 +2105,8 @@ static svn_error_t *lock(svn_ra_svn_conn svn_revnum_t current_rev; svn_lock_t *l; + SLOG("lock"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment, &steal_lock, ¤t_rev)); full_path = svn_path_join(b->fs_path->data, @@ -2070,6 +2141,8 @@ static svn_error_t *lock_many(svn_ra_svn svn_lock_t *l; svn_error_t *err = SVN_NO_ERROR, *write_err; + SLOG("lock-many"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock, &path_revs)); @@ -2153,6 +2226,8 @@ static svn_error_t *unlock(svn_ra_svn_co const char *path, *token, *full_path; svn_boolean_t break_lock; + SLOG("unlock"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b", &path, &token, &break_lock)); @@ -2184,6 +2259,8 @@ static svn_error_t *unlock_many(svn_ra_s const char *token; svn_error_t *err = SVN_NO_ERROR, *write_err; + SLOG("unlock-many"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "bl", &break_lock, &unlock_tokens)); @@ -2256,6 +2333,8 @@ static svn_error_t *get_lock(svn_ra_svn_ const char *full_path; svn_lock_t *l; + SLOG("get-lock"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path)); full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, @@ -2286,6 +2365,8 @@ static svn_error_t *get_locks(svn_ra_svn void *val; svn_lock_t *l; + SLOG("get-locks"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path)); full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, @@ -2344,6 +2425,8 @@ static svn_error_t *replay(svn_ra_svn_co svn_boolean_t send_deltas; server_baton_t *b = baton; + SLOG("replay"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrb", &rev, &low_water_mark, &send_deltas)); @@ -2365,6 +2448,8 @@ static svn_error_t *replay_range(svn_ra_ server_baton_t *b = baton; apr_pool_t *iterpool; + SLOG("replay-range"); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrrb", &start_rev, &end_rev, &low_water_mark, &send_deltas)); @@ -2603,6 +2688,7 @@ svn_error_t *serve(svn_ra_svn_conn_t *co b.pwdb = params->pwdb; /* Likewise. */ b.authzdb = params->authzdb; b.realm = NULL; + b.log_file = params->log_file; b.pool = pool; /* Send greeting. We don't support version 1 any more, so we can Index: subversion/svnserve/server.h =================================================================== --- subversion/svnserve/server.h (revision 29387) +++ subversion/svnserve/server.h (working copy) @@ -46,6 +46,7 @@ typedef struct server_baton_t { #ifdef SVN_HAVE_SASL svn_boolean_t use_sasl; /* Use Cyrus SASL for authentication */ #endif + apr_file_t *log_file; /* Log filehandle. */ apr_pool_t *pool; } server_baton_t; @@ -90,6 +91,9 @@ typedef struct serve_params_t { command line, or it was specified and it did not refer to a authorization database. */ svn_authz_t *authzdb; + + /* A filehandle open for writing logs to; possibly NULL. */ + apr_file_t *log_file; } serve_params_t; /* Serve the connection CONN according to the parameters PARAMS. */