]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_repos/hooks.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_repos / hooks.c
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 \f
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 *
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 **
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 *)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. */
206 static svn_error_t *
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 *
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*
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 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. */
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 } 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. */
372 static svn_boolean_t
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 } parse_hooks_env_section_baton;
397
398 /* An implementation of svn_config_section_enumerator2_t. */
399 static svn_boolean_t
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 *
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   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. */
441 static svn_error_t *
442 hook_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
449 svn_error_t *
450 svn_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. */
504 static svn_error_t *
505 lock_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
532 svn_error_t  *
533 svn_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
577 svn_error_t  *
578 svn_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
609 svn_error_t  *
610 svn_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
672 svn_error_t  *
673 svn_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
723 svn_error_t  *
724 svn_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
769 svn_error_t  *
770 svn_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
809 svn_error_t  *
810 svn_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
845 svn_error_t  *
846 svn_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 \f
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  */