]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_repos/hooks.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 *)apr_hash_this_key(hi),
188                            (const char *)apr_hash_this_val(hi));
189       envp++;
190     }
191   *envp = NULL;
192
193   return env;
194 }
195
196 /* NAME, CMD and ARGS are the name, path to and arguments for the hook
197    program that is to be run.  The hook's exit status will be checked,
198    and if an error occurred the hook's stderr output will be added to
199    the returned error.
200
201    If STDIN_HANDLE is non-null, pass it as the hook's stdin, else pass
202    no stdin to the hook.
203
204    If RESULT is non-null, set *RESULT to the stdout of the hook or to
205    a zero-length string if the hook generates no output on stdout. */
206 static svn_error_t *
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 accommodate (at least)
322      JavaScript (.js) and VB Script (.vbs) code, extensions for the
323      corresponding file types need not be enumerated explicitly. */
324     ".exe", ".cmd", ".bat", ".wsf", /* ### Any other extensions? */
325 #else
326     "",
327 #endif
328     NULL
329   };
330
331   const char *const *extn;
332   svn_error_t *err = NULL;
333   svn_boolean_t is_special;
334   for (extn = check_extns; *extn; ++extn)
335     {
336       const char *const hook_path =
337         (**extn ? apr_pstrcat(pool, hook, *extn, SVN_VA_NULL) : hook);
338
339       svn_node_kind_t kind;
340       if (!(err = svn_io_check_resolved_path(hook_path, &kind, pool))
341           && kind == svn_node_file)
342         {
343           *broken_link = FALSE;
344           return hook_path;
345         }
346       svn_error_clear(err);
347       if (!(err = svn_io_check_special_path(hook_path, &kind, &is_special,
348                                             pool))
349           && is_special)
350         {
351           *broken_link = TRUE;
352           return hook_path;
353         }
354       svn_error_clear(err);
355     }
356   return NULL;
357 }
358
359 /* Baton for parse_hooks_env_option. */
360 struct parse_hooks_env_option_baton {
361   /* The name of the section being parsed. If not the default section,
362    * the section name should match the name of a hook to which the
363    * options apply. */
364   const char *section;
365   apr_hash_t *hooks_env;
366 };
367
368 /* An implementation of svn_config_enumerator2_t.
369  * Set environment variable NAME to value VALUE in the environment for
370  * all hooks (in case the current section is the default section),
371  * or the hook with the name corresponding to the current section's name. */
372 static svn_boolean_t
373 parse_hooks_env_option(const char *name, const char *value,
374                        void *baton, apr_pool_t *pool)
375 {
376   struct parse_hooks_env_option_baton *bo = baton;
377   apr_pool_t *result_pool = apr_hash_pool_get(bo->hooks_env);
378   apr_hash_t *hook_env;
379
380   hook_env = svn_hash_gets(bo->hooks_env, bo->section);
381   if (hook_env == NULL)
382     {
383       hook_env = apr_hash_make(result_pool);
384       svn_hash_sets(bo->hooks_env, apr_pstrdup(result_pool, bo->section),
385                     hook_env);
386     }
387   svn_hash_sets(hook_env, apr_pstrdup(result_pool, name),
388                 apr_pstrdup(result_pool, value));
389
390   return TRUE;
391 }
392
393 struct parse_hooks_env_section_baton {
394   svn_config_t *cfg;
395   apr_hash_t *hooks_env;
396 };
397
398 /* An implementation of svn_config_section_enumerator2_t. */
399 static svn_boolean_t
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   struct parse_hooks_env_section_baton b;
420   if (local_abspath)
421     {
422       svn_node_kind_t kind;
423       SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
424
425       b.hooks_env = apr_hash_make(result_pool);
426
427       if (kind != svn_node_none)
428         {
429           svn_config_t *cfg;
430           SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE,
431                                   TRUE, TRUE, scratch_pool));
432           b.cfg = cfg;
433
434           (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section,
435                                                &b, scratch_pool);
436         }
437
438       *hooks_env_p = b.hooks_env;
439     }
440   else
441     {
442       *hooks_env_p = NULL;
443     }
444
445   return SVN_NO_ERROR;
446 }
447
448 /* Return an error for the failure of HOOK due to a broken symlink. */
449 static svn_error_t *
450 hook_symlink_error(const char *hook)
451 {
452   return svn_error_createf
453     (SVN_ERR_REPOS_HOOK_FAILURE, NULL,
454      _("Failed to run '%s' hook; broken symlink"), hook);
455 }
456
457 svn_error_t *
458 svn_repos__hooks_start_commit(svn_repos_t *repos,
459                               apr_hash_t *hooks_env,
460                               const char *user,
461                               const apr_array_header_t *capabilities,
462                               const char *txn_name,
463                               apr_pool_t *pool)
464 {
465   const char *hook = svn_repos_start_commit_hook(repos, pool);
466   svn_boolean_t broken_link;
467
468   if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
469     {
470       return hook_symlink_error(hook);
471     }
472   else if (hook)
473     {
474       const char *args[6];
475       char *capabilities_string;
476
477       if (capabilities)
478         {
479           capabilities_string = svn_cstring_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. */
512 static svn_error_t *
513 lock_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
547 svn_error_t  *
548 svn_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
592 svn_error_t  *
593 svn_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
624 svn_error_t  *
625 svn_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
687 svn_error_t  *
688 svn_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
738 svn_error_t  *
739 svn_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
784 svn_error_t  *
785 svn_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
824 svn_error_t  *
825 svn_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
860 svn_error_t  *
861 svn_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 \f
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  */