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