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