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. */
64 static svn_error_t *
check_hook_result(const char * name,const char * cmd,apr_proc_t * cmd_proc,apr_file_t * read_errhandle,apr_pool_t * pool)65 check_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. */
169 static const char **
env_from_env_hash(apr_hash_t * env_hash,apr_pool_t * result_pool,apr_pool_t * scratch_pool)170 env_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 *)apr_hash_this_key(hi),
188 (const char *)apr_hash_this_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. */
206 static svn_error_t *
run_hook_cmd(svn_string_t ** result,const char * name,const char * cmd,const char ** args,apr_hash_t * hooks_env,apr_file_t * stdin_handle,apr_pool_t * pool)207 run_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. */
292 static svn_error_t *
create_temp_file(apr_file_t ** f,const svn_string_t * value,apr_pool_t * pool)293 create_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 */
314 static const char*
check_hook_cmd(const char * hook,svn_boolean_t * broken_link,apr_pool_t * pool)315 check_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 accommodate (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, SVN_VA_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. */
360 struct 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 };
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. */
372 static svn_boolean_t
parse_hooks_env_option(const char * name,const char * value,void * baton,apr_pool_t * pool)373 parse_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
393 struct parse_hooks_env_section_baton {
394 svn_config_t *cfg;
395 apr_hash_t *hooks_env;
396 };
397
398 /* An implementation of svn_config_section_enumerator2_t. */
399 static svn_boolean_t
parse_hooks_env_section(const char * name,void * baton,apr_pool_t * pool)400 parse_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
413 svn_error_t *
svn_repos__parse_hooks_env(apr_hash_t ** hooks_env_p,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)414 svn_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 struct parse_hooks_env_section_baton b;
420 if (local_abspath)
421 {
422 svn_node_kind_t kind;
423 SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
424
425 b.hooks_env = apr_hash_make(result_pool);
426
427 if (kind != svn_node_none)
428 {
429 svn_config_t *cfg;
430 SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE,
431 TRUE, TRUE, scratch_pool));
432 b.cfg = cfg;
433
434 (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section,
435 &b, scratch_pool);
436 }
437
438 *hooks_env_p = b.hooks_env;
439 }
440 else
441 {
442 *hooks_env_p = NULL;
443 }
444
445 return SVN_NO_ERROR;
446 }
447
448 /* Return an error for the failure of HOOK due to a broken symlink. */
449 static svn_error_t *
hook_symlink_error(const char * hook)450 hook_symlink_error(const char *hook)
451 {
452 return svn_error_createf
453 (SVN_ERR_REPOS_HOOK_FAILURE, NULL,
454 _("Failed to run '%s' hook; broken symlink"), hook);
455 }
456
457 svn_error_t *
svn_repos__hooks_start_commit(svn_repos_t * repos,apr_hash_t * hooks_env,const char * user,const apr_array_header_t * capabilities,const char * txn_name,apr_pool_t * pool)458 svn_repos__hooks_start_commit(svn_repos_t *repos,
459 apr_hash_t *hooks_env,
460 const char *user,
461 const apr_array_header_t *capabilities,
462 const char *txn_name,
463 apr_pool_t *pool)
464 {
465 const char *hook = svn_repos_start_commit_hook(repos, pool);
466 svn_boolean_t broken_link;
467
468 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
469 {
470 return hook_symlink_error(hook);
471 }
472 else if (hook)
473 {
474 const char *args[6];
475 char *capabilities_string;
476
477 if (capabilities)
478 {
479 capabilities_string = svn_cstring_join2(capabilities, ":",
480 FALSE, pool);
481 }
482 else
483 {
484 capabilities_string = apr_pstrdup(pool, "");
485 }
486
487 args[0] = hook;
488 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
489 args[2] = user ? user : "";
490 args[3] = capabilities_string;
491 args[4] = txn_name;
492 args[5] = NULL;
493
494 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args,
495 hooks_env, NULL, pool));
496 }
497
498 return SVN_NO_ERROR;
499 }
500
501 /* Set *HANDLE to an open filehandle for a temporary file (i.e.,
502 automatically deleted when closed), into which the LOCK_TOKENS have
503 been written out in the format described in the pre-commit hook
504 template.
505
506 LOCK_TOKENS is as returned by svn_fs__access_get_lock_tokens().
507
508 Allocate *HANDLE in POOL, and use POOL for temporary allocations. */
509 static svn_error_t *
lock_token_content(apr_file_t ** handle,apr_hash_t * lock_tokens,apr_pool_t * pool)510 lock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens,
511 apr_pool_t *pool)
512 {
513 svn_stringbuf_t *lock_str = svn_stringbuf_create("LOCK-TOKENS:\n", pool);
514 apr_hash_index_t *hi;
515
516 for (hi = apr_hash_first(pool, lock_tokens); hi;
517 hi = apr_hash_next(hi))
518 {
519 const char *token = apr_hash_this_key(hi);
520 const char *path = apr_hash_this_val(hi);
521
522 if (path == (const char *) 1)
523 {
524 /* Special handling for svn_fs_access_t * created by using deprecated
525 svn_fs_access_add_lock_token() function. */
526 path = "";
527 }
528 else
529 {
530 path = svn_path_uri_autoescape(path, pool);
531 }
532
533 svn_stringbuf_appendstr(lock_str,
534 svn_stringbuf_createf(pool, "%s|%s\n", path, token));
535 }
536
537 svn_stringbuf_appendcstr(lock_str, "\n");
538 return create_temp_file(handle,
539 svn_stringbuf__morph_into_string(lock_str), pool);
540 }
541
542
543
544 svn_error_t *
svn_repos__hooks_pre_commit(svn_repos_t * repos,apr_hash_t * hooks_env,const char * txn_name,apr_pool_t * pool)545 svn_repos__hooks_pre_commit(svn_repos_t *repos,
546 apr_hash_t *hooks_env,
547 const char *txn_name,
548 apr_pool_t *pool)
549 {
550 const char *hook = svn_repos_pre_commit_hook(repos, pool);
551 svn_boolean_t broken_link;
552
553 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
554 {
555 return hook_symlink_error(hook);
556 }
557 else if (hook)
558 {
559 const char *args[4];
560 svn_fs_access_t *access_ctx;
561 apr_file_t *stdin_handle = NULL;
562
563 args[0] = hook;
564 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
565 args[2] = txn_name;
566 args[3] = NULL;
567
568 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
569 if (access_ctx)
570 {
571 apr_hash_t *lock_tokens = svn_fs__access_get_lock_tokens(access_ctx);
572 if (apr_hash_count(lock_tokens)) {
573 SVN_ERR(lock_token_content(&stdin_handle, lock_tokens, pool));
574 }
575 }
576
577 if (!stdin_handle)
578 SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
579 APR_READ, APR_OS_DEFAULT, pool));
580
581 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_COMMIT, hook, args,
582 hooks_env, stdin_handle, pool));
583 }
584
585 return SVN_NO_ERROR;
586 }
587
588
589 svn_error_t *
svn_repos__hooks_post_commit(svn_repos_t * repos,apr_hash_t * hooks_env,svn_revnum_t rev,const char * txn_name,apr_pool_t * pool)590 svn_repos__hooks_post_commit(svn_repos_t *repos,
591 apr_hash_t *hooks_env,
592 svn_revnum_t rev,
593 const char *txn_name,
594 apr_pool_t *pool)
595 {
596 const char *hook = svn_repos_post_commit_hook(repos, pool);
597 svn_boolean_t broken_link;
598
599 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
600 {
601 return hook_symlink_error(hook);
602 }
603 else if (hook)
604 {
605 const char *args[5];
606
607 args[0] = hook;
608 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
609 args[2] = apr_psprintf(pool, "%ld", rev);
610 args[3] = txn_name;
611 args[4] = NULL;
612
613 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args,
614 hooks_env, NULL, pool));
615 }
616
617 return SVN_NO_ERROR;
618 }
619
620
621 svn_error_t *
svn_repos__hooks_pre_revprop_change(svn_repos_t * repos,apr_hash_t * hooks_env,svn_revnum_t rev,const char * author,const char * name,const svn_string_t * new_value,char action,apr_pool_t * pool)622 svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
623 apr_hash_t *hooks_env,
624 svn_revnum_t rev,
625 const char *author,
626 const char *name,
627 const svn_string_t *new_value,
628 char action,
629 apr_pool_t *pool)
630 {
631 const char *hook = svn_repos_pre_revprop_change_hook(repos, pool);
632 svn_boolean_t broken_link;
633
634 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
635 {
636 return hook_symlink_error(hook);
637 }
638 else if (hook)
639 {
640 const char *args[7];
641 apr_file_t *stdin_handle = NULL;
642 char action_string[2];
643
644 /* Pass the new value as stdin to hook */
645 if (new_value)
646 SVN_ERR(create_temp_file(&stdin_handle, new_value, pool));
647 else
648 SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
649 APR_READ, APR_OS_DEFAULT, pool));
650
651 action_string[0] = action;
652 action_string[1] = '\0';
653
654 args[0] = hook;
655 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
656 args[2] = apr_psprintf(pool, "%ld", rev);
657 args[3] = author ? author : "";
658 args[4] = name;
659 args[5] = action_string;
660 args[6] = NULL;
661
662 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook,
663 args, hooks_env, stdin_handle, pool));
664
665 SVN_ERR(svn_io_file_close(stdin_handle, pool));
666 }
667 else
668 {
669 /* If the pre- hook doesn't exist at all, then default to
670 MASSIVE PARANOIA. Changing revision properties is a lossy
671 operation; so unless the repository admininstrator has
672 *deliberately* created the pre-hook, disallow all changes. */
673 return
674 svn_error_create
675 (SVN_ERR_REPOS_DISABLED_FEATURE, NULL,
676 _("Repository has not been enabled to accept revision propchanges;\n"
677 "ask the administrator to create a pre-revprop-change hook"));
678 }
679
680 return SVN_NO_ERROR;
681 }
682
683
684 svn_error_t *
svn_repos__hooks_post_revprop_change(svn_repos_t * repos,apr_hash_t * hooks_env,svn_revnum_t rev,const char * author,const char * name,const svn_string_t * old_value,char action,apr_pool_t * pool)685 svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
686 apr_hash_t *hooks_env,
687 svn_revnum_t rev,
688 const char *author,
689 const char *name,
690 const svn_string_t *old_value,
691 char action,
692 apr_pool_t *pool)
693 {
694 const char *hook = svn_repos_post_revprop_change_hook(repos, pool);
695 svn_boolean_t broken_link;
696
697 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
698 {
699 return hook_symlink_error(hook);
700 }
701 else if (hook)
702 {
703 const char *args[7];
704 apr_file_t *stdin_handle = NULL;
705 char action_string[2];
706
707 /* Pass the old value as stdin to hook */
708 if (old_value)
709 SVN_ERR(create_temp_file(&stdin_handle, old_value, pool));
710 else
711 SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
712 APR_READ, APR_OS_DEFAULT, pool));
713
714 action_string[0] = action;
715 action_string[1] = '\0';
716
717 args[0] = hook;
718 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
719 args[2] = apr_psprintf(pool, "%ld", rev);
720 args[3] = author ? author : "";
721 args[4] = name;
722 args[5] = action_string;
723 args[6] = NULL;
724
725 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_REVPROP_CHANGE, hook,
726 args, hooks_env, stdin_handle, pool));
727
728 SVN_ERR(svn_io_file_close(stdin_handle, pool));
729 }
730
731 return SVN_NO_ERROR;
732 }
733
734
735 svn_error_t *
svn_repos__hooks_pre_lock(svn_repos_t * repos,apr_hash_t * hooks_env,const char ** token,const char * path,const char * username,const char * comment,svn_boolean_t steal_lock,apr_pool_t * pool)736 svn_repos__hooks_pre_lock(svn_repos_t *repos,
737 apr_hash_t *hooks_env,
738 const char **token,
739 const char *path,
740 const char *username,
741 const char *comment,
742 svn_boolean_t steal_lock,
743 apr_pool_t *pool)
744 {
745 const char *hook = svn_repos_pre_lock_hook(repos, pool);
746 svn_boolean_t broken_link;
747
748 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
749 {
750 return hook_symlink_error(hook);
751 }
752 else if (hook)
753 {
754 const char *args[7];
755 svn_string_t *buf;
756
757
758 args[0] = hook;
759 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
760 args[2] = path;
761 args[3] = username;
762 args[4] = comment ? comment : "";
763 args[5] = steal_lock ? "1" : "0";
764 args[6] = NULL;
765
766 SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args,
767 hooks_env, NULL, pool));
768
769 if (token)
770 /* No validation here; the FS will take care of that. */
771 *token = buf->data;
772
773 }
774 else if (token)
775 *token = "";
776
777 return SVN_NO_ERROR;
778 }
779
780
781 svn_error_t *
svn_repos__hooks_post_lock(svn_repos_t * repos,apr_hash_t * hooks_env,const apr_array_header_t * paths,const char * username,apr_pool_t * pool)782 svn_repos__hooks_post_lock(svn_repos_t *repos,
783 apr_hash_t *hooks_env,
784 const apr_array_header_t *paths,
785 const char *username,
786 apr_pool_t *pool)
787 {
788 const char *hook = svn_repos_post_lock_hook(repos, pool);
789 svn_boolean_t broken_link;
790
791 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
792 {
793 return hook_symlink_error(hook);
794 }
795 else if (hook)
796 {
797 const char *args[5];
798 apr_file_t *stdin_handle = NULL;
799 svn_string_t *paths_str = svn_string_create(svn_cstring_join2
800 (paths, "\n", TRUE, pool),
801 pool);
802
803 SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
804
805 args[0] = hook;
806 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
807 args[2] = username;
808 args[3] = NULL;
809 args[4] = NULL;
810
811 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_LOCK, hook, args,
812 hooks_env, stdin_handle, pool));
813
814 SVN_ERR(svn_io_file_close(stdin_handle, pool));
815 }
816
817 return SVN_NO_ERROR;
818 }
819
820
821 svn_error_t *
svn_repos__hooks_pre_unlock(svn_repos_t * repos,apr_hash_t * hooks_env,const char * path,const char * username,const char * token,svn_boolean_t break_lock,apr_pool_t * pool)822 svn_repos__hooks_pre_unlock(svn_repos_t *repos,
823 apr_hash_t *hooks_env,
824 const char *path,
825 const char *username,
826 const char *token,
827 svn_boolean_t break_lock,
828 apr_pool_t *pool)
829 {
830 const char *hook = svn_repos_pre_unlock_hook(repos, pool);
831 svn_boolean_t broken_link;
832
833 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
834 {
835 return hook_symlink_error(hook);
836 }
837 else if (hook)
838 {
839 const char *args[7];
840
841 args[0] = hook;
842 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
843 args[2] = path;
844 args[3] = username ? username : "";
845 args[4] = token ? token : "";
846 args[5] = break_lock ? "1" : "0";
847 args[6] = NULL;
848
849 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args,
850 hooks_env, NULL, pool));
851 }
852
853 return SVN_NO_ERROR;
854 }
855
856
857 svn_error_t *
svn_repos__hooks_post_unlock(svn_repos_t * repos,apr_hash_t * hooks_env,const apr_array_header_t * paths,const char * username,apr_pool_t * pool)858 svn_repos__hooks_post_unlock(svn_repos_t *repos,
859 apr_hash_t *hooks_env,
860 const apr_array_header_t *paths,
861 const char *username,
862 apr_pool_t *pool)
863 {
864 const char *hook = svn_repos_post_unlock_hook(repos, pool);
865 svn_boolean_t broken_link;
866
867 if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
868 {
869 return hook_symlink_error(hook);
870 }
871 else if (hook)
872 {
873 const char *args[5];
874 apr_file_t *stdin_handle = NULL;
875 svn_string_t *paths_str = svn_string_create(svn_cstring_join2
876 (paths, "\n", TRUE, pool),
877 pool);
878
879 SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
880
881 args[0] = hook;
882 args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
883 args[2] = username ? username : "";
884 args[3] = NULL;
885 args[4] = NULL;
886
887 SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_UNLOCK, hook, args,
888 hooks_env, stdin_handle, pool));
889
890 SVN_ERR(svn_io_file_close(stdin_handle, pool));
891 }
892
893 return SVN_NO_ERROR;
894 }
895
896
897
898 /*
899 * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq
900 * vim:isk=a-z,A-Z,48-57,_,.,-,>
901 * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0
902 */
903