1251881Speter/* hooks.c : running repository hooks 2251881Speter * 3251881Speter * ==================================================================== 4251881Speter * Licensed to the Apache Software Foundation (ASF) under one 5251881Speter * or more contributor license agreements. See the NOTICE file 6251881Speter * distributed with this work for additional information 7251881Speter * regarding copyright ownership. The ASF licenses this file 8251881Speter * to you under the Apache License, Version 2.0 (the 9251881Speter * "License"); you may not use this file except in compliance 10251881Speter * with the License. You may obtain a copy of the License at 11251881Speter * 12251881Speter * http://www.apache.org/licenses/LICENSE-2.0 13251881Speter * 14251881Speter * Unless required by applicable law or agreed to in writing, 15251881Speter * software distributed under the License is distributed on an 16251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17251881Speter * KIND, either express or implied. See the License for the 18251881Speter * specific language governing permissions and limitations 19251881Speter * under the License. 20251881Speter * ==================================================================== 21251881Speter */ 22251881Speter 23251881Speter#include <stdio.h> 24251881Speter#include <string.h> 25251881Speter#include <ctype.h> 26251881Speter 27251881Speter#include <apr_pools.h> 28251881Speter#include <apr_file_io.h> 29251881Speter 30251881Speter#include "svn_config.h" 31251881Speter#include "svn_hash.h" 32251881Speter#include "svn_error.h" 33251881Speter#include "svn_dirent_uri.h" 34251881Speter#include "svn_path.h" 35251881Speter#include "svn_pools.h" 36251881Speter#include "svn_repos.h" 37251881Speter#include "svn_utf.h" 38251881Speter#include "repos.h" 39251881Speter#include "svn_private_config.h" 40251881Speter#include "private/svn_fs_private.h" 41251881Speter#include "private/svn_repos_private.h" 42251881Speter#include "private/svn_string_private.h" 43251881Speter 44251881Speter 45251881Speter 46251881Speter/*** Hook drivers. ***/ 47251881Speter 48251881Speter/* Helper function for run_hook_cmd(). Wait for a hook to finish 49251881Speter executing and return either SVN_NO_ERROR if the hook script completed 50251881Speter without error, or an error describing the reason for failure. 51251881Speter 52251881Speter NAME and CMD are the name and path of the hook program, CMD_PROC 53251881Speter is a pointer to the structure representing the running process, 54251881Speter and READ_ERRHANDLE is an open handle to the hook's stderr. 55251881Speter 56251881Speter Hooks are considered to have failed if we are unable to wait for the 57251881Speter process, if we are unable to read from the hook's stderr, if the 58251881Speter process has failed to exit cleanly (due to a coredump, for example), 59251881Speter or if the process returned a non-zero return code. 60251881Speter 61251881Speter Any error output returned by the hook's stderr will be included in an 62251881Speter error message, though the presence of output on stderr is not itself 63251881Speter a reason to fail a hook. */ 64251881Speterstatic svn_error_t * 65251881Spetercheck_hook_result(const char *name, const char *cmd, apr_proc_t *cmd_proc, 66251881Speter apr_file_t *read_errhandle, apr_pool_t *pool) 67251881Speter{ 68251881Speter svn_error_t *err, *err2; 69251881Speter svn_stringbuf_t *native_stderr, *failure_message; 70251881Speter const char *utf8_stderr; 71251881Speter int exitcode; 72251881Speter apr_exit_why_e exitwhy; 73251881Speter 74251881Speter err2 = svn_stringbuf_from_aprfile(&native_stderr, read_errhandle, pool); 75251881Speter 76251881Speter err = svn_io_wait_for_cmd(cmd_proc, cmd, &exitcode, &exitwhy, pool); 77251881Speter if (err) 78251881Speter { 79251881Speter svn_error_clear(err2); 80251881Speter return svn_error_trace(err); 81251881Speter } 82251881Speter 83251881Speter if (APR_PROC_CHECK_EXIT(exitwhy) && exitcode == 0) 84251881Speter { 85251881Speter /* The hook exited cleanly. However, if we got an error reading 86251881Speter the hook's stderr, fail the hook anyway, because this might be 87251881Speter symptomatic of a more important problem. */ 88251881Speter if (err2) 89251881Speter { 90251881Speter return svn_error_createf 91251881Speter (SVN_ERR_REPOS_HOOK_FAILURE, err2, 92251881Speter _("'%s' hook succeeded, but error output could not be read"), 93251881Speter name); 94251881Speter } 95251881Speter 96251881Speter return SVN_NO_ERROR; 97251881Speter } 98251881Speter 99251881Speter /* The hook script failed. */ 100251881Speter 101251881Speter /* If we got the stderr output okay, try to translate it into UTF-8. 102251881Speter Ensure there is something sensible in the UTF-8 string regardless. */ 103251881Speter if (!err2) 104251881Speter { 105251881Speter err2 = svn_utf_cstring_to_utf8(&utf8_stderr, native_stderr->data, pool); 106251881Speter if (err2) 107251881Speter utf8_stderr = _("[Error output could not be translated from the " 108251881Speter "native locale to UTF-8.]"); 109251881Speter } 110251881Speter else 111251881Speter { 112251881Speter utf8_stderr = _("[Error output could not be read.]"); 113251881Speter } 114251881Speter /*### It would be nice to include the text of any translation or read 115251881Speter error in the messages above before we clear it here. */ 116251881Speter svn_error_clear(err2); 117251881Speter 118251881Speter if (!APR_PROC_CHECK_EXIT(exitwhy)) 119251881Speter { 120251881Speter failure_message = svn_stringbuf_createf(pool, 121251881Speter _("'%s' hook failed (did not exit cleanly: " 122251881Speter "apr_exit_why_e was %d, exitcode was %d). "), 123251881Speter name, exitwhy, exitcode); 124251881Speter } 125251881Speter else 126251881Speter { 127251881Speter const char *action; 128251881Speter if (strcmp(name, "start-commit") == 0 129251881Speter || strcmp(name, "pre-commit") == 0) 130251881Speter action = _("Commit"); 131251881Speter else if (strcmp(name, "pre-revprop-change") == 0) 132251881Speter action = _("Revprop change"); 133251881Speter else if (strcmp(name, "pre-lock") == 0) 134251881Speter action = _("Lock"); 135251881Speter else if (strcmp(name, "pre-unlock") == 0) 136251881Speter action = _("Unlock"); 137251881Speter else 138251881Speter action = NULL; 139251881Speter if (action == NULL) 140251881Speter failure_message = svn_stringbuf_createf( 141251881Speter pool, _("%s hook failed (exit code %d)"), 142251881Speter name, exitcode); 143251881Speter else 144251881Speter failure_message = svn_stringbuf_createf( 145251881Speter pool, _("%s blocked by %s hook (exit code %d)"), 146251881Speter action, name, exitcode); 147251881Speter } 148251881Speter 149251881Speter if (utf8_stderr[0]) 150251881Speter { 151251881Speter svn_stringbuf_appendcstr(failure_message, 152251881Speter _(" with output:\n")); 153251881Speter svn_stringbuf_appendcstr(failure_message, utf8_stderr); 154251881Speter } 155251881Speter else 156251881Speter { 157251881Speter svn_stringbuf_appendcstr(failure_message, 158251881Speter _(" with no output.")); 159251881Speter } 160251881Speter 161251881Speter return svn_error_create(SVN_ERR_REPOS_HOOK_FAILURE, err, 162251881Speter failure_message->data); 163251881Speter} 164251881Speter 165251881Speter/* Copy the environment given as key/value pairs of ENV_HASH into 166251881Speter * an array of C strings allocated in RESULT_POOL. 167251881Speter * If the hook environment is empty, return NULL. 168251881Speter * Use SCRATCH_POOL for temporary allocations. */ 169251881Speterstatic const char ** 170251881Speterenv_from_env_hash(apr_hash_t *env_hash, 171251881Speter apr_pool_t *result_pool, 172251881Speter apr_pool_t *scratch_pool) 173251881Speter{ 174251881Speter apr_hash_index_t *hi; 175251881Speter const char **env; 176251881Speter const char **envp; 177251881Speter 178251881Speter if (!env_hash) 179251881Speter return NULL; 180251881Speter 181251881Speter env = apr_palloc(result_pool, 182251881Speter sizeof(const char *) * (apr_hash_count(env_hash) + 1)); 183251881Speter envp = env; 184251881Speter for (hi = apr_hash_first(scratch_pool, env_hash); hi; hi = apr_hash_next(hi)) 185251881Speter { 186251881Speter *envp = apr_psprintf(result_pool, "%s=%s", 187299742Sdim (const char *)apr_hash_this_key(hi), 188299742Sdim (const char *)apr_hash_this_val(hi)); 189251881Speter envp++; 190251881Speter } 191251881Speter *envp = NULL; 192251881Speter 193251881Speter return env; 194251881Speter} 195251881Speter 196251881Speter/* NAME, CMD and ARGS are the name, path to and arguments for the hook 197251881Speter program that is to be run. The hook's exit status will be checked, 198251881Speter and if an error occurred the hook's stderr output will be added to 199251881Speter the returned error. 200251881Speter 201251881Speter If STDIN_HANDLE is non-null, pass it as the hook's stdin, else pass 202251881Speter no stdin to the hook. 203251881Speter 204251881Speter If RESULT is non-null, set *RESULT to the stdout of the hook or to 205251881Speter a zero-length string if the hook generates no output on stdout. */ 206251881Speterstatic svn_error_t * 207251881Speterrun_hook_cmd(svn_string_t **result, 208251881Speter const char *name, 209251881Speter const char *cmd, 210251881Speter const char **args, 211251881Speter apr_hash_t *hooks_env, 212251881Speter apr_file_t *stdin_handle, 213251881Speter apr_pool_t *pool) 214251881Speter{ 215251881Speter apr_file_t *null_handle; 216251881Speter apr_status_t apr_err; 217251881Speter svn_error_t *err; 218251881Speter apr_proc_t cmd_proc = {0}; 219251881Speter apr_pool_t *cmd_pool; 220251881Speter apr_hash_t *hook_env = NULL; 221251881Speter 222251881Speter if (result) 223251881Speter { 224251881Speter null_handle = NULL; 225251881Speter } 226251881Speter else 227251881Speter { 228251881Speter /* Redirect stdout to the null device */ 229251881Speter apr_err = apr_file_open(&null_handle, SVN_NULL_DEVICE_NAME, APR_WRITE, 230251881Speter APR_OS_DEFAULT, pool); 231251881Speter if (apr_err) 232251881Speter return svn_error_wrap_apr 233251881Speter (apr_err, _("Can't create null stdout for hook '%s'"), cmd); 234251881Speter } 235251881Speter 236251881Speter /* Tie resources allocated for the command to a special pool which we can 237251881Speter * destroy in order to clean up the stderr pipe opened for the process. */ 238251881Speter cmd_pool = svn_pool_create(pool); 239251881Speter 240251881Speter /* Check if a custom environment is defined for this hook, or else 241251881Speter * whether a default environment is defined. */ 242251881Speter if (hooks_env) 243251881Speter { 244251881Speter hook_env = svn_hash_gets(hooks_env, name); 245251881Speter if (hook_env == NULL) 246251881Speter hook_env = svn_hash_gets(hooks_env, 247251881Speter SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION); 248251881Speter } 249251881Speter 250251881Speter err = svn_io_start_cmd3(&cmd_proc, ".", cmd, args, 251251881Speter env_from_env_hash(hook_env, pool, pool), 252251881Speter FALSE, FALSE, stdin_handle, result != NULL, 253251881Speter null_handle, TRUE, NULL, cmd_pool); 254251881Speter if (!err) 255251881Speter err = check_hook_result(name, cmd, &cmd_proc, cmd_proc.err, pool); 256251881Speter else 257251881Speter { 258251881Speter /* The command could not be started for some reason. */ 259251881Speter err = svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, err, 260251881Speter _("Failed to start '%s' hook"), cmd); 261251881Speter } 262251881Speter 263251881Speter /* Hooks are fallible, and so hook failure is "expected" to occur at 264251881Speter times. When such a failure happens we still want to close the pipe 265251881Speter and null file */ 266251881Speter if (!err && result) 267251881Speter { 268251881Speter svn_stringbuf_t *native_stdout; 269251881Speter err = svn_stringbuf_from_aprfile(&native_stdout, cmd_proc.out, pool); 270251881Speter if (!err) 271251881Speter *result = svn_stringbuf__morph_into_string(native_stdout); 272251881Speter } 273251881Speter 274251881Speter /* Close resources allocated by svn_io_start_cmd3(), such as the pipe. */ 275251881Speter svn_pool_destroy(cmd_pool); 276251881Speter 277251881Speter /* Close the null handle. */ 278251881Speter if (null_handle) 279251881Speter { 280251881Speter apr_err = apr_file_close(null_handle); 281251881Speter if (!err && apr_err) 282251881Speter return svn_error_wrap_apr(apr_err, _("Error closing null file")); 283251881Speter } 284251881Speter 285251881Speter return svn_error_trace(err); 286251881Speter} 287251881Speter 288251881Speter 289251881Speter/* Create a temporary file F that will automatically be deleted when the 290251881Speter pool is cleaned up. Fill it with VALUE, and leave it open and rewound, 291251881Speter ready to be read from. */ 292251881Speterstatic svn_error_t * 293251881Spetercreate_temp_file(apr_file_t **f, const svn_string_t *value, apr_pool_t *pool) 294251881Speter{ 295251881Speter apr_off_t offset = 0; 296251881Speter 297251881Speter SVN_ERR(svn_io_open_unique_file3(f, NULL, NULL, 298251881Speter svn_io_file_del_on_pool_cleanup, 299251881Speter pool, pool)); 300251881Speter SVN_ERR(svn_io_file_write_full(*f, value->data, value->len, NULL, pool)); 301251881Speter return svn_io_file_seek(*f, APR_SET, &offset, pool); 302251881Speter} 303251881Speter 304251881Speter 305251881Speter/* Check if the HOOK program exists and is a file or a symbolic link, using 306251881Speter POOL for temporary allocations. 307251881Speter 308251881Speter If the hook exists but is a broken symbolic link, set *BROKEN_LINK 309251881Speter to TRUE, else if the hook program exists set *BROKEN_LINK to FALSE. 310251881Speter 311251881Speter Return the hook program if found, else return NULL and don't touch 312251881Speter *BROKEN_LINK. 313251881Speter*/ 314251881Speterstatic const char* 315251881Spetercheck_hook_cmd(const char *hook, svn_boolean_t *broken_link, apr_pool_t *pool) 316251881Speter{ 317251881Speter static const char* const check_extns[] = { 318251881Speter#ifdef WIN32 319251881Speter /* For WIN32, we need to check with file name extension(s) added. 320251881Speter 321299742Sdim As Windows Scripting Host (.wsf) files can accommodate (at least) 322251881Speter JavaScript (.js) and VB Script (.vbs) code, extensions for the 323251881Speter corresponding file types need not be enumerated explicitly. */ 324251881Speter ".exe", ".cmd", ".bat", ".wsf", /* ### Any other extensions? */ 325251881Speter#else 326251881Speter "", 327251881Speter#endif 328251881Speter NULL 329251881Speter }; 330251881Speter 331251881Speter const char *const *extn; 332251881Speter svn_error_t *err = NULL; 333251881Speter svn_boolean_t is_special; 334251881Speter for (extn = check_extns; *extn; ++extn) 335251881Speter { 336251881Speter const char *const hook_path = 337299742Sdim (**extn ? apr_pstrcat(pool, hook, *extn, SVN_VA_NULL) : hook); 338251881Speter 339251881Speter svn_node_kind_t kind; 340251881Speter if (!(err = svn_io_check_resolved_path(hook_path, &kind, pool)) 341251881Speter && kind == svn_node_file) 342251881Speter { 343251881Speter *broken_link = FALSE; 344251881Speter return hook_path; 345251881Speter } 346251881Speter svn_error_clear(err); 347251881Speter if (!(err = svn_io_check_special_path(hook_path, &kind, &is_special, 348251881Speter pool)) 349251881Speter && is_special) 350251881Speter { 351251881Speter *broken_link = TRUE; 352251881Speter return hook_path; 353251881Speter } 354251881Speter svn_error_clear(err); 355251881Speter } 356251881Speter return NULL; 357251881Speter} 358251881Speter 359251881Speter/* Baton for parse_hooks_env_option. */ 360251881Speterstruct parse_hooks_env_option_baton { 361251881Speter /* The name of the section being parsed. If not the default section, 362251881Speter * the section name should match the name of a hook to which the 363251881Speter * options apply. */ 364251881Speter const char *section; 365251881Speter apr_hash_t *hooks_env; 366299742Sdim}; 367251881Speter 368251881Speter/* An implementation of svn_config_enumerator2_t. 369251881Speter * Set environment variable NAME to value VALUE in the environment for 370251881Speter * all hooks (in case the current section is the default section), 371251881Speter * or the hook with the name corresponding to the current section's name. */ 372251881Speterstatic svn_boolean_t 373251881Speterparse_hooks_env_option(const char *name, const char *value, 374251881Speter void *baton, apr_pool_t *pool) 375251881Speter{ 376251881Speter struct parse_hooks_env_option_baton *bo = baton; 377251881Speter apr_pool_t *result_pool = apr_hash_pool_get(bo->hooks_env); 378251881Speter apr_hash_t *hook_env; 379251881Speter 380251881Speter hook_env = svn_hash_gets(bo->hooks_env, bo->section); 381251881Speter if (hook_env == NULL) 382251881Speter { 383251881Speter hook_env = apr_hash_make(result_pool); 384251881Speter svn_hash_sets(bo->hooks_env, apr_pstrdup(result_pool, bo->section), 385251881Speter hook_env); 386251881Speter } 387251881Speter svn_hash_sets(hook_env, apr_pstrdup(result_pool, name), 388251881Speter apr_pstrdup(result_pool, value)); 389251881Speter 390251881Speter return TRUE; 391251881Speter} 392251881Speter 393251881Speterstruct parse_hooks_env_section_baton { 394251881Speter svn_config_t *cfg; 395251881Speter apr_hash_t *hooks_env; 396299742Sdim}; 397251881Speter 398251881Speter/* An implementation of svn_config_section_enumerator2_t. */ 399251881Speterstatic svn_boolean_t 400251881Speterparse_hooks_env_section(const char *name, void *baton, apr_pool_t *pool) 401251881Speter{ 402251881Speter struct parse_hooks_env_section_baton *b = baton; 403251881Speter struct parse_hooks_env_option_baton bo; 404251881Speter 405251881Speter bo.section = name; 406251881Speter bo.hooks_env = b->hooks_env; 407251881Speter 408251881Speter (void)svn_config_enumerate2(b->cfg, name, parse_hooks_env_option, &bo, pool); 409251881Speter 410251881Speter return TRUE; 411251881Speter} 412251881Speter 413251881Spetersvn_error_t * 414251881Spetersvn_repos__parse_hooks_env(apr_hash_t **hooks_env_p, 415251881Speter const char *local_abspath, 416251881Speter apr_pool_t *result_pool, 417251881Speter apr_pool_t *scratch_pool) 418251881Speter{ 419251881Speter struct parse_hooks_env_section_baton b; 420251881Speter if (local_abspath) 421251881Speter { 422299742Sdim svn_node_kind_t kind; 423299742Sdim SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); 424299742Sdim 425251881Speter b.hooks_env = apr_hash_make(result_pool); 426299742Sdim 427299742Sdim if (kind != svn_node_none) 428299742Sdim { 429299742Sdim svn_config_t *cfg; 430299742Sdim SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE, 431299742Sdim TRUE, TRUE, scratch_pool)); 432299742Sdim b.cfg = cfg; 433299742Sdim 434299742Sdim (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section, 435299742Sdim &b, scratch_pool); 436299742Sdim } 437299742Sdim 438251881Speter *hooks_env_p = b.hooks_env; 439251881Speter } 440251881Speter else 441251881Speter { 442251881Speter *hooks_env_p = NULL; 443251881Speter } 444251881Speter 445251881Speter return SVN_NO_ERROR; 446251881Speter} 447251881Speter 448251881Speter/* Return an error for the failure of HOOK due to a broken symlink. */ 449251881Speterstatic svn_error_t * 450251881Speterhook_symlink_error(const char *hook) 451251881Speter{ 452251881Speter return svn_error_createf 453251881Speter (SVN_ERR_REPOS_HOOK_FAILURE, NULL, 454251881Speter _("Failed to run '%s' hook; broken symlink"), hook); 455251881Speter} 456251881Speter 457251881Spetersvn_error_t * 458251881Spetersvn_repos__hooks_start_commit(svn_repos_t *repos, 459251881Speter apr_hash_t *hooks_env, 460251881Speter const char *user, 461251881Speter const apr_array_header_t *capabilities, 462251881Speter const char *txn_name, 463251881Speter apr_pool_t *pool) 464251881Speter{ 465251881Speter const char *hook = svn_repos_start_commit_hook(repos, pool); 466251881Speter svn_boolean_t broken_link; 467251881Speter 468251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 469251881Speter { 470251881Speter return hook_symlink_error(hook); 471251881Speter } 472251881Speter else if (hook) 473251881Speter { 474251881Speter const char *args[6]; 475251881Speter char *capabilities_string; 476251881Speter 477251881Speter if (capabilities) 478251881Speter { 479251881Speter capabilities_string = svn_cstring_join(capabilities, ":", pool); 480251881Speter 481251881Speter /* Get rid of that annoying final colon. */ 482251881Speter if (capabilities_string[0]) 483251881Speter capabilities_string[strlen(capabilities_string) - 1] = '\0'; 484251881Speter } 485251881Speter else 486251881Speter { 487251881Speter capabilities_string = apr_pstrdup(pool, ""); 488251881Speter } 489251881Speter 490251881Speter args[0] = hook; 491251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 492251881Speter args[2] = user ? user : ""; 493251881Speter args[3] = capabilities_string; 494251881Speter args[4] = txn_name; 495251881Speter args[5] = NULL; 496251881Speter 497251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args, 498251881Speter hooks_env, NULL, pool)); 499251881Speter } 500251881Speter 501251881Speter return SVN_NO_ERROR; 502251881Speter} 503251881Speter 504251881Speter/* Set *HANDLE to an open filehandle for a temporary file (i.e., 505251881Speter automatically deleted when closed), into which the LOCK_TOKENS have 506251881Speter been written out in the format described in the pre-commit hook 507251881Speter template. 508251881Speter 509251881Speter LOCK_TOKENS is as returned by svn_fs__access_get_lock_tokens(). 510251881Speter 511251881Speter Allocate *HANDLE in POOL, and use POOL for temporary allocations. */ 512251881Speterstatic svn_error_t * 513251881Speterlock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens, 514251881Speter apr_pool_t *pool) 515251881Speter{ 516251881Speter svn_stringbuf_t *lock_str = svn_stringbuf_create("LOCK-TOKENS:\n", pool); 517251881Speter apr_hash_index_t *hi; 518251881Speter 519251881Speter for (hi = apr_hash_first(pool, lock_tokens); hi; 520251881Speter hi = apr_hash_next(hi)) 521251881Speter { 522299742Sdim const char *token = apr_hash_this_key(hi); 523299742Sdim const char *path = apr_hash_this_val(hi); 524251881Speter 525299742Sdim if (path == (const char *) 1) 526299742Sdim { 527299742Sdim /* Special handling for svn_fs_access_t * created by using deprecated 528299742Sdim svn_fs_access_add_lock_token() function. */ 529299742Sdim path = ""; 530299742Sdim } 531299742Sdim else 532299742Sdim { 533299742Sdim path = svn_path_uri_autoescape(path, pool); 534299742Sdim } 535299742Sdim 536251881Speter svn_stringbuf_appendstr(lock_str, 537299742Sdim svn_stringbuf_createf(pool, "%s|%s\n", path, token)); 538251881Speter } 539251881Speter 540251881Speter svn_stringbuf_appendcstr(lock_str, "\n"); 541251881Speter return create_temp_file(handle, 542251881Speter svn_stringbuf__morph_into_string(lock_str), pool); 543251881Speter} 544251881Speter 545251881Speter 546251881Speter 547251881Spetersvn_error_t * 548251881Spetersvn_repos__hooks_pre_commit(svn_repos_t *repos, 549251881Speter apr_hash_t *hooks_env, 550251881Speter const char *txn_name, 551251881Speter apr_pool_t *pool) 552251881Speter{ 553251881Speter const char *hook = svn_repos_pre_commit_hook(repos, pool); 554251881Speter svn_boolean_t broken_link; 555251881Speter 556251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 557251881Speter { 558251881Speter return hook_symlink_error(hook); 559251881Speter } 560251881Speter else if (hook) 561251881Speter { 562251881Speter const char *args[4]; 563251881Speter svn_fs_access_t *access_ctx; 564251881Speter apr_file_t *stdin_handle = NULL; 565251881Speter 566251881Speter args[0] = hook; 567251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 568251881Speter args[2] = txn_name; 569251881Speter args[3] = NULL; 570251881Speter 571251881Speter SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs)); 572251881Speter if (access_ctx) 573251881Speter { 574251881Speter apr_hash_t *lock_tokens = svn_fs__access_get_lock_tokens(access_ctx); 575251881Speter if (apr_hash_count(lock_tokens)) { 576251881Speter SVN_ERR(lock_token_content(&stdin_handle, lock_tokens, pool)); 577251881Speter } 578251881Speter } 579251881Speter 580251881Speter if (!stdin_handle) 581251881Speter SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 582251881Speter APR_READ, APR_OS_DEFAULT, pool)); 583251881Speter 584251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_COMMIT, hook, args, 585251881Speter hooks_env, stdin_handle, pool)); 586251881Speter } 587251881Speter 588251881Speter return SVN_NO_ERROR; 589251881Speter} 590251881Speter 591251881Speter 592251881Spetersvn_error_t * 593251881Spetersvn_repos__hooks_post_commit(svn_repos_t *repos, 594251881Speter apr_hash_t *hooks_env, 595251881Speter svn_revnum_t rev, 596251881Speter const char *txn_name, 597251881Speter apr_pool_t *pool) 598251881Speter{ 599251881Speter const char *hook = svn_repos_post_commit_hook(repos, pool); 600251881Speter svn_boolean_t broken_link; 601251881Speter 602251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 603251881Speter { 604251881Speter return hook_symlink_error(hook); 605251881Speter } 606251881Speter else if (hook) 607251881Speter { 608251881Speter const char *args[5]; 609251881Speter 610251881Speter args[0] = hook; 611251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 612251881Speter args[2] = apr_psprintf(pool, "%ld", rev); 613251881Speter args[3] = txn_name; 614251881Speter args[4] = NULL; 615251881Speter 616251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args, 617251881Speter hooks_env, NULL, pool)); 618251881Speter } 619251881Speter 620251881Speter return SVN_NO_ERROR; 621251881Speter} 622251881Speter 623251881Speter 624251881Spetersvn_error_t * 625251881Spetersvn_repos__hooks_pre_revprop_change(svn_repos_t *repos, 626251881Speter apr_hash_t *hooks_env, 627251881Speter svn_revnum_t rev, 628251881Speter const char *author, 629251881Speter const char *name, 630251881Speter const svn_string_t *new_value, 631251881Speter char action, 632251881Speter apr_pool_t *pool) 633251881Speter{ 634251881Speter const char *hook = svn_repos_pre_revprop_change_hook(repos, pool); 635251881Speter svn_boolean_t broken_link; 636251881Speter 637251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 638251881Speter { 639251881Speter return hook_symlink_error(hook); 640251881Speter } 641251881Speter else if (hook) 642251881Speter { 643251881Speter const char *args[7]; 644251881Speter apr_file_t *stdin_handle = NULL; 645251881Speter char action_string[2]; 646251881Speter 647251881Speter /* Pass the new value as stdin to hook */ 648251881Speter if (new_value) 649251881Speter SVN_ERR(create_temp_file(&stdin_handle, new_value, pool)); 650251881Speter else 651251881Speter SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 652251881Speter APR_READ, APR_OS_DEFAULT, pool)); 653251881Speter 654251881Speter action_string[0] = action; 655251881Speter action_string[1] = '\0'; 656251881Speter 657251881Speter args[0] = hook; 658251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 659251881Speter args[2] = apr_psprintf(pool, "%ld", rev); 660251881Speter args[3] = author ? author : ""; 661251881Speter args[4] = name; 662251881Speter args[5] = action_string; 663251881Speter args[6] = NULL; 664251881Speter 665251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook, 666251881Speter args, hooks_env, stdin_handle, pool)); 667251881Speter 668251881Speter SVN_ERR(svn_io_file_close(stdin_handle, pool)); 669251881Speter } 670251881Speter else 671251881Speter { 672251881Speter /* If the pre- hook doesn't exist at all, then default to 673251881Speter MASSIVE PARANOIA. Changing revision properties is a lossy 674251881Speter operation; so unless the repository admininstrator has 675251881Speter *deliberately* created the pre-hook, disallow all changes. */ 676251881Speter return 677251881Speter svn_error_create 678251881Speter (SVN_ERR_REPOS_DISABLED_FEATURE, NULL, 679251881Speter _("Repository has not been enabled to accept revision propchanges;\n" 680251881Speter "ask the administrator to create a pre-revprop-change hook")); 681251881Speter } 682251881Speter 683251881Speter return SVN_NO_ERROR; 684251881Speter} 685251881Speter 686251881Speter 687251881Spetersvn_error_t * 688251881Spetersvn_repos__hooks_post_revprop_change(svn_repos_t *repos, 689251881Speter apr_hash_t *hooks_env, 690251881Speter svn_revnum_t rev, 691251881Speter const char *author, 692251881Speter const char *name, 693251881Speter const svn_string_t *old_value, 694251881Speter char action, 695251881Speter apr_pool_t *pool) 696251881Speter{ 697251881Speter const char *hook = svn_repos_post_revprop_change_hook(repos, pool); 698251881Speter svn_boolean_t broken_link; 699251881Speter 700251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 701251881Speter { 702251881Speter return hook_symlink_error(hook); 703251881Speter } 704251881Speter else if (hook) 705251881Speter { 706251881Speter const char *args[7]; 707251881Speter apr_file_t *stdin_handle = NULL; 708251881Speter char action_string[2]; 709251881Speter 710251881Speter /* Pass the old value as stdin to hook */ 711251881Speter if (old_value) 712251881Speter SVN_ERR(create_temp_file(&stdin_handle, old_value, pool)); 713251881Speter else 714251881Speter SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 715251881Speter APR_READ, APR_OS_DEFAULT, pool)); 716251881Speter 717251881Speter action_string[0] = action; 718251881Speter action_string[1] = '\0'; 719251881Speter 720251881Speter args[0] = hook; 721251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 722251881Speter args[2] = apr_psprintf(pool, "%ld", rev); 723251881Speter args[3] = author ? author : ""; 724251881Speter args[4] = name; 725251881Speter args[5] = action_string; 726251881Speter args[6] = NULL; 727251881Speter 728251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_REVPROP_CHANGE, hook, 729251881Speter args, hooks_env, stdin_handle, pool)); 730251881Speter 731251881Speter SVN_ERR(svn_io_file_close(stdin_handle, pool)); 732251881Speter } 733251881Speter 734251881Speter return SVN_NO_ERROR; 735251881Speter} 736251881Speter 737251881Speter 738251881Spetersvn_error_t * 739251881Spetersvn_repos__hooks_pre_lock(svn_repos_t *repos, 740251881Speter apr_hash_t *hooks_env, 741251881Speter const char **token, 742251881Speter const char *path, 743251881Speter const char *username, 744251881Speter const char *comment, 745251881Speter svn_boolean_t steal_lock, 746251881Speter apr_pool_t *pool) 747251881Speter{ 748251881Speter const char *hook = svn_repos_pre_lock_hook(repos, pool); 749251881Speter svn_boolean_t broken_link; 750251881Speter 751251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 752251881Speter { 753251881Speter return hook_symlink_error(hook); 754251881Speter } 755251881Speter else if (hook) 756251881Speter { 757251881Speter const char *args[7]; 758251881Speter svn_string_t *buf; 759251881Speter 760251881Speter 761251881Speter args[0] = hook; 762251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 763251881Speter args[2] = path; 764251881Speter args[3] = username; 765251881Speter args[4] = comment ? comment : ""; 766251881Speter args[5] = steal_lock ? "1" : "0"; 767251881Speter args[6] = NULL; 768251881Speter 769251881Speter SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args, 770251881Speter hooks_env, NULL, pool)); 771251881Speter 772251881Speter if (token) 773251881Speter /* No validation here; the FS will take care of that. */ 774251881Speter *token = buf->data; 775251881Speter 776251881Speter } 777251881Speter else if (token) 778251881Speter *token = ""; 779251881Speter 780251881Speter return SVN_NO_ERROR; 781251881Speter} 782251881Speter 783251881Speter 784251881Spetersvn_error_t * 785251881Spetersvn_repos__hooks_post_lock(svn_repos_t *repos, 786251881Speter apr_hash_t *hooks_env, 787251881Speter const apr_array_header_t *paths, 788251881Speter const char *username, 789251881Speter apr_pool_t *pool) 790251881Speter{ 791251881Speter const char *hook = svn_repos_post_lock_hook(repos, pool); 792251881Speter svn_boolean_t broken_link; 793251881Speter 794251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 795251881Speter { 796251881Speter return hook_symlink_error(hook); 797251881Speter } 798251881Speter else if (hook) 799251881Speter { 800251881Speter const char *args[5]; 801251881Speter apr_file_t *stdin_handle = NULL; 802251881Speter svn_string_t *paths_str = svn_string_create(svn_cstring_join 803251881Speter (paths, "\n", pool), 804251881Speter pool); 805251881Speter 806251881Speter SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool)); 807251881Speter 808251881Speter args[0] = hook; 809251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 810251881Speter args[2] = username; 811251881Speter args[3] = NULL; 812251881Speter args[4] = NULL; 813251881Speter 814251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_LOCK, hook, args, 815251881Speter hooks_env, stdin_handle, pool)); 816251881Speter 817251881Speter SVN_ERR(svn_io_file_close(stdin_handle, pool)); 818251881Speter } 819251881Speter 820251881Speter return SVN_NO_ERROR; 821251881Speter} 822251881Speter 823251881Speter 824251881Spetersvn_error_t * 825251881Spetersvn_repos__hooks_pre_unlock(svn_repos_t *repos, 826251881Speter apr_hash_t *hooks_env, 827251881Speter const char *path, 828251881Speter const char *username, 829251881Speter const char *token, 830251881Speter svn_boolean_t break_lock, 831251881Speter apr_pool_t *pool) 832251881Speter{ 833251881Speter const char *hook = svn_repos_pre_unlock_hook(repos, pool); 834251881Speter svn_boolean_t broken_link; 835251881Speter 836251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 837251881Speter { 838251881Speter return hook_symlink_error(hook); 839251881Speter } 840251881Speter else if (hook) 841251881Speter { 842251881Speter const char *args[7]; 843251881Speter 844251881Speter args[0] = hook; 845251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 846251881Speter args[2] = path; 847251881Speter args[3] = username ? username : ""; 848251881Speter args[4] = token ? token : ""; 849251881Speter args[5] = break_lock ? "1" : "0"; 850251881Speter args[6] = NULL; 851251881Speter 852251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args, 853251881Speter hooks_env, NULL, pool)); 854251881Speter } 855251881Speter 856251881Speter return SVN_NO_ERROR; 857251881Speter} 858251881Speter 859251881Speter 860251881Spetersvn_error_t * 861251881Spetersvn_repos__hooks_post_unlock(svn_repos_t *repos, 862251881Speter apr_hash_t *hooks_env, 863251881Speter const apr_array_header_t *paths, 864251881Speter const char *username, 865251881Speter apr_pool_t *pool) 866251881Speter{ 867251881Speter const char *hook = svn_repos_post_unlock_hook(repos, pool); 868251881Speter svn_boolean_t broken_link; 869251881Speter 870251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 871251881Speter { 872251881Speter return hook_symlink_error(hook); 873251881Speter } 874251881Speter else if (hook) 875251881Speter { 876251881Speter const char *args[5]; 877251881Speter apr_file_t *stdin_handle = NULL; 878251881Speter svn_string_t *paths_str = svn_string_create(svn_cstring_join 879251881Speter (paths, "\n", pool), 880251881Speter pool); 881251881Speter 882251881Speter SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool)); 883251881Speter 884251881Speter args[0] = hook; 885251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 886251881Speter args[2] = username ? username : ""; 887251881Speter args[3] = NULL; 888251881Speter args[4] = NULL; 889251881Speter 890251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_UNLOCK, hook, args, 891251881Speter hooks_env, stdin_handle, pool)); 892251881Speter 893251881Speter SVN_ERR(svn_io_file_close(stdin_handle, pool)); 894251881Speter } 895251881Speter 896251881Speter return SVN_NO_ERROR; 897251881Speter} 898251881Speter 899251881Speter 900251881Speter 901251881Speter/* 902251881Speter * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq 903251881Speter * vim:isk=a-z,A-Z,48-57,_,.,-,> 904251881Speter * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0 905251881Speter */ 906