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