1251881Speter/* hooks.c : running repository hooks
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter#include <stdio.h>
24251881Speter#include <string.h>
25251881Speter#include <ctype.h>
26251881Speter
27251881Speter#include <apr_pools.h>
28251881Speter#include <apr_file_io.h>
29251881Speter
30251881Speter#include "svn_config.h"
31251881Speter#include "svn_hash.h"
32251881Speter#include "svn_error.h"
33251881Speter#include "svn_dirent_uri.h"
34251881Speter#include "svn_path.h"
35251881Speter#include "svn_pools.h"
36251881Speter#include "svn_repos.h"
37251881Speter#include "svn_utf.h"
38251881Speter#include "repos.h"
39251881Speter#include "svn_private_config.h"
40251881Speter#include "private/svn_fs_private.h"
41251881Speter#include "private/svn_repos_private.h"
42251881Speter#include "private/svn_string_private.h"
43251881Speter
44251881Speter
45251881Speter
46251881Speter/*** Hook drivers. ***/
47251881Speter
48251881Speter/* Helper function for run_hook_cmd().  Wait for a hook to finish
49251881Speter   executing and return either SVN_NO_ERROR if the hook script completed
50251881Speter   without error, or an error describing the reason for failure.
51251881Speter
52251881Speter   NAME and CMD are the name and path of the hook program, CMD_PROC
53251881Speter   is a pointer to the structure representing the running process,
54251881Speter   and READ_ERRHANDLE is an open handle to the hook's stderr.
55251881Speter
56251881Speter   Hooks are considered to have failed if we are unable to wait for the
57251881Speter   process, if we are unable to read from the hook's stderr, if the
58251881Speter   process has failed to exit cleanly (due to a coredump, for example),
59251881Speter   or if the process returned a non-zero return code.
60251881Speter
61251881Speter   Any error output returned by the hook's stderr will be included in an
62251881Speter   error message, though the presence of output on stderr is not itself
63251881Speter   a reason to fail a hook. */
64251881Speterstatic svn_error_t *
65251881Spetercheck_hook_result(const char *name, const char *cmd, apr_proc_t *cmd_proc,
66251881Speter                  apr_file_t *read_errhandle, apr_pool_t *pool)
67251881Speter{
68251881Speter  svn_error_t *err, *err2;
69251881Speter  svn_stringbuf_t *native_stderr, *failure_message;
70251881Speter  const char *utf8_stderr;
71251881Speter  int exitcode;
72251881Speter  apr_exit_why_e exitwhy;
73251881Speter
74251881Speter  err2 = svn_stringbuf_from_aprfile(&native_stderr, read_errhandle, pool);
75251881Speter
76251881Speter  err = svn_io_wait_for_cmd(cmd_proc, cmd, &exitcode, &exitwhy, pool);
77251881Speter  if (err)
78251881Speter    {
79251881Speter      svn_error_clear(err2);
80251881Speter      return svn_error_trace(err);
81251881Speter    }
82251881Speter
83251881Speter  if (APR_PROC_CHECK_EXIT(exitwhy) && exitcode == 0)
84251881Speter    {
85251881Speter      /* The hook exited cleanly.  However, if we got an error reading
86251881Speter         the hook's stderr, fail the hook anyway, because this might be
87251881Speter         symptomatic of a more important problem. */
88251881Speter      if (err2)
89251881Speter        {
90251881Speter          return svn_error_createf
91251881Speter            (SVN_ERR_REPOS_HOOK_FAILURE, err2,
92251881Speter             _("'%s' hook succeeded, but error output could not be read"),
93251881Speter             name);
94251881Speter        }
95251881Speter
96251881Speter      return SVN_NO_ERROR;
97251881Speter    }
98251881Speter
99251881Speter  /* The hook script failed. */
100251881Speter
101251881Speter  /* If we got the stderr output okay, try to translate it into UTF-8.
102251881Speter     Ensure there is something sensible in the UTF-8 string regardless. */
103251881Speter  if (!err2)
104251881Speter    {
105251881Speter      err2 = svn_utf_cstring_to_utf8(&utf8_stderr, native_stderr->data, pool);
106251881Speter      if (err2)
107251881Speter        utf8_stderr = _("[Error output could not be translated from the "
108251881Speter                        "native locale to UTF-8.]");
109251881Speter    }
110251881Speter  else
111251881Speter    {
112251881Speter      utf8_stderr = _("[Error output could not be read.]");
113251881Speter    }
114251881Speter  /*### It would be nice to include the text of any translation or read
115251881Speter        error in the messages above before we clear it here. */
116251881Speter  svn_error_clear(err2);
117251881Speter
118251881Speter  if (!APR_PROC_CHECK_EXIT(exitwhy))
119251881Speter    {
120251881Speter      failure_message = svn_stringbuf_createf(pool,
121251881Speter        _("'%s' hook failed (did not exit cleanly: "
122251881Speter          "apr_exit_why_e was %d, exitcode was %d).  "),
123251881Speter        name, exitwhy, exitcode);
124251881Speter    }
125251881Speter  else
126251881Speter    {
127251881Speter      const char *action;
128251881Speter      if (strcmp(name, "start-commit") == 0
129251881Speter          || strcmp(name, "pre-commit") == 0)
130251881Speter        action = _("Commit");
131251881Speter      else if (strcmp(name, "pre-revprop-change") == 0)
132251881Speter        action = _("Revprop change");
133251881Speter      else if (strcmp(name, "pre-lock") == 0)
134251881Speter        action = _("Lock");
135251881Speter      else if (strcmp(name, "pre-unlock") == 0)
136251881Speter        action = _("Unlock");
137251881Speter      else
138251881Speter        action = NULL;
139251881Speter      if (action == NULL)
140251881Speter        failure_message = svn_stringbuf_createf(
141251881Speter            pool, _("%s hook failed (exit code %d)"),
142251881Speter            name, exitcode);
143251881Speter      else
144251881Speter        failure_message = svn_stringbuf_createf(
145251881Speter            pool, _("%s blocked by %s hook (exit code %d)"),
146251881Speter            action, name, exitcode);
147251881Speter    }
148251881Speter
149251881Speter  if (utf8_stderr[0])
150251881Speter    {
151251881Speter      svn_stringbuf_appendcstr(failure_message,
152251881Speter                               _(" with output:\n"));
153251881Speter      svn_stringbuf_appendcstr(failure_message, utf8_stderr);
154251881Speter    }
155251881Speter  else
156251881Speter    {
157251881Speter      svn_stringbuf_appendcstr(failure_message,
158251881Speter                               _(" with no output."));
159251881Speter    }
160251881Speter
161251881Speter  return svn_error_create(SVN_ERR_REPOS_HOOK_FAILURE, err,
162251881Speter                          failure_message->data);
163251881Speter}
164251881Speter
165251881Speter/* Copy the environment given as key/value pairs of ENV_HASH into
166251881Speter * an array of C strings allocated in RESULT_POOL.
167251881Speter * If the hook environment is empty, return NULL.
168251881Speter * Use SCRATCH_POOL for temporary allocations. */
169251881Speterstatic const char **
170251881Speterenv_from_env_hash(apr_hash_t *env_hash,
171251881Speter                  apr_pool_t *result_pool,
172251881Speter                  apr_pool_t *scratch_pool)
173251881Speter{
174251881Speter  apr_hash_index_t *hi;
175251881Speter  const char **env;
176251881Speter  const char **envp;
177251881Speter
178251881Speter  if (!env_hash)
179251881Speter    return NULL;
180251881Speter
181251881Speter  env = apr_palloc(result_pool,
182251881Speter                   sizeof(const char *) * (apr_hash_count(env_hash) + 1));
183251881Speter  envp = env;
184251881Speter  for (hi = apr_hash_first(scratch_pool, env_hash); hi; hi = apr_hash_next(hi))
185251881Speter    {
186251881Speter      *envp = apr_psprintf(result_pool, "%s=%s",
187299742Sdim                           (const char *)apr_hash_this_key(hi),
188299742Sdim                           (const char *)apr_hash_this_val(hi));
189251881Speter      envp++;
190251881Speter    }
191251881Speter  *envp = NULL;
192251881Speter
193251881Speter  return env;
194251881Speter}
195251881Speter
196251881Speter/* NAME, CMD and ARGS are the name, path to and arguments for the hook
197251881Speter   program that is to be run.  The hook's exit status will be checked,
198251881Speter   and if an error occurred the hook's stderr output will be added to
199251881Speter   the returned error.
200251881Speter
201251881Speter   If STDIN_HANDLE is non-null, pass it as the hook's stdin, else pass
202251881Speter   no stdin to the hook.
203251881Speter
204251881Speter   If RESULT is non-null, set *RESULT to the stdout of the hook or to
205251881Speter   a zero-length string if the hook generates no output on stdout. */
206251881Speterstatic svn_error_t *
207251881Speterrun_hook_cmd(svn_string_t **result,
208251881Speter             const char *name,
209251881Speter             const char *cmd,
210251881Speter             const char **args,
211251881Speter             apr_hash_t *hooks_env,
212251881Speter             apr_file_t *stdin_handle,
213251881Speter             apr_pool_t *pool)
214251881Speter{
215251881Speter  apr_file_t *null_handle;
216251881Speter  apr_status_t apr_err;
217251881Speter  svn_error_t *err;
218251881Speter  apr_proc_t cmd_proc = {0};
219251881Speter  apr_pool_t *cmd_pool;
220251881Speter  apr_hash_t *hook_env = NULL;
221251881Speter
222251881Speter  if (result)
223251881Speter    {
224251881Speter      null_handle = NULL;
225251881Speter    }
226251881Speter  else
227251881Speter    {
228251881Speter      /* Redirect stdout to the null device */
229251881Speter        apr_err = apr_file_open(&null_handle, SVN_NULL_DEVICE_NAME, APR_WRITE,
230251881Speter                                APR_OS_DEFAULT, pool);
231251881Speter        if (apr_err)
232251881Speter          return svn_error_wrap_apr
233251881Speter            (apr_err, _("Can't create null stdout for hook '%s'"), cmd);
234251881Speter    }
235251881Speter
236251881Speter  /* Tie resources allocated for the command to a special pool which we can
237251881Speter   * destroy in order to clean up the stderr pipe opened for the process. */
238251881Speter  cmd_pool = svn_pool_create(pool);
239251881Speter
240251881Speter  /* Check if a custom environment is defined for this hook, or else
241251881Speter   * whether a default environment is defined. */
242251881Speter  if (hooks_env)
243251881Speter    {
244251881Speter      hook_env = svn_hash_gets(hooks_env, name);
245251881Speter      if (hook_env == NULL)
246251881Speter        hook_env = svn_hash_gets(hooks_env,
247251881Speter                                 SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION);
248251881Speter    }
249251881Speter
250251881Speter  err = svn_io_start_cmd3(&cmd_proc, ".", cmd, args,
251251881Speter                          env_from_env_hash(hook_env, pool, pool),
252251881Speter                          FALSE, FALSE, stdin_handle, result != NULL,
253251881Speter                          null_handle, TRUE, NULL, cmd_pool);
254251881Speter  if (!err)
255251881Speter    err = check_hook_result(name, cmd, &cmd_proc, cmd_proc.err, pool);
256251881Speter  else
257251881Speter    {
258251881Speter      /* The command could not be started for some reason. */
259251881Speter      err = svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, err,
260251881Speter                              _("Failed to start '%s' hook"), cmd);
261251881Speter    }
262251881Speter
263251881Speter  /* Hooks are fallible, and so hook failure is "expected" to occur at
264251881Speter     times.  When such a failure happens we still want to close the pipe
265251881Speter     and null file */
266251881Speter  if (!err && result)
267251881Speter    {
268251881Speter      svn_stringbuf_t *native_stdout;
269251881Speter      err = svn_stringbuf_from_aprfile(&native_stdout, cmd_proc.out, pool);
270251881Speter      if (!err)
271251881Speter        *result = svn_stringbuf__morph_into_string(native_stdout);
272251881Speter    }
273251881Speter
274251881Speter  /* Close resources allocated by svn_io_start_cmd3(), such as the pipe. */
275251881Speter  svn_pool_destroy(cmd_pool);
276251881Speter
277251881Speter  /* Close the null handle. */
278251881Speter  if (null_handle)
279251881Speter    {
280251881Speter      apr_err = apr_file_close(null_handle);
281251881Speter      if (!err && apr_err)
282251881Speter        return svn_error_wrap_apr(apr_err, _("Error closing null file"));
283251881Speter    }
284251881Speter
285251881Speter  return svn_error_trace(err);
286251881Speter}
287251881Speter
288251881Speter
289251881Speter/* Create a temporary file F that will automatically be deleted when the
290251881Speter   pool is cleaned up.  Fill it with VALUE, and leave it open and rewound,
291251881Speter   ready to be read from. */
292251881Speterstatic svn_error_t *
293251881Spetercreate_temp_file(apr_file_t **f, const svn_string_t *value, apr_pool_t *pool)
294251881Speter{
295251881Speter  apr_off_t offset = 0;
296251881Speter
297251881Speter  SVN_ERR(svn_io_open_unique_file3(f, NULL, NULL,
298251881Speter                                   svn_io_file_del_on_pool_cleanup,
299251881Speter                                   pool, pool));
300251881Speter  SVN_ERR(svn_io_file_write_full(*f, value->data, value->len, NULL, pool));
301251881Speter  return svn_io_file_seek(*f, APR_SET, &offset, pool);
302251881Speter}
303251881Speter
304251881Speter
305251881Speter/* Check if the HOOK program exists and is a file or a symbolic link, using
306251881Speter   POOL for temporary allocations.
307251881Speter
308251881Speter   If the hook exists but is a broken symbolic link, set *BROKEN_LINK
309251881Speter   to TRUE, else if the hook program exists set *BROKEN_LINK to FALSE.
310251881Speter
311251881Speter   Return the hook program if found, else return NULL and don't touch
312251881Speter   *BROKEN_LINK.
313251881Speter*/
314251881Speterstatic const char*
315251881Spetercheck_hook_cmd(const char *hook, svn_boolean_t *broken_link, apr_pool_t *pool)
316251881Speter{
317251881Speter  static const char* const check_extns[] = {
318251881Speter#ifdef WIN32
319251881Speter  /* For WIN32, we need to check with file name extension(s) added.
320251881Speter
321299742Sdim     As Windows Scripting Host (.wsf) files can accommodate (at least)
322251881Speter     JavaScript (.js) and VB Script (.vbs) code, extensions for the
323251881Speter     corresponding file types need not be enumerated explicitly. */
324251881Speter    ".exe", ".cmd", ".bat", ".wsf", /* ### Any other extensions? */
325251881Speter#else
326251881Speter    "",
327251881Speter#endif
328251881Speter    NULL
329251881Speter  };
330251881Speter
331251881Speter  const char *const *extn;
332251881Speter  svn_error_t *err = NULL;
333251881Speter  svn_boolean_t is_special;
334251881Speter  for (extn = check_extns; *extn; ++extn)
335251881Speter    {
336251881Speter      const char *const hook_path =
337299742Sdim        (**extn ? apr_pstrcat(pool, hook, *extn, SVN_VA_NULL) : hook);
338251881Speter
339251881Speter      svn_node_kind_t kind;
340251881Speter      if (!(err = svn_io_check_resolved_path(hook_path, &kind, pool))
341251881Speter          && kind == svn_node_file)
342251881Speter        {
343251881Speter          *broken_link = FALSE;
344251881Speter          return hook_path;
345251881Speter        }
346251881Speter      svn_error_clear(err);
347251881Speter      if (!(err = svn_io_check_special_path(hook_path, &kind, &is_special,
348251881Speter                                            pool))
349251881Speter          && is_special)
350251881Speter        {
351251881Speter          *broken_link = TRUE;
352251881Speter          return hook_path;
353251881Speter        }
354251881Speter      svn_error_clear(err);
355251881Speter    }
356251881Speter  return NULL;
357251881Speter}
358251881Speter
359251881Speter/* Baton for parse_hooks_env_option. */
360251881Speterstruct parse_hooks_env_option_baton {
361251881Speter  /* The name of the section being parsed. If not the default section,
362251881Speter   * the section name should match the name of a hook to which the
363251881Speter   * options apply. */
364251881Speter  const char *section;
365251881Speter  apr_hash_t *hooks_env;
366299742Sdim};
367251881Speter
368251881Speter/* An implementation of svn_config_enumerator2_t.
369251881Speter * Set environment variable NAME to value VALUE in the environment for
370251881Speter * all hooks (in case the current section is the default section),
371251881Speter * or the hook with the name corresponding to the current section's name. */
372251881Speterstatic svn_boolean_t
373251881Speterparse_hooks_env_option(const char *name, const char *value,
374251881Speter                       void *baton, apr_pool_t *pool)
375251881Speter{
376251881Speter  struct parse_hooks_env_option_baton *bo = baton;
377251881Speter  apr_pool_t *result_pool = apr_hash_pool_get(bo->hooks_env);
378251881Speter  apr_hash_t *hook_env;
379251881Speter
380251881Speter  hook_env = svn_hash_gets(bo->hooks_env, bo->section);
381251881Speter  if (hook_env == NULL)
382251881Speter    {
383251881Speter      hook_env = apr_hash_make(result_pool);
384251881Speter      svn_hash_sets(bo->hooks_env, apr_pstrdup(result_pool, bo->section),
385251881Speter                    hook_env);
386251881Speter    }
387251881Speter  svn_hash_sets(hook_env, apr_pstrdup(result_pool, name),
388251881Speter                apr_pstrdup(result_pool, value));
389251881Speter
390251881Speter  return TRUE;
391251881Speter}
392251881Speter
393251881Speterstruct parse_hooks_env_section_baton {
394251881Speter  svn_config_t *cfg;
395251881Speter  apr_hash_t *hooks_env;
396299742Sdim};
397251881Speter
398251881Speter/* An implementation of svn_config_section_enumerator2_t. */
399251881Speterstatic svn_boolean_t
400251881Speterparse_hooks_env_section(const char *name, void *baton, apr_pool_t *pool)
401251881Speter{
402251881Speter  struct parse_hooks_env_section_baton *b = baton;
403251881Speter  struct parse_hooks_env_option_baton bo;
404251881Speter
405251881Speter  bo.section = name;
406251881Speter  bo.hooks_env = b->hooks_env;
407251881Speter
408251881Speter  (void)svn_config_enumerate2(b->cfg, name, parse_hooks_env_option, &bo, pool);
409251881Speter
410251881Speter  return TRUE;
411251881Speter}
412251881Speter
413251881Spetersvn_error_t *
414251881Spetersvn_repos__parse_hooks_env(apr_hash_t **hooks_env_p,
415251881Speter                           const char *local_abspath,
416251881Speter                           apr_pool_t *result_pool,
417251881Speter                           apr_pool_t *scratch_pool)
418251881Speter{
419251881Speter  struct parse_hooks_env_section_baton b;
420251881Speter  if (local_abspath)
421251881Speter    {
422299742Sdim      svn_node_kind_t kind;
423299742Sdim      SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
424299742Sdim
425251881Speter      b.hooks_env = apr_hash_make(result_pool);
426299742Sdim
427299742Sdim      if (kind != svn_node_none)
428299742Sdim        {
429299742Sdim          svn_config_t *cfg;
430299742Sdim          SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE,
431299742Sdim                                  TRUE, TRUE, scratch_pool));
432299742Sdim          b.cfg = cfg;
433299742Sdim
434299742Sdim          (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section,
435299742Sdim                                               &b, scratch_pool);
436299742Sdim        }
437299742Sdim
438251881Speter      *hooks_env_p = b.hooks_env;
439251881Speter    }
440251881Speter  else
441251881Speter    {
442251881Speter      *hooks_env_p = NULL;
443251881Speter    }
444251881Speter
445251881Speter  return SVN_NO_ERROR;
446251881Speter}
447251881Speter
448251881Speter/* Return an error for the failure of HOOK due to a broken symlink. */
449251881Speterstatic svn_error_t *
450251881Speterhook_symlink_error(const char *hook)
451251881Speter{
452251881Speter  return svn_error_createf
453251881Speter    (SVN_ERR_REPOS_HOOK_FAILURE, NULL,
454251881Speter     _("Failed to run '%s' hook; broken symlink"), hook);
455251881Speter}
456251881Speter
457251881Spetersvn_error_t *
458251881Spetersvn_repos__hooks_start_commit(svn_repos_t *repos,
459251881Speter                              apr_hash_t *hooks_env,
460251881Speter                              const char *user,
461251881Speter                              const apr_array_header_t *capabilities,
462251881Speter                              const char *txn_name,
463251881Speter                              apr_pool_t *pool)
464251881Speter{
465251881Speter  const char *hook = svn_repos_start_commit_hook(repos, pool);
466251881Speter  svn_boolean_t broken_link;
467251881Speter
468251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
469251881Speter    {
470251881Speter      return hook_symlink_error(hook);
471251881Speter    }
472251881Speter  else if (hook)
473251881Speter    {
474251881Speter      const char *args[6];
475251881Speter      char *capabilities_string;
476251881Speter
477251881Speter      if (capabilities)
478251881Speter        {
479251881Speter          capabilities_string = svn_cstring_join(capabilities, ":", pool);
480251881Speter
481251881Speter          /* Get rid of that annoying final colon. */
482251881Speter          if (capabilities_string[0])
483251881Speter            capabilities_string[strlen(capabilities_string) - 1] = '\0';
484251881Speter        }
485251881Speter      else
486251881Speter        {
487251881Speter          capabilities_string = apr_pstrdup(pool, "");
488251881Speter        }
489251881Speter
490251881Speter      args[0] = hook;
491251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
492251881Speter      args[2] = user ? user : "";
493251881Speter      args[3] = capabilities_string;
494251881Speter      args[4] = txn_name;
495251881Speter      args[5] = NULL;
496251881Speter
497251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args,
498251881Speter                           hooks_env, NULL, pool));
499251881Speter    }
500251881Speter
501251881Speter  return SVN_NO_ERROR;
502251881Speter}
503251881Speter
504251881Speter/* Set *HANDLE to an open filehandle for a temporary file (i.e.,
505251881Speter   automatically deleted when closed), into which the LOCK_TOKENS have
506251881Speter   been written out in the format described in the pre-commit hook
507251881Speter   template.
508251881Speter
509251881Speter   LOCK_TOKENS is as returned by svn_fs__access_get_lock_tokens().
510251881Speter
511251881Speter   Allocate *HANDLE in POOL, and use POOL for temporary allocations. */
512251881Speterstatic svn_error_t *
513251881Speterlock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens,
514251881Speter                   apr_pool_t *pool)
515251881Speter{
516251881Speter  svn_stringbuf_t *lock_str = svn_stringbuf_create("LOCK-TOKENS:\n", pool);
517251881Speter  apr_hash_index_t *hi;
518251881Speter
519251881Speter  for (hi = apr_hash_first(pool, lock_tokens); hi;
520251881Speter       hi = apr_hash_next(hi))
521251881Speter    {
522299742Sdim      const char *token = apr_hash_this_key(hi);
523299742Sdim      const char *path = apr_hash_this_val(hi);
524251881Speter
525299742Sdim      if (path == (const char *) 1)
526299742Sdim        {
527299742Sdim          /* Special handling for svn_fs_access_t * created by using deprecated
528299742Sdim             svn_fs_access_add_lock_token() function. */
529299742Sdim          path = "";
530299742Sdim        }
531299742Sdim      else
532299742Sdim        {
533299742Sdim          path = svn_path_uri_autoescape(path, pool);
534299742Sdim        }
535299742Sdim
536251881Speter      svn_stringbuf_appendstr(lock_str,
537299742Sdim          svn_stringbuf_createf(pool, "%s|%s\n", path, token));
538251881Speter    }
539251881Speter
540251881Speter  svn_stringbuf_appendcstr(lock_str, "\n");
541251881Speter  return create_temp_file(handle,
542251881Speter                          svn_stringbuf__morph_into_string(lock_str), pool);
543251881Speter}
544251881Speter
545251881Speter
546251881Speter
547251881Spetersvn_error_t  *
548251881Spetersvn_repos__hooks_pre_commit(svn_repos_t *repos,
549251881Speter                            apr_hash_t *hooks_env,
550251881Speter                            const char *txn_name,
551251881Speter                            apr_pool_t *pool)
552251881Speter{
553251881Speter  const char *hook = svn_repos_pre_commit_hook(repos, pool);
554251881Speter  svn_boolean_t broken_link;
555251881Speter
556251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
557251881Speter    {
558251881Speter      return hook_symlink_error(hook);
559251881Speter    }
560251881Speter  else if (hook)
561251881Speter    {
562251881Speter      const char *args[4];
563251881Speter      svn_fs_access_t *access_ctx;
564251881Speter      apr_file_t *stdin_handle = NULL;
565251881Speter
566251881Speter      args[0] = hook;
567251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
568251881Speter      args[2] = txn_name;
569251881Speter      args[3] = NULL;
570251881Speter
571251881Speter      SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
572251881Speter      if (access_ctx)
573251881Speter        {
574251881Speter          apr_hash_t *lock_tokens = svn_fs__access_get_lock_tokens(access_ctx);
575251881Speter          if (apr_hash_count(lock_tokens))  {
576251881Speter            SVN_ERR(lock_token_content(&stdin_handle, lock_tokens, pool));
577251881Speter          }
578251881Speter        }
579251881Speter
580251881Speter      if (!stdin_handle)
581251881Speter        SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
582251881Speter                                 APR_READ, APR_OS_DEFAULT, pool));
583251881Speter
584251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_COMMIT, hook, args,
585251881Speter                           hooks_env, stdin_handle, pool));
586251881Speter    }
587251881Speter
588251881Speter  return SVN_NO_ERROR;
589251881Speter}
590251881Speter
591251881Speter
592251881Spetersvn_error_t  *
593251881Spetersvn_repos__hooks_post_commit(svn_repos_t *repos,
594251881Speter                             apr_hash_t *hooks_env,
595251881Speter                             svn_revnum_t rev,
596251881Speter                             const char *txn_name,
597251881Speter                             apr_pool_t *pool)
598251881Speter{
599251881Speter  const char *hook = svn_repos_post_commit_hook(repos, pool);
600251881Speter  svn_boolean_t broken_link;
601251881Speter
602251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
603251881Speter    {
604251881Speter      return hook_symlink_error(hook);
605251881Speter    }
606251881Speter  else if (hook)
607251881Speter    {
608251881Speter      const char *args[5];
609251881Speter
610251881Speter      args[0] = hook;
611251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
612251881Speter      args[2] = apr_psprintf(pool, "%ld", rev);
613251881Speter      args[3] = txn_name;
614251881Speter      args[4] = NULL;
615251881Speter
616251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args,
617251881Speter                           hooks_env, NULL, pool));
618251881Speter    }
619251881Speter
620251881Speter  return SVN_NO_ERROR;
621251881Speter}
622251881Speter
623251881Speter
624251881Spetersvn_error_t  *
625251881Spetersvn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
626251881Speter                                    apr_hash_t *hooks_env,
627251881Speter                                    svn_revnum_t rev,
628251881Speter                                    const char *author,
629251881Speter                                    const char *name,
630251881Speter                                    const svn_string_t *new_value,
631251881Speter                                    char action,
632251881Speter                                    apr_pool_t *pool)
633251881Speter{
634251881Speter  const char *hook = svn_repos_pre_revprop_change_hook(repos, pool);
635251881Speter  svn_boolean_t broken_link;
636251881Speter
637251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
638251881Speter    {
639251881Speter      return hook_symlink_error(hook);
640251881Speter    }
641251881Speter  else if (hook)
642251881Speter    {
643251881Speter      const char *args[7];
644251881Speter      apr_file_t *stdin_handle = NULL;
645251881Speter      char action_string[2];
646251881Speter
647251881Speter      /* Pass the new value as stdin to hook */
648251881Speter      if (new_value)
649251881Speter        SVN_ERR(create_temp_file(&stdin_handle, new_value, pool));
650251881Speter      else
651251881Speter        SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
652251881Speter                                 APR_READ, APR_OS_DEFAULT, pool));
653251881Speter
654251881Speter      action_string[0] = action;
655251881Speter      action_string[1] = '\0';
656251881Speter
657251881Speter      args[0] = hook;
658251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
659251881Speter      args[2] = apr_psprintf(pool, "%ld", rev);
660251881Speter      args[3] = author ? author : "";
661251881Speter      args[4] = name;
662251881Speter      args[5] = action_string;
663251881Speter      args[6] = NULL;
664251881Speter
665251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook,
666251881Speter                           args, hooks_env, stdin_handle, pool));
667251881Speter
668251881Speter      SVN_ERR(svn_io_file_close(stdin_handle, pool));
669251881Speter    }
670251881Speter  else
671251881Speter    {
672251881Speter      /* If the pre- hook doesn't exist at all, then default to
673251881Speter         MASSIVE PARANOIA.  Changing revision properties is a lossy
674251881Speter         operation; so unless the repository admininstrator has
675251881Speter         *deliberately* created the pre-hook, disallow all changes. */
676251881Speter      return
677251881Speter        svn_error_create
678251881Speter        (SVN_ERR_REPOS_DISABLED_FEATURE, NULL,
679251881Speter         _("Repository has not been enabled to accept revision propchanges;\n"
680251881Speter           "ask the administrator to create a pre-revprop-change hook"));
681251881Speter    }
682251881Speter
683251881Speter  return SVN_NO_ERROR;
684251881Speter}
685251881Speter
686251881Speter
687251881Spetersvn_error_t  *
688251881Spetersvn_repos__hooks_post_revprop_change(svn_repos_t *repos,
689251881Speter                                     apr_hash_t *hooks_env,
690251881Speter                                     svn_revnum_t rev,
691251881Speter                                     const char *author,
692251881Speter                                     const char *name,
693251881Speter                                     const svn_string_t *old_value,
694251881Speter                                     char action,
695251881Speter                                     apr_pool_t *pool)
696251881Speter{
697251881Speter  const char *hook = svn_repos_post_revprop_change_hook(repos, pool);
698251881Speter  svn_boolean_t broken_link;
699251881Speter
700251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
701251881Speter    {
702251881Speter      return hook_symlink_error(hook);
703251881Speter    }
704251881Speter  else if (hook)
705251881Speter    {
706251881Speter      const char *args[7];
707251881Speter      apr_file_t *stdin_handle = NULL;
708251881Speter      char action_string[2];
709251881Speter
710251881Speter      /* Pass the old value as stdin to hook */
711251881Speter      if (old_value)
712251881Speter        SVN_ERR(create_temp_file(&stdin_handle, old_value, pool));
713251881Speter      else
714251881Speter        SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
715251881Speter                                 APR_READ, APR_OS_DEFAULT, pool));
716251881Speter
717251881Speter      action_string[0] = action;
718251881Speter      action_string[1] = '\0';
719251881Speter
720251881Speter      args[0] = hook;
721251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
722251881Speter      args[2] = apr_psprintf(pool, "%ld", rev);
723251881Speter      args[3] = author ? author : "";
724251881Speter      args[4] = name;
725251881Speter      args[5] = action_string;
726251881Speter      args[6] = NULL;
727251881Speter
728251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_REVPROP_CHANGE, hook,
729251881Speter                           args, hooks_env, stdin_handle, pool));
730251881Speter
731251881Speter      SVN_ERR(svn_io_file_close(stdin_handle, pool));
732251881Speter    }
733251881Speter
734251881Speter  return SVN_NO_ERROR;
735251881Speter}
736251881Speter
737251881Speter
738251881Spetersvn_error_t  *
739251881Spetersvn_repos__hooks_pre_lock(svn_repos_t *repos,
740251881Speter                          apr_hash_t *hooks_env,
741251881Speter                          const char **token,
742251881Speter                          const char *path,
743251881Speter                          const char *username,
744251881Speter                          const char *comment,
745251881Speter                          svn_boolean_t steal_lock,
746251881Speter                          apr_pool_t *pool)
747251881Speter{
748251881Speter  const char *hook = svn_repos_pre_lock_hook(repos, pool);
749251881Speter  svn_boolean_t broken_link;
750251881Speter
751251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
752251881Speter    {
753251881Speter      return hook_symlink_error(hook);
754251881Speter    }
755251881Speter  else if (hook)
756251881Speter    {
757251881Speter      const char *args[7];
758251881Speter      svn_string_t *buf;
759251881Speter
760251881Speter
761251881Speter      args[0] = hook;
762251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
763251881Speter      args[2] = path;
764251881Speter      args[3] = username;
765251881Speter      args[4] = comment ? comment : "";
766251881Speter      args[5] = steal_lock ? "1" : "0";
767251881Speter      args[6] = NULL;
768251881Speter
769251881Speter      SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args,
770251881Speter                           hooks_env, NULL, pool));
771251881Speter
772251881Speter      if (token)
773251881Speter        /* No validation here; the FS will take care of that. */
774251881Speter        *token = buf->data;
775251881Speter
776251881Speter    }
777251881Speter  else if (token)
778251881Speter    *token = "";
779251881Speter
780251881Speter  return SVN_NO_ERROR;
781251881Speter}
782251881Speter
783251881Speter
784251881Spetersvn_error_t  *
785251881Spetersvn_repos__hooks_post_lock(svn_repos_t *repos,
786251881Speter                           apr_hash_t *hooks_env,
787251881Speter                           const apr_array_header_t *paths,
788251881Speter                           const char *username,
789251881Speter                           apr_pool_t *pool)
790251881Speter{
791251881Speter  const char *hook = svn_repos_post_lock_hook(repos, pool);
792251881Speter  svn_boolean_t broken_link;
793251881Speter
794251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
795251881Speter    {
796251881Speter      return hook_symlink_error(hook);
797251881Speter    }
798251881Speter  else if (hook)
799251881Speter    {
800251881Speter      const char *args[5];
801251881Speter      apr_file_t *stdin_handle = NULL;
802251881Speter      svn_string_t *paths_str = svn_string_create(svn_cstring_join
803251881Speter                                                  (paths, "\n", pool),
804251881Speter                                                  pool);
805251881Speter
806251881Speter      SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
807251881Speter
808251881Speter      args[0] = hook;
809251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
810251881Speter      args[2] = username;
811251881Speter      args[3] = NULL;
812251881Speter      args[4] = NULL;
813251881Speter
814251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_LOCK, hook, args,
815251881Speter                           hooks_env, stdin_handle, pool));
816251881Speter
817251881Speter      SVN_ERR(svn_io_file_close(stdin_handle, pool));
818251881Speter    }
819251881Speter
820251881Speter  return SVN_NO_ERROR;
821251881Speter}
822251881Speter
823251881Speter
824251881Spetersvn_error_t  *
825251881Spetersvn_repos__hooks_pre_unlock(svn_repos_t *repos,
826251881Speter                            apr_hash_t *hooks_env,
827251881Speter                            const char *path,
828251881Speter                            const char *username,
829251881Speter                            const char *token,
830251881Speter                            svn_boolean_t break_lock,
831251881Speter                            apr_pool_t *pool)
832251881Speter{
833251881Speter  const char *hook = svn_repos_pre_unlock_hook(repos, pool);
834251881Speter  svn_boolean_t broken_link;
835251881Speter
836251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
837251881Speter    {
838251881Speter      return hook_symlink_error(hook);
839251881Speter    }
840251881Speter  else if (hook)
841251881Speter    {
842251881Speter      const char *args[7];
843251881Speter
844251881Speter      args[0] = hook;
845251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
846251881Speter      args[2] = path;
847251881Speter      args[3] = username ? username : "";
848251881Speter      args[4] = token ? token : "";
849251881Speter      args[5] = break_lock ? "1" : "0";
850251881Speter      args[6] = NULL;
851251881Speter
852251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args,
853251881Speter                           hooks_env, NULL, pool));
854251881Speter    }
855251881Speter
856251881Speter  return SVN_NO_ERROR;
857251881Speter}
858251881Speter
859251881Speter
860251881Spetersvn_error_t  *
861251881Spetersvn_repos__hooks_post_unlock(svn_repos_t *repos,
862251881Speter                             apr_hash_t *hooks_env,
863251881Speter                             const apr_array_header_t *paths,
864251881Speter                             const char *username,
865251881Speter                             apr_pool_t *pool)
866251881Speter{
867251881Speter  const char *hook = svn_repos_post_unlock_hook(repos, pool);
868251881Speter  svn_boolean_t broken_link;
869251881Speter
870251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
871251881Speter    {
872251881Speter      return hook_symlink_error(hook);
873251881Speter    }
874251881Speter  else if (hook)
875251881Speter    {
876251881Speter      const char *args[5];
877251881Speter      apr_file_t *stdin_handle = NULL;
878251881Speter      svn_string_t *paths_str = svn_string_create(svn_cstring_join
879251881Speter                                                  (paths, "\n", pool),
880251881Speter                                                  pool);
881251881Speter
882251881Speter      SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
883251881Speter
884251881Speter      args[0] = hook;
885251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
886251881Speter      args[2] = username ? username : "";
887251881Speter      args[3] = NULL;
888251881Speter      args[4] = NULL;
889251881Speter
890251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_UNLOCK, hook, args,
891251881Speter                           hooks_env, stdin_handle, pool));
892251881Speter
893251881Speter      SVN_ERR(svn_io_file_close(stdin_handle, pool));
894251881Speter    }
895251881Speter
896251881Speter  return SVN_NO_ERROR;
897251881Speter}
898251881Speter
899251881Speter
900251881Speter
901251881Speter/*
902251881Speter * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq
903251881Speter * vim:isk=a-z,A-Z,48-57,_,.,-,>
904251881Speter * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0
905251881Speter */
906