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", 187251881Speter (const char *)svn__apr_hash_index_key(hi), 188251881Speter (const char *)svn__apr_hash_index_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 321251881Speter As Windows Scripting Host (.wsf) files can accomodate (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 = 337251881Speter (**extn ? apr_pstrcat(pool, hook, *extn, (char *)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; 366251881Speter} parse_hooks_env_option_baton; 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; 396251881Speter} parse_hooks_env_section_baton; 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 svn_config_t *cfg; 420251881Speter struct parse_hooks_env_section_baton b; 421251881Speter 422251881Speter if (local_abspath) 423251881Speter { 424251881Speter SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE, 425251881Speter TRUE, TRUE, scratch_pool)); 426251881Speter b.cfg = cfg; 427251881Speter b.hooks_env = apr_hash_make(result_pool); 428251881Speter (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section, &b, 429251881Speter scratch_pool); 430251881Speter *hooks_env_p = b.hooks_env; 431251881Speter } 432251881Speter else 433251881Speter { 434251881Speter *hooks_env_p = NULL; 435251881Speter } 436251881Speter 437251881Speter return SVN_NO_ERROR; 438251881Speter} 439251881Speter 440251881Speter/* Return an error for the failure of HOOK due to a broken symlink. */ 441251881Speterstatic svn_error_t * 442251881Speterhook_symlink_error(const char *hook) 443251881Speter{ 444251881Speter return svn_error_createf 445251881Speter (SVN_ERR_REPOS_HOOK_FAILURE, NULL, 446251881Speter _("Failed to run '%s' hook; broken symlink"), hook); 447251881Speter} 448251881Speter 449251881Spetersvn_error_t * 450251881Spetersvn_repos__hooks_start_commit(svn_repos_t *repos, 451251881Speter apr_hash_t *hooks_env, 452251881Speter const char *user, 453251881Speter const apr_array_header_t *capabilities, 454251881Speter const char *txn_name, 455251881Speter apr_pool_t *pool) 456251881Speter{ 457251881Speter const char *hook = svn_repos_start_commit_hook(repos, pool); 458251881Speter svn_boolean_t broken_link; 459251881Speter 460251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 461251881Speter { 462251881Speter return hook_symlink_error(hook); 463251881Speter } 464251881Speter else if (hook) 465251881Speter { 466251881Speter const char *args[6]; 467251881Speter char *capabilities_string; 468251881Speter 469251881Speter if (capabilities) 470251881Speter { 471251881Speter capabilities_string = svn_cstring_join(capabilities, ":", pool); 472251881Speter 473251881Speter /* Get rid of that annoying final colon. */ 474251881Speter if (capabilities_string[0]) 475251881Speter capabilities_string[strlen(capabilities_string) - 1] = '\0'; 476251881Speter } 477251881Speter else 478251881Speter { 479251881Speter capabilities_string = apr_pstrdup(pool, ""); 480251881Speter } 481251881Speter 482251881Speter args[0] = hook; 483251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 484251881Speter args[2] = user ? user : ""; 485251881Speter args[3] = capabilities_string; 486251881Speter args[4] = txn_name; 487251881Speter args[5] = NULL; 488251881Speter 489251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args, 490251881Speter hooks_env, NULL, pool)); 491251881Speter } 492251881Speter 493251881Speter return SVN_NO_ERROR; 494251881Speter} 495251881Speter 496251881Speter/* Set *HANDLE to an open filehandle for a temporary file (i.e., 497251881Speter automatically deleted when closed), into which the LOCK_TOKENS have 498251881Speter been written out in the format described in the pre-commit hook 499251881Speter template. 500251881Speter 501251881Speter LOCK_TOKENS is as returned by svn_fs__access_get_lock_tokens(). 502251881Speter 503251881Speter Allocate *HANDLE in POOL, and use POOL for temporary allocations. */ 504251881Speterstatic svn_error_t * 505251881Speterlock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens, 506251881Speter apr_pool_t *pool) 507251881Speter{ 508251881Speter svn_stringbuf_t *lock_str = svn_stringbuf_create("LOCK-TOKENS:\n", pool); 509251881Speter apr_hash_index_t *hi; 510251881Speter 511251881Speter for (hi = apr_hash_first(pool, lock_tokens); hi; 512251881Speter hi = apr_hash_next(hi)) 513251881Speter { 514251881Speter void *val; 515251881Speter const char *path, *token; 516251881Speter 517251881Speter apr_hash_this(hi, (void *)&token, NULL, &val); 518251881Speter path = val; 519251881Speter svn_stringbuf_appendstr(lock_str, 520251881Speter svn_stringbuf_createf(pool, "%s|%s\n", 521251881Speter svn_path_uri_autoescape(path, pool), 522251881Speter token)); 523251881Speter } 524251881Speter 525251881Speter svn_stringbuf_appendcstr(lock_str, "\n"); 526251881Speter return create_temp_file(handle, 527251881Speter svn_stringbuf__morph_into_string(lock_str), pool); 528251881Speter} 529251881Speter 530251881Speter 531251881Speter 532251881Spetersvn_error_t * 533251881Spetersvn_repos__hooks_pre_commit(svn_repos_t *repos, 534251881Speter apr_hash_t *hooks_env, 535251881Speter const char *txn_name, 536251881Speter apr_pool_t *pool) 537251881Speter{ 538251881Speter const char *hook = svn_repos_pre_commit_hook(repos, pool); 539251881Speter svn_boolean_t broken_link; 540251881Speter 541251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 542251881Speter { 543251881Speter return hook_symlink_error(hook); 544251881Speter } 545251881Speter else if (hook) 546251881Speter { 547251881Speter const char *args[4]; 548251881Speter svn_fs_access_t *access_ctx; 549251881Speter apr_file_t *stdin_handle = NULL; 550251881Speter 551251881Speter args[0] = hook; 552251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 553251881Speter args[2] = txn_name; 554251881Speter args[3] = NULL; 555251881Speter 556251881Speter SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs)); 557251881Speter if (access_ctx) 558251881Speter { 559251881Speter apr_hash_t *lock_tokens = svn_fs__access_get_lock_tokens(access_ctx); 560251881Speter if (apr_hash_count(lock_tokens)) { 561251881Speter SVN_ERR(lock_token_content(&stdin_handle, lock_tokens, pool)); 562251881Speter } 563251881Speter } 564251881Speter 565251881Speter if (!stdin_handle) 566251881Speter SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 567251881Speter APR_READ, APR_OS_DEFAULT, pool)); 568251881Speter 569251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_COMMIT, hook, args, 570251881Speter hooks_env, stdin_handle, pool)); 571251881Speter } 572251881Speter 573251881Speter return SVN_NO_ERROR; 574251881Speter} 575251881Speter 576251881Speter 577251881Spetersvn_error_t * 578251881Spetersvn_repos__hooks_post_commit(svn_repos_t *repos, 579251881Speter apr_hash_t *hooks_env, 580251881Speter svn_revnum_t rev, 581251881Speter const char *txn_name, 582251881Speter apr_pool_t *pool) 583251881Speter{ 584251881Speter const char *hook = svn_repos_post_commit_hook(repos, pool); 585251881Speter svn_boolean_t broken_link; 586251881Speter 587251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 588251881Speter { 589251881Speter return hook_symlink_error(hook); 590251881Speter } 591251881Speter else if (hook) 592251881Speter { 593251881Speter const char *args[5]; 594251881Speter 595251881Speter args[0] = hook; 596251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 597251881Speter args[2] = apr_psprintf(pool, "%ld", rev); 598251881Speter args[3] = txn_name; 599251881Speter args[4] = NULL; 600251881Speter 601251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args, 602251881Speter hooks_env, NULL, pool)); 603251881Speter } 604251881Speter 605251881Speter return SVN_NO_ERROR; 606251881Speter} 607251881Speter 608251881Speter 609251881Spetersvn_error_t * 610251881Spetersvn_repos__hooks_pre_revprop_change(svn_repos_t *repos, 611251881Speter apr_hash_t *hooks_env, 612251881Speter svn_revnum_t rev, 613251881Speter const char *author, 614251881Speter const char *name, 615251881Speter const svn_string_t *new_value, 616251881Speter char action, 617251881Speter apr_pool_t *pool) 618251881Speter{ 619251881Speter const char *hook = svn_repos_pre_revprop_change_hook(repos, pool); 620251881Speter svn_boolean_t broken_link; 621251881Speter 622251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 623251881Speter { 624251881Speter return hook_symlink_error(hook); 625251881Speter } 626251881Speter else if (hook) 627251881Speter { 628251881Speter const char *args[7]; 629251881Speter apr_file_t *stdin_handle = NULL; 630251881Speter char action_string[2]; 631251881Speter 632251881Speter /* Pass the new value as stdin to hook */ 633251881Speter if (new_value) 634251881Speter SVN_ERR(create_temp_file(&stdin_handle, new_value, pool)); 635251881Speter else 636251881Speter SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 637251881Speter APR_READ, APR_OS_DEFAULT, pool)); 638251881Speter 639251881Speter action_string[0] = action; 640251881Speter action_string[1] = '\0'; 641251881Speter 642251881Speter args[0] = hook; 643251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 644251881Speter args[2] = apr_psprintf(pool, "%ld", rev); 645251881Speter args[3] = author ? author : ""; 646251881Speter args[4] = name; 647251881Speter args[5] = action_string; 648251881Speter args[6] = NULL; 649251881Speter 650251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook, 651251881Speter args, hooks_env, stdin_handle, pool)); 652251881Speter 653251881Speter SVN_ERR(svn_io_file_close(stdin_handle, pool)); 654251881Speter } 655251881Speter else 656251881Speter { 657251881Speter /* If the pre- hook doesn't exist at all, then default to 658251881Speter MASSIVE PARANOIA. Changing revision properties is a lossy 659251881Speter operation; so unless the repository admininstrator has 660251881Speter *deliberately* created the pre-hook, disallow all changes. */ 661251881Speter return 662251881Speter svn_error_create 663251881Speter (SVN_ERR_REPOS_DISABLED_FEATURE, NULL, 664251881Speter _("Repository has not been enabled to accept revision propchanges;\n" 665251881Speter "ask the administrator to create a pre-revprop-change hook")); 666251881Speter } 667251881Speter 668251881Speter return SVN_NO_ERROR; 669251881Speter} 670251881Speter 671251881Speter 672251881Spetersvn_error_t * 673251881Spetersvn_repos__hooks_post_revprop_change(svn_repos_t *repos, 674251881Speter apr_hash_t *hooks_env, 675251881Speter svn_revnum_t rev, 676251881Speter const char *author, 677251881Speter const char *name, 678251881Speter const svn_string_t *old_value, 679251881Speter char action, 680251881Speter apr_pool_t *pool) 681251881Speter{ 682251881Speter const char *hook = svn_repos_post_revprop_change_hook(repos, pool); 683251881Speter svn_boolean_t broken_link; 684251881Speter 685251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 686251881Speter { 687251881Speter return hook_symlink_error(hook); 688251881Speter } 689251881Speter else if (hook) 690251881Speter { 691251881Speter const char *args[7]; 692251881Speter apr_file_t *stdin_handle = NULL; 693251881Speter char action_string[2]; 694251881Speter 695251881Speter /* Pass the old value as stdin to hook */ 696251881Speter if (old_value) 697251881Speter SVN_ERR(create_temp_file(&stdin_handle, old_value, pool)); 698251881Speter else 699251881Speter SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 700251881Speter APR_READ, APR_OS_DEFAULT, pool)); 701251881Speter 702251881Speter action_string[0] = action; 703251881Speter action_string[1] = '\0'; 704251881Speter 705251881Speter args[0] = hook; 706251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 707251881Speter args[2] = apr_psprintf(pool, "%ld", rev); 708251881Speter args[3] = author ? author : ""; 709251881Speter args[4] = name; 710251881Speter args[5] = action_string; 711251881Speter args[6] = NULL; 712251881Speter 713251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_REVPROP_CHANGE, hook, 714251881Speter args, hooks_env, stdin_handle, pool)); 715251881Speter 716251881Speter SVN_ERR(svn_io_file_close(stdin_handle, pool)); 717251881Speter } 718251881Speter 719251881Speter return SVN_NO_ERROR; 720251881Speter} 721251881Speter 722251881Speter 723251881Spetersvn_error_t * 724251881Spetersvn_repos__hooks_pre_lock(svn_repos_t *repos, 725251881Speter apr_hash_t *hooks_env, 726251881Speter const char **token, 727251881Speter const char *path, 728251881Speter const char *username, 729251881Speter const char *comment, 730251881Speter svn_boolean_t steal_lock, 731251881Speter apr_pool_t *pool) 732251881Speter{ 733251881Speter const char *hook = svn_repos_pre_lock_hook(repos, pool); 734251881Speter svn_boolean_t broken_link; 735251881Speter 736251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 737251881Speter { 738251881Speter return hook_symlink_error(hook); 739251881Speter } 740251881Speter else if (hook) 741251881Speter { 742251881Speter const char *args[7]; 743251881Speter svn_string_t *buf; 744251881Speter 745251881Speter 746251881Speter args[0] = hook; 747251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 748251881Speter args[2] = path; 749251881Speter args[3] = username; 750251881Speter args[4] = comment ? comment : ""; 751251881Speter args[5] = steal_lock ? "1" : "0"; 752251881Speter args[6] = NULL; 753251881Speter 754251881Speter SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args, 755251881Speter hooks_env, NULL, pool)); 756251881Speter 757251881Speter if (token) 758251881Speter /* No validation here; the FS will take care of that. */ 759251881Speter *token = buf->data; 760251881Speter 761251881Speter } 762251881Speter else if (token) 763251881Speter *token = ""; 764251881Speter 765251881Speter return SVN_NO_ERROR; 766251881Speter} 767251881Speter 768251881Speter 769251881Spetersvn_error_t * 770251881Spetersvn_repos__hooks_post_lock(svn_repos_t *repos, 771251881Speter apr_hash_t *hooks_env, 772251881Speter const apr_array_header_t *paths, 773251881Speter const char *username, 774251881Speter apr_pool_t *pool) 775251881Speter{ 776251881Speter const char *hook = svn_repos_post_lock_hook(repos, pool); 777251881Speter svn_boolean_t broken_link; 778251881Speter 779251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 780251881Speter { 781251881Speter return hook_symlink_error(hook); 782251881Speter } 783251881Speter else if (hook) 784251881Speter { 785251881Speter const char *args[5]; 786251881Speter apr_file_t *stdin_handle = NULL; 787251881Speter svn_string_t *paths_str = svn_string_create(svn_cstring_join 788251881Speter (paths, "\n", pool), 789251881Speter pool); 790251881Speter 791251881Speter SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool)); 792251881Speter 793251881Speter args[0] = hook; 794251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 795251881Speter args[2] = username; 796251881Speter args[3] = NULL; 797251881Speter args[4] = NULL; 798251881Speter 799251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_LOCK, hook, args, 800251881Speter hooks_env, stdin_handle, pool)); 801251881Speter 802251881Speter SVN_ERR(svn_io_file_close(stdin_handle, pool)); 803251881Speter } 804251881Speter 805251881Speter return SVN_NO_ERROR; 806251881Speter} 807251881Speter 808251881Speter 809251881Spetersvn_error_t * 810251881Spetersvn_repos__hooks_pre_unlock(svn_repos_t *repos, 811251881Speter apr_hash_t *hooks_env, 812251881Speter const char *path, 813251881Speter const char *username, 814251881Speter const char *token, 815251881Speter svn_boolean_t break_lock, 816251881Speter apr_pool_t *pool) 817251881Speter{ 818251881Speter const char *hook = svn_repos_pre_unlock_hook(repos, pool); 819251881Speter svn_boolean_t broken_link; 820251881Speter 821251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 822251881Speter { 823251881Speter return hook_symlink_error(hook); 824251881Speter } 825251881Speter else if (hook) 826251881Speter { 827251881Speter const char *args[7]; 828251881Speter 829251881Speter args[0] = hook; 830251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 831251881Speter args[2] = path; 832251881Speter args[3] = username ? username : ""; 833251881Speter args[4] = token ? token : ""; 834251881Speter args[5] = break_lock ? "1" : "0"; 835251881Speter args[6] = NULL; 836251881Speter 837251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args, 838251881Speter hooks_env, NULL, pool)); 839251881Speter } 840251881Speter 841251881Speter return SVN_NO_ERROR; 842251881Speter} 843251881Speter 844251881Speter 845251881Spetersvn_error_t * 846251881Spetersvn_repos__hooks_post_unlock(svn_repos_t *repos, 847251881Speter apr_hash_t *hooks_env, 848251881Speter const apr_array_header_t *paths, 849251881Speter const char *username, 850251881Speter apr_pool_t *pool) 851251881Speter{ 852251881Speter const char *hook = svn_repos_post_unlock_hook(repos, pool); 853251881Speter svn_boolean_t broken_link; 854251881Speter 855251881Speter if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 856251881Speter { 857251881Speter return hook_symlink_error(hook); 858251881Speter } 859251881Speter else if (hook) 860251881Speter { 861251881Speter const char *args[5]; 862251881Speter apr_file_t *stdin_handle = NULL; 863251881Speter svn_string_t *paths_str = svn_string_create(svn_cstring_join 864251881Speter (paths, "\n", pool), 865251881Speter pool); 866251881Speter 867251881Speter SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool)); 868251881Speter 869251881Speter args[0] = hook; 870251881Speter args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 871251881Speter args[2] = username ? username : ""; 872251881Speter args[3] = NULL; 873251881Speter args[4] = NULL; 874251881Speter 875251881Speter SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_UNLOCK, hook, args, 876251881Speter hooks_env, stdin_handle, pool)); 877251881Speter 878251881Speter SVN_ERR(svn_io_file_close(stdin_handle, pool)); 879251881Speter } 880251881Speter 881251881Speter return SVN_NO_ERROR; 882251881Speter} 883251881Speter 884251881Speter 885251881Speter 886251881Speter/* 887251881Speter * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq 888251881Speter * vim:isk=a-z,A-Z,48-57,_,.,-,> 889251881Speter * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0 890251881Speter */ 891