1/* hooks.c : running repository hooks 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23#include <stdio.h> 24#include <string.h> 25#include <ctype.h> 26 27#include <apr_pools.h> 28#include <apr_file_io.h> 29 30#include "svn_config.h" 31#include "svn_hash.h" 32#include "svn_error.h" 33#include "svn_dirent_uri.h" 34#include "svn_path.h" 35#include "svn_pools.h" 36#include "svn_repos.h" 37#include "svn_utf.h" 38#include "repos.h" 39#include "svn_private_config.h" 40#include "private/svn_fs_private.h" 41#include "private/svn_repos_private.h" 42#include "private/svn_string_private.h" 43 44 45 46/*** Hook drivers. ***/ 47 48/* Helper function for run_hook_cmd(). Wait for a hook to finish 49 executing and return either SVN_NO_ERROR if the hook script completed 50 without error, or an error describing the reason for failure. 51 52 NAME and CMD are the name and path of the hook program, CMD_PROC 53 is a pointer to the structure representing the running process, 54 and READ_ERRHANDLE is an open handle to the hook's stderr. 55 56 Hooks are considered to have failed if we are unable to wait for the 57 process, if we are unable to read from the hook's stderr, if the 58 process has failed to exit cleanly (due to a coredump, for example), 59 or if the process returned a non-zero return code. 60 61 Any error output returned by the hook's stderr will be included in an 62 error message, though the presence of output on stderr is not itself 63 a reason to fail a hook. */ 64static svn_error_t * 65check_hook_result(const char *name, const char *cmd, apr_proc_t *cmd_proc, 66 apr_file_t *read_errhandle, apr_pool_t *pool) 67{ 68 svn_error_t *err, *err2; 69 svn_stringbuf_t *native_stderr, *failure_message; 70 const char *utf8_stderr; 71 int exitcode; 72 apr_exit_why_e exitwhy; 73 74 err2 = svn_stringbuf_from_aprfile(&native_stderr, read_errhandle, pool); 75 76 err = svn_io_wait_for_cmd(cmd_proc, cmd, &exitcode, &exitwhy, pool); 77 if (err) 78 { 79 svn_error_clear(err2); 80 return svn_error_trace(err); 81 } 82 83 if (APR_PROC_CHECK_EXIT(exitwhy) && exitcode == 0) 84 { 85 /* The hook exited cleanly. However, if we got an error reading 86 the hook's stderr, fail the hook anyway, because this might be 87 symptomatic of a more important problem. */ 88 if (err2) 89 { 90 return svn_error_createf 91 (SVN_ERR_REPOS_HOOK_FAILURE, err2, 92 _("'%s' hook succeeded, but error output could not be read"), 93 name); 94 } 95 96 return SVN_NO_ERROR; 97 } 98 99 /* The hook script failed. */ 100 101 /* If we got the stderr output okay, try to translate it into UTF-8. 102 Ensure there is something sensible in the UTF-8 string regardless. */ 103 if (!err2) 104 { 105 err2 = svn_utf_cstring_to_utf8(&utf8_stderr, native_stderr->data, pool); 106 if (err2) 107 utf8_stderr = _("[Error output could not be translated from the " 108 "native locale to UTF-8.]"); 109 } 110 else 111 { 112 utf8_stderr = _("[Error output could not be read.]"); 113 } 114 /*### It would be nice to include the text of any translation or read 115 error in the messages above before we clear it here. */ 116 svn_error_clear(err2); 117 118 if (!APR_PROC_CHECK_EXIT(exitwhy)) 119 { 120 failure_message = svn_stringbuf_createf(pool, 121 _("'%s' hook failed (did not exit cleanly: " 122 "apr_exit_why_e was %d, exitcode was %d). "), 123 name, exitwhy, exitcode); 124 } 125 else 126 { 127 const char *action; 128 if (strcmp(name, "start-commit") == 0 129 || strcmp(name, "pre-commit") == 0) 130 action = _("Commit"); 131 else if (strcmp(name, "pre-revprop-change") == 0) 132 action = _("Revprop change"); 133 else if (strcmp(name, "pre-lock") == 0) 134 action = _("Lock"); 135 else if (strcmp(name, "pre-unlock") == 0) 136 action = _("Unlock"); 137 else 138 action = NULL; 139 if (action == NULL) 140 failure_message = svn_stringbuf_createf( 141 pool, _("%s hook failed (exit code %d)"), 142 name, exitcode); 143 else 144 failure_message = svn_stringbuf_createf( 145 pool, _("%s blocked by %s hook (exit code %d)"), 146 action, name, exitcode); 147 } 148 149 if (utf8_stderr[0]) 150 { 151 svn_stringbuf_appendcstr(failure_message, 152 _(" with output:\n")); 153 svn_stringbuf_appendcstr(failure_message, utf8_stderr); 154 } 155 else 156 { 157 svn_stringbuf_appendcstr(failure_message, 158 _(" with no output.")); 159 } 160 161 return svn_error_create(SVN_ERR_REPOS_HOOK_FAILURE, err, 162 failure_message->data); 163} 164 165/* Copy the environment given as key/value pairs of ENV_HASH into 166 * an array of C strings allocated in RESULT_POOL. 167 * If the hook environment is empty, return NULL. 168 * Use SCRATCH_POOL for temporary allocations. */ 169static const char ** 170env_from_env_hash(apr_hash_t *env_hash, 171 apr_pool_t *result_pool, 172 apr_pool_t *scratch_pool) 173{ 174 apr_hash_index_t *hi; 175 const char **env; 176 const char **envp; 177 178 if (!env_hash) 179 return NULL; 180 181 env = apr_palloc(result_pool, 182 sizeof(const char *) * (apr_hash_count(env_hash) + 1)); 183 envp = env; 184 for (hi = apr_hash_first(scratch_pool, env_hash); hi; hi = apr_hash_next(hi)) 185 { 186 *envp = apr_psprintf(result_pool, "%s=%s", 187 (const char *)svn__apr_hash_index_key(hi), 188 (const char *)svn__apr_hash_index_val(hi)); 189 envp++; 190 } 191 *envp = NULL; 192 193 return env; 194} 195 196/* NAME, CMD and ARGS are the name, path to and arguments for the hook 197 program that is to be run. The hook's exit status will be checked, 198 and if an error occurred the hook's stderr output will be added to 199 the returned error. 200 201 If STDIN_HANDLE is non-null, pass it as the hook's stdin, else pass 202 no stdin to the hook. 203 204 If RESULT is non-null, set *RESULT to the stdout of the hook or to 205 a zero-length string if the hook generates no output on stdout. */ 206static svn_error_t * 207run_hook_cmd(svn_string_t **result, 208 const char *name, 209 const char *cmd, 210 const char **args, 211 apr_hash_t *hooks_env, 212 apr_file_t *stdin_handle, 213 apr_pool_t *pool) 214{ 215 apr_file_t *null_handle; 216 apr_status_t apr_err; 217 svn_error_t *err; 218 apr_proc_t cmd_proc = {0}; 219 apr_pool_t *cmd_pool; 220 apr_hash_t *hook_env = NULL; 221 222 if (result) 223 { 224 null_handle = NULL; 225 } 226 else 227 { 228 /* Redirect stdout to the null device */ 229 apr_err = apr_file_open(&null_handle, SVN_NULL_DEVICE_NAME, APR_WRITE, 230 APR_OS_DEFAULT, pool); 231 if (apr_err) 232 return svn_error_wrap_apr 233 (apr_err, _("Can't create null stdout for hook '%s'"), cmd); 234 } 235 236 /* Tie resources allocated for the command to a special pool which we can 237 * destroy in order to clean up the stderr pipe opened for the process. */ 238 cmd_pool = svn_pool_create(pool); 239 240 /* Check if a custom environment is defined for this hook, or else 241 * whether a default environment is defined. */ 242 if (hooks_env) 243 { 244 hook_env = svn_hash_gets(hooks_env, name); 245 if (hook_env == NULL) 246 hook_env = svn_hash_gets(hooks_env, 247 SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION); 248 } 249 250 err = svn_io_start_cmd3(&cmd_proc, ".", cmd, args, 251 env_from_env_hash(hook_env, pool, pool), 252 FALSE, FALSE, stdin_handle, result != NULL, 253 null_handle, TRUE, NULL, cmd_pool); 254 if (!err) 255 err = check_hook_result(name, cmd, &cmd_proc, cmd_proc.err, pool); 256 else 257 { 258 /* The command could not be started for some reason. */ 259 err = svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, err, 260 _("Failed to start '%s' hook"), cmd); 261 } 262 263 /* Hooks are fallible, and so hook failure is "expected" to occur at 264 times. When such a failure happens we still want to close the pipe 265 and null file */ 266 if (!err && result) 267 { 268 svn_stringbuf_t *native_stdout; 269 err = svn_stringbuf_from_aprfile(&native_stdout, cmd_proc.out, pool); 270 if (!err) 271 *result = svn_stringbuf__morph_into_string(native_stdout); 272 } 273 274 /* Close resources allocated by svn_io_start_cmd3(), such as the pipe. */ 275 svn_pool_destroy(cmd_pool); 276 277 /* Close the null handle. */ 278 if (null_handle) 279 { 280 apr_err = apr_file_close(null_handle); 281 if (!err && apr_err) 282 return svn_error_wrap_apr(apr_err, _("Error closing null file")); 283 } 284 285 return svn_error_trace(err); 286} 287 288 289/* Create a temporary file F that will automatically be deleted when the 290 pool is cleaned up. Fill it with VALUE, and leave it open and rewound, 291 ready to be read from. */ 292static svn_error_t * 293create_temp_file(apr_file_t **f, const svn_string_t *value, apr_pool_t *pool) 294{ 295 apr_off_t offset = 0; 296 297 SVN_ERR(svn_io_open_unique_file3(f, NULL, NULL, 298 svn_io_file_del_on_pool_cleanup, 299 pool, pool)); 300 SVN_ERR(svn_io_file_write_full(*f, value->data, value->len, NULL, pool)); 301 return svn_io_file_seek(*f, APR_SET, &offset, pool); 302} 303 304 305/* Check if the HOOK program exists and is a file or a symbolic link, using 306 POOL for temporary allocations. 307 308 If the hook exists but is a broken symbolic link, set *BROKEN_LINK 309 to TRUE, else if the hook program exists set *BROKEN_LINK to FALSE. 310 311 Return the hook program if found, else return NULL and don't touch 312 *BROKEN_LINK. 313*/ 314static const char* 315check_hook_cmd(const char *hook, svn_boolean_t *broken_link, apr_pool_t *pool) 316{ 317 static const char* const check_extns[] = { 318#ifdef WIN32 319 /* For WIN32, we need to check with file name extension(s) added. 320 321 As Windows Scripting Host (.wsf) files can accomodate (at least) 322 JavaScript (.js) and VB Script (.vbs) code, extensions for the 323 corresponding file types need not be enumerated explicitly. */ 324 ".exe", ".cmd", ".bat", ".wsf", /* ### Any other extensions? */ 325#else 326 "", 327#endif 328 NULL 329 }; 330 331 const char *const *extn; 332 svn_error_t *err = NULL; 333 svn_boolean_t is_special; 334 for (extn = check_extns; *extn; ++extn) 335 { 336 const char *const hook_path = 337 (**extn ? apr_pstrcat(pool, hook, *extn, (char *)NULL) : hook); 338 339 svn_node_kind_t kind; 340 if (!(err = svn_io_check_resolved_path(hook_path, &kind, pool)) 341 && kind == svn_node_file) 342 { 343 *broken_link = FALSE; 344 return hook_path; 345 } 346 svn_error_clear(err); 347 if (!(err = svn_io_check_special_path(hook_path, &kind, &is_special, 348 pool)) 349 && is_special) 350 { 351 *broken_link = TRUE; 352 return hook_path; 353 } 354 svn_error_clear(err); 355 } 356 return NULL; 357} 358 359/* Baton for parse_hooks_env_option. */ 360struct parse_hooks_env_option_baton { 361 /* The name of the section being parsed. If not the default section, 362 * the section name should match the name of a hook to which the 363 * options apply. */ 364 const char *section; 365 apr_hash_t *hooks_env; 366} parse_hooks_env_option_baton; 367 368/* An implementation of svn_config_enumerator2_t. 369 * Set environment variable NAME to value VALUE in the environment for 370 * all hooks (in case the current section is the default section), 371 * or the hook with the name corresponding to the current section's name. */ 372static svn_boolean_t 373parse_hooks_env_option(const char *name, const char *value, 374 void *baton, apr_pool_t *pool) 375{ 376 struct parse_hooks_env_option_baton *bo = baton; 377 apr_pool_t *result_pool = apr_hash_pool_get(bo->hooks_env); 378 apr_hash_t *hook_env; 379 380 hook_env = svn_hash_gets(bo->hooks_env, bo->section); 381 if (hook_env == NULL) 382 { 383 hook_env = apr_hash_make(result_pool); 384 svn_hash_sets(bo->hooks_env, apr_pstrdup(result_pool, bo->section), 385 hook_env); 386 } 387 svn_hash_sets(hook_env, apr_pstrdup(result_pool, name), 388 apr_pstrdup(result_pool, value)); 389 390 return TRUE; 391} 392 393struct parse_hooks_env_section_baton { 394 svn_config_t *cfg; 395 apr_hash_t *hooks_env; 396} parse_hooks_env_section_baton; 397 398/* An implementation of svn_config_section_enumerator2_t. */ 399static svn_boolean_t 400parse_hooks_env_section(const char *name, void *baton, apr_pool_t *pool) 401{ 402 struct parse_hooks_env_section_baton *b = baton; 403 struct parse_hooks_env_option_baton bo; 404 405 bo.section = name; 406 bo.hooks_env = b->hooks_env; 407 408 (void)svn_config_enumerate2(b->cfg, name, parse_hooks_env_option, &bo, pool); 409 410 return TRUE; 411} 412 413svn_error_t * 414svn_repos__parse_hooks_env(apr_hash_t **hooks_env_p, 415 const char *local_abspath, 416 apr_pool_t *result_pool, 417 apr_pool_t *scratch_pool) 418{ 419 svn_config_t *cfg; 420 struct parse_hooks_env_section_baton b; 421 422 if (local_abspath) 423 { 424 SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE, 425 TRUE, TRUE, scratch_pool)); 426 b.cfg = cfg; 427 b.hooks_env = apr_hash_make(result_pool); 428 (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section, &b, 429 scratch_pool); 430 *hooks_env_p = b.hooks_env; 431 } 432 else 433 { 434 *hooks_env_p = NULL; 435 } 436 437 return SVN_NO_ERROR; 438} 439 440/* Return an error for the failure of HOOK due to a broken symlink. */ 441static svn_error_t * 442hook_symlink_error(const char *hook) 443{ 444 return svn_error_createf 445 (SVN_ERR_REPOS_HOOK_FAILURE, NULL, 446 _("Failed to run '%s' hook; broken symlink"), hook); 447} 448 449svn_error_t * 450svn_repos__hooks_start_commit(svn_repos_t *repos, 451 apr_hash_t *hooks_env, 452 const char *user, 453 const apr_array_header_t *capabilities, 454 const char *txn_name, 455 apr_pool_t *pool) 456{ 457 const char *hook = svn_repos_start_commit_hook(repos, pool); 458 svn_boolean_t broken_link; 459 460 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 461 { 462 return hook_symlink_error(hook); 463 } 464 else if (hook) 465 { 466 const char *args[6]; 467 char *capabilities_string; 468 469 if (capabilities) 470 { 471 capabilities_string = svn_cstring_join(capabilities, ":", pool); 472 473 /* Get rid of that annoying final colon. */ 474 if (capabilities_string[0]) 475 capabilities_string[strlen(capabilities_string) - 1] = '\0'; 476 } 477 else 478 { 479 capabilities_string = apr_pstrdup(pool, ""); 480 } 481 482 args[0] = hook; 483 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 484 args[2] = user ? user : ""; 485 args[3] = capabilities_string; 486 args[4] = txn_name; 487 args[5] = NULL; 488 489 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args, 490 hooks_env, NULL, pool)); 491 } 492 493 return SVN_NO_ERROR; 494} 495 496/* Set *HANDLE to an open filehandle for a temporary file (i.e., 497 automatically deleted when closed), into which the LOCK_TOKENS have 498 been written out in the format described in the pre-commit hook 499 template. 500 501 LOCK_TOKENS is as returned by svn_fs__access_get_lock_tokens(). 502 503 Allocate *HANDLE in POOL, and use POOL for temporary allocations. */ 504static svn_error_t * 505lock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens, 506 apr_pool_t *pool) 507{ 508 svn_stringbuf_t *lock_str = svn_stringbuf_create("LOCK-TOKENS:\n", pool); 509 apr_hash_index_t *hi; 510 511 for (hi = apr_hash_first(pool, lock_tokens); hi; 512 hi = apr_hash_next(hi)) 513 { 514 void *val; 515 const char *path, *token; 516 517 apr_hash_this(hi, (void *)&token, NULL, &val); 518 path = val; 519 svn_stringbuf_appendstr(lock_str, 520 svn_stringbuf_createf(pool, "%s|%s\n", 521 svn_path_uri_autoescape(path, pool), 522 token)); 523 } 524 525 svn_stringbuf_appendcstr(lock_str, "\n"); 526 return create_temp_file(handle, 527 svn_stringbuf__morph_into_string(lock_str), pool); 528} 529 530 531 532svn_error_t * 533svn_repos__hooks_pre_commit(svn_repos_t *repos, 534 apr_hash_t *hooks_env, 535 const char *txn_name, 536 apr_pool_t *pool) 537{ 538 const char *hook = svn_repos_pre_commit_hook(repos, pool); 539 svn_boolean_t broken_link; 540 541 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 542 { 543 return hook_symlink_error(hook); 544 } 545 else if (hook) 546 { 547 const char *args[4]; 548 svn_fs_access_t *access_ctx; 549 apr_file_t *stdin_handle = NULL; 550 551 args[0] = hook; 552 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 553 args[2] = txn_name; 554 args[3] = NULL; 555 556 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs)); 557 if (access_ctx) 558 { 559 apr_hash_t *lock_tokens = svn_fs__access_get_lock_tokens(access_ctx); 560 if (apr_hash_count(lock_tokens)) { 561 SVN_ERR(lock_token_content(&stdin_handle, lock_tokens, pool)); 562 } 563 } 564 565 if (!stdin_handle) 566 SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 567 APR_READ, APR_OS_DEFAULT, pool)); 568 569 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_COMMIT, hook, args, 570 hooks_env, stdin_handle, pool)); 571 } 572 573 return SVN_NO_ERROR; 574} 575 576 577svn_error_t * 578svn_repos__hooks_post_commit(svn_repos_t *repos, 579 apr_hash_t *hooks_env, 580 svn_revnum_t rev, 581 const char *txn_name, 582 apr_pool_t *pool) 583{ 584 const char *hook = svn_repos_post_commit_hook(repos, pool); 585 svn_boolean_t broken_link; 586 587 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 588 { 589 return hook_symlink_error(hook); 590 } 591 else if (hook) 592 { 593 const char *args[5]; 594 595 args[0] = hook; 596 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 597 args[2] = apr_psprintf(pool, "%ld", rev); 598 args[3] = txn_name; 599 args[4] = NULL; 600 601 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args, 602 hooks_env, NULL, pool)); 603 } 604 605 return SVN_NO_ERROR; 606} 607 608 609svn_error_t * 610svn_repos__hooks_pre_revprop_change(svn_repos_t *repos, 611 apr_hash_t *hooks_env, 612 svn_revnum_t rev, 613 const char *author, 614 const char *name, 615 const svn_string_t *new_value, 616 char action, 617 apr_pool_t *pool) 618{ 619 const char *hook = svn_repos_pre_revprop_change_hook(repos, pool); 620 svn_boolean_t broken_link; 621 622 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 623 { 624 return hook_symlink_error(hook); 625 } 626 else if (hook) 627 { 628 const char *args[7]; 629 apr_file_t *stdin_handle = NULL; 630 char action_string[2]; 631 632 /* Pass the new value as stdin to hook */ 633 if (new_value) 634 SVN_ERR(create_temp_file(&stdin_handle, new_value, pool)); 635 else 636 SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 637 APR_READ, APR_OS_DEFAULT, pool)); 638 639 action_string[0] = action; 640 action_string[1] = '\0'; 641 642 args[0] = hook; 643 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 644 args[2] = apr_psprintf(pool, "%ld", rev); 645 args[3] = author ? author : ""; 646 args[4] = name; 647 args[5] = action_string; 648 args[6] = NULL; 649 650 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook, 651 args, hooks_env, stdin_handle, pool)); 652 653 SVN_ERR(svn_io_file_close(stdin_handle, pool)); 654 } 655 else 656 { 657 /* If the pre- hook doesn't exist at all, then default to 658 MASSIVE PARANOIA. Changing revision properties is a lossy 659 operation; so unless the repository admininstrator has 660 *deliberately* created the pre-hook, disallow all changes. */ 661 return 662 svn_error_create 663 (SVN_ERR_REPOS_DISABLED_FEATURE, NULL, 664 _("Repository has not been enabled to accept revision propchanges;\n" 665 "ask the administrator to create a pre-revprop-change hook")); 666 } 667 668 return SVN_NO_ERROR; 669} 670 671 672svn_error_t * 673svn_repos__hooks_post_revprop_change(svn_repos_t *repos, 674 apr_hash_t *hooks_env, 675 svn_revnum_t rev, 676 const char *author, 677 const char *name, 678 const svn_string_t *old_value, 679 char action, 680 apr_pool_t *pool) 681{ 682 const char *hook = svn_repos_post_revprop_change_hook(repos, pool); 683 svn_boolean_t broken_link; 684 685 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 686 { 687 return hook_symlink_error(hook); 688 } 689 else if (hook) 690 { 691 const char *args[7]; 692 apr_file_t *stdin_handle = NULL; 693 char action_string[2]; 694 695 /* Pass the old value as stdin to hook */ 696 if (old_value) 697 SVN_ERR(create_temp_file(&stdin_handle, old_value, pool)); 698 else 699 SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, 700 APR_READ, APR_OS_DEFAULT, pool)); 701 702 action_string[0] = action; 703 action_string[1] = '\0'; 704 705 args[0] = hook; 706 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 707 args[2] = apr_psprintf(pool, "%ld", rev); 708 args[3] = author ? author : ""; 709 args[4] = name; 710 args[5] = action_string; 711 args[6] = NULL; 712 713 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_REVPROP_CHANGE, hook, 714 args, hooks_env, stdin_handle, pool)); 715 716 SVN_ERR(svn_io_file_close(stdin_handle, pool)); 717 } 718 719 return SVN_NO_ERROR; 720} 721 722 723svn_error_t * 724svn_repos__hooks_pre_lock(svn_repos_t *repos, 725 apr_hash_t *hooks_env, 726 const char **token, 727 const char *path, 728 const char *username, 729 const char *comment, 730 svn_boolean_t steal_lock, 731 apr_pool_t *pool) 732{ 733 const char *hook = svn_repos_pre_lock_hook(repos, pool); 734 svn_boolean_t broken_link; 735 736 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 737 { 738 return hook_symlink_error(hook); 739 } 740 else if (hook) 741 { 742 const char *args[7]; 743 svn_string_t *buf; 744 745 746 args[0] = hook; 747 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 748 args[2] = path; 749 args[3] = username; 750 args[4] = comment ? comment : ""; 751 args[5] = steal_lock ? "1" : "0"; 752 args[6] = NULL; 753 754 SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args, 755 hooks_env, NULL, pool)); 756 757 if (token) 758 /* No validation here; the FS will take care of that. */ 759 *token = buf->data; 760 761 } 762 else if (token) 763 *token = ""; 764 765 return SVN_NO_ERROR; 766} 767 768 769svn_error_t * 770svn_repos__hooks_post_lock(svn_repos_t *repos, 771 apr_hash_t *hooks_env, 772 const apr_array_header_t *paths, 773 const char *username, 774 apr_pool_t *pool) 775{ 776 const char *hook = svn_repos_post_lock_hook(repos, pool); 777 svn_boolean_t broken_link; 778 779 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 780 { 781 return hook_symlink_error(hook); 782 } 783 else if (hook) 784 { 785 const char *args[5]; 786 apr_file_t *stdin_handle = NULL; 787 svn_string_t *paths_str = svn_string_create(svn_cstring_join 788 (paths, "\n", pool), 789 pool); 790 791 SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool)); 792 793 args[0] = hook; 794 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 795 args[2] = username; 796 args[3] = NULL; 797 args[4] = NULL; 798 799 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_LOCK, hook, args, 800 hooks_env, stdin_handle, pool)); 801 802 SVN_ERR(svn_io_file_close(stdin_handle, pool)); 803 } 804 805 return SVN_NO_ERROR; 806} 807 808 809svn_error_t * 810svn_repos__hooks_pre_unlock(svn_repos_t *repos, 811 apr_hash_t *hooks_env, 812 const char *path, 813 const char *username, 814 const char *token, 815 svn_boolean_t break_lock, 816 apr_pool_t *pool) 817{ 818 const char *hook = svn_repos_pre_unlock_hook(repos, pool); 819 svn_boolean_t broken_link; 820 821 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 822 { 823 return hook_symlink_error(hook); 824 } 825 else if (hook) 826 { 827 const char *args[7]; 828 829 args[0] = hook; 830 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 831 args[2] = path; 832 args[3] = username ? username : ""; 833 args[4] = token ? token : ""; 834 args[5] = break_lock ? "1" : "0"; 835 args[6] = NULL; 836 837 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args, 838 hooks_env, NULL, pool)); 839 } 840 841 return SVN_NO_ERROR; 842} 843 844 845svn_error_t * 846svn_repos__hooks_post_unlock(svn_repos_t *repos, 847 apr_hash_t *hooks_env, 848 const apr_array_header_t *paths, 849 const char *username, 850 apr_pool_t *pool) 851{ 852 const char *hook = svn_repos_post_unlock_hook(repos, pool); 853 svn_boolean_t broken_link; 854 855 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link) 856 { 857 return hook_symlink_error(hook); 858 } 859 else if (hook) 860 { 861 const char *args[5]; 862 apr_file_t *stdin_handle = NULL; 863 svn_string_t *paths_str = svn_string_create(svn_cstring_join 864 (paths, "\n", pool), 865 pool); 866 867 SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool)); 868 869 args[0] = hook; 870 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool); 871 args[2] = username ? username : ""; 872 args[3] = NULL; 873 args[4] = NULL; 874 875 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_UNLOCK, hook, args, 876 hooks_env, stdin_handle, pool)); 877 878 SVN_ERR(svn_io_file_close(stdin_handle, pool)); 879 } 880 881 return SVN_NO_ERROR; 882} 883 884 885 886/* 887 * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq 888 * vim:isk=a-z,A-Z,48-57,_,.,-,> 889 * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0 890 */ 891