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",
187251881Speter                           (const char *)svn__apr_hash_index_key(hi),
188251881Speter                           (const char *)svn__apr_hash_index_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
321251881Speter     As Windows Scripting Host (.wsf) files can accomodate (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 =
337251881Speter        (**extn ? apr_pstrcat(pool, hook, *extn, (char *)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;
366251881Speter} parse_hooks_env_option_baton;
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;
396251881Speter} parse_hooks_env_section_baton;
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  svn_config_t *cfg;
420251881Speter  struct parse_hooks_env_section_baton b;
421251881Speter
422251881Speter  if (local_abspath)
423251881Speter    {
424251881Speter      SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE,
425251881Speter                               TRUE, TRUE, scratch_pool));
426251881Speter      b.cfg = cfg;
427251881Speter      b.hooks_env = apr_hash_make(result_pool);
428251881Speter      (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section, &b,
429251881Speter                                           scratch_pool);
430251881Speter      *hooks_env_p = b.hooks_env;
431251881Speter    }
432251881Speter  else
433251881Speter    {
434251881Speter      *hooks_env_p = NULL;
435251881Speter    }
436251881Speter
437251881Speter  return SVN_NO_ERROR;
438251881Speter}
439251881Speter
440251881Speter/* Return an error for the failure of HOOK due to a broken symlink. */
441251881Speterstatic svn_error_t *
442251881Speterhook_symlink_error(const char *hook)
443251881Speter{
444251881Speter  return svn_error_createf
445251881Speter    (SVN_ERR_REPOS_HOOK_FAILURE, NULL,
446251881Speter     _("Failed to run '%s' hook; broken symlink"), hook);
447251881Speter}
448251881Speter
449251881Spetersvn_error_t *
450251881Spetersvn_repos__hooks_start_commit(svn_repos_t *repos,
451251881Speter                              apr_hash_t *hooks_env,
452251881Speter                              const char *user,
453251881Speter                              const apr_array_header_t *capabilities,
454251881Speter                              const char *txn_name,
455251881Speter                              apr_pool_t *pool)
456251881Speter{
457251881Speter  const char *hook = svn_repos_start_commit_hook(repos, pool);
458251881Speter  svn_boolean_t broken_link;
459251881Speter
460251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
461251881Speter    {
462251881Speter      return hook_symlink_error(hook);
463251881Speter    }
464251881Speter  else if (hook)
465251881Speter    {
466251881Speter      const char *args[6];
467251881Speter      char *capabilities_string;
468251881Speter
469251881Speter      if (capabilities)
470251881Speter        {
471251881Speter          capabilities_string = svn_cstring_join(capabilities, ":", pool);
472251881Speter
473251881Speter          /* Get rid of that annoying final colon. */
474251881Speter          if (capabilities_string[0])
475251881Speter            capabilities_string[strlen(capabilities_string) - 1] = '\0';
476251881Speter        }
477251881Speter      else
478251881Speter        {
479251881Speter          capabilities_string = apr_pstrdup(pool, "");
480251881Speter        }
481251881Speter
482251881Speter      args[0] = hook;
483251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
484251881Speter      args[2] = user ? user : "";
485251881Speter      args[3] = capabilities_string;
486251881Speter      args[4] = txn_name;
487251881Speter      args[5] = NULL;
488251881Speter
489251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args,
490251881Speter                           hooks_env, NULL, pool));
491251881Speter    }
492251881Speter
493251881Speter  return SVN_NO_ERROR;
494251881Speter}
495251881Speter
496251881Speter/* Set *HANDLE to an open filehandle for a temporary file (i.e.,
497251881Speter   automatically deleted when closed), into which the LOCK_TOKENS have
498251881Speter   been written out in the format described in the pre-commit hook
499251881Speter   template.
500251881Speter
501251881Speter   LOCK_TOKENS is as returned by svn_fs__access_get_lock_tokens().
502251881Speter
503251881Speter   Allocate *HANDLE in POOL, and use POOL for temporary allocations. */
504251881Speterstatic svn_error_t *
505251881Speterlock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens,
506251881Speter                   apr_pool_t *pool)
507251881Speter{
508251881Speter  svn_stringbuf_t *lock_str = svn_stringbuf_create("LOCK-TOKENS:\n", pool);
509251881Speter  apr_hash_index_t *hi;
510251881Speter
511251881Speter  for (hi = apr_hash_first(pool, lock_tokens); hi;
512251881Speter       hi = apr_hash_next(hi))
513251881Speter    {
514251881Speter      void *val;
515251881Speter      const char *path, *token;
516251881Speter
517251881Speter      apr_hash_this(hi, (void *)&token, NULL, &val);
518251881Speter      path = val;
519251881Speter      svn_stringbuf_appendstr(lock_str,
520251881Speter        svn_stringbuf_createf(pool, "%s|%s\n",
521251881Speter                              svn_path_uri_autoescape(path, pool),
522251881Speter                              token));
523251881Speter    }
524251881Speter
525251881Speter  svn_stringbuf_appendcstr(lock_str, "\n");
526251881Speter  return create_temp_file(handle,
527251881Speter                          svn_stringbuf__morph_into_string(lock_str), pool);
528251881Speter}
529251881Speter
530251881Speter
531251881Speter
532251881Spetersvn_error_t  *
533251881Spetersvn_repos__hooks_pre_commit(svn_repos_t *repos,
534251881Speter                            apr_hash_t *hooks_env,
535251881Speter                            const char *txn_name,
536251881Speter                            apr_pool_t *pool)
537251881Speter{
538251881Speter  const char *hook = svn_repos_pre_commit_hook(repos, pool);
539251881Speter  svn_boolean_t broken_link;
540251881Speter
541251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
542251881Speter    {
543251881Speter      return hook_symlink_error(hook);
544251881Speter    }
545251881Speter  else if (hook)
546251881Speter    {
547251881Speter      const char *args[4];
548251881Speter      svn_fs_access_t *access_ctx;
549251881Speter      apr_file_t *stdin_handle = NULL;
550251881Speter
551251881Speter      args[0] = hook;
552251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
553251881Speter      args[2] = txn_name;
554251881Speter      args[3] = NULL;
555251881Speter
556251881Speter      SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
557251881Speter      if (access_ctx)
558251881Speter        {
559251881Speter          apr_hash_t *lock_tokens = svn_fs__access_get_lock_tokens(access_ctx);
560251881Speter          if (apr_hash_count(lock_tokens))  {
561251881Speter            SVN_ERR(lock_token_content(&stdin_handle, lock_tokens, pool));
562251881Speter          }
563251881Speter        }
564251881Speter
565251881Speter      if (!stdin_handle)
566251881Speter        SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
567251881Speter                                 APR_READ, APR_OS_DEFAULT, pool));
568251881Speter
569251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_COMMIT, hook, args,
570251881Speter                           hooks_env, stdin_handle, pool));
571251881Speter    }
572251881Speter
573251881Speter  return SVN_NO_ERROR;
574251881Speter}
575251881Speter
576251881Speter
577251881Spetersvn_error_t  *
578251881Spetersvn_repos__hooks_post_commit(svn_repos_t *repos,
579251881Speter                             apr_hash_t *hooks_env,
580251881Speter                             svn_revnum_t rev,
581251881Speter                             const char *txn_name,
582251881Speter                             apr_pool_t *pool)
583251881Speter{
584251881Speter  const char *hook = svn_repos_post_commit_hook(repos, pool);
585251881Speter  svn_boolean_t broken_link;
586251881Speter
587251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
588251881Speter    {
589251881Speter      return hook_symlink_error(hook);
590251881Speter    }
591251881Speter  else if (hook)
592251881Speter    {
593251881Speter      const char *args[5];
594251881Speter
595251881Speter      args[0] = hook;
596251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
597251881Speter      args[2] = apr_psprintf(pool, "%ld", rev);
598251881Speter      args[3] = txn_name;
599251881Speter      args[4] = NULL;
600251881Speter
601251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args,
602251881Speter                           hooks_env, NULL, pool));
603251881Speter    }
604251881Speter
605251881Speter  return SVN_NO_ERROR;
606251881Speter}
607251881Speter
608251881Speter
609251881Spetersvn_error_t  *
610251881Spetersvn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
611251881Speter                                    apr_hash_t *hooks_env,
612251881Speter                                    svn_revnum_t rev,
613251881Speter                                    const char *author,
614251881Speter                                    const char *name,
615251881Speter                                    const svn_string_t *new_value,
616251881Speter                                    char action,
617251881Speter                                    apr_pool_t *pool)
618251881Speter{
619251881Speter  const char *hook = svn_repos_pre_revprop_change_hook(repos, pool);
620251881Speter  svn_boolean_t broken_link;
621251881Speter
622251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
623251881Speter    {
624251881Speter      return hook_symlink_error(hook);
625251881Speter    }
626251881Speter  else if (hook)
627251881Speter    {
628251881Speter      const char *args[7];
629251881Speter      apr_file_t *stdin_handle = NULL;
630251881Speter      char action_string[2];
631251881Speter
632251881Speter      /* Pass the new value as stdin to hook */
633251881Speter      if (new_value)
634251881Speter        SVN_ERR(create_temp_file(&stdin_handle, new_value, pool));
635251881Speter      else
636251881Speter        SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
637251881Speter                                 APR_READ, APR_OS_DEFAULT, pool));
638251881Speter
639251881Speter      action_string[0] = action;
640251881Speter      action_string[1] = '\0';
641251881Speter
642251881Speter      args[0] = hook;
643251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
644251881Speter      args[2] = apr_psprintf(pool, "%ld", rev);
645251881Speter      args[3] = author ? author : "";
646251881Speter      args[4] = name;
647251881Speter      args[5] = action_string;
648251881Speter      args[6] = NULL;
649251881Speter
650251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook,
651251881Speter                           args, hooks_env, stdin_handle, pool));
652251881Speter
653251881Speter      SVN_ERR(svn_io_file_close(stdin_handle, pool));
654251881Speter    }
655251881Speter  else
656251881Speter    {
657251881Speter      /* If the pre- hook doesn't exist at all, then default to
658251881Speter         MASSIVE PARANOIA.  Changing revision properties is a lossy
659251881Speter         operation; so unless the repository admininstrator has
660251881Speter         *deliberately* created the pre-hook, disallow all changes. */
661251881Speter      return
662251881Speter        svn_error_create
663251881Speter        (SVN_ERR_REPOS_DISABLED_FEATURE, NULL,
664251881Speter         _("Repository has not been enabled to accept revision propchanges;\n"
665251881Speter           "ask the administrator to create a pre-revprop-change hook"));
666251881Speter    }
667251881Speter
668251881Speter  return SVN_NO_ERROR;
669251881Speter}
670251881Speter
671251881Speter
672251881Spetersvn_error_t  *
673251881Spetersvn_repos__hooks_post_revprop_change(svn_repos_t *repos,
674251881Speter                                     apr_hash_t *hooks_env,
675251881Speter                                     svn_revnum_t rev,
676251881Speter                                     const char *author,
677251881Speter                                     const char *name,
678251881Speter                                     const svn_string_t *old_value,
679251881Speter                                     char action,
680251881Speter                                     apr_pool_t *pool)
681251881Speter{
682251881Speter  const char *hook = svn_repos_post_revprop_change_hook(repos, pool);
683251881Speter  svn_boolean_t broken_link;
684251881Speter
685251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
686251881Speter    {
687251881Speter      return hook_symlink_error(hook);
688251881Speter    }
689251881Speter  else if (hook)
690251881Speter    {
691251881Speter      const char *args[7];
692251881Speter      apr_file_t *stdin_handle = NULL;
693251881Speter      char action_string[2];
694251881Speter
695251881Speter      /* Pass the old value as stdin to hook */
696251881Speter      if (old_value)
697251881Speter        SVN_ERR(create_temp_file(&stdin_handle, old_value, pool));
698251881Speter      else
699251881Speter        SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
700251881Speter                                 APR_READ, APR_OS_DEFAULT, pool));
701251881Speter
702251881Speter      action_string[0] = action;
703251881Speter      action_string[1] = '\0';
704251881Speter
705251881Speter      args[0] = hook;
706251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
707251881Speter      args[2] = apr_psprintf(pool, "%ld", rev);
708251881Speter      args[3] = author ? author : "";
709251881Speter      args[4] = name;
710251881Speter      args[5] = action_string;
711251881Speter      args[6] = NULL;
712251881Speter
713251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_REVPROP_CHANGE, hook,
714251881Speter                           args, hooks_env, stdin_handle, pool));
715251881Speter
716251881Speter      SVN_ERR(svn_io_file_close(stdin_handle, pool));
717251881Speter    }
718251881Speter
719251881Speter  return SVN_NO_ERROR;
720251881Speter}
721251881Speter
722251881Speter
723251881Spetersvn_error_t  *
724251881Spetersvn_repos__hooks_pre_lock(svn_repos_t *repos,
725251881Speter                          apr_hash_t *hooks_env,
726251881Speter                          const char **token,
727251881Speter                          const char *path,
728251881Speter                          const char *username,
729251881Speter                          const char *comment,
730251881Speter                          svn_boolean_t steal_lock,
731251881Speter                          apr_pool_t *pool)
732251881Speter{
733251881Speter  const char *hook = svn_repos_pre_lock_hook(repos, pool);
734251881Speter  svn_boolean_t broken_link;
735251881Speter
736251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
737251881Speter    {
738251881Speter      return hook_symlink_error(hook);
739251881Speter    }
740251881Speter  else if (hook)
741251881Speter    {
742251881Speter      const char *args[7];
743251881Speter      svn_string_t *buf;
744251881Speter
745251881Speter
746251881Speter      args[0] = hook;
747251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
748251881Speter      args[2] = path;
749251881Speter      args[3] = username;
750251881Speter      args[4] = comment ? comment : "";
751251881Speter      args[5] = steal_lock ? "1" : "0";
752251881Speter      args[6] = NULL;
753251881Speter
754251881Speter      SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args,
755251881Speter                           hooks_env, NULL, pool));
756251881Speter
757251881Speter      if (token)
758251881Speter        /* No validation here; the FS will take care of that. */
759251881Speter        *token = buf->data;
760251881Speter
761251881Speter    }
762251881Speter  else if (token)
763251881Speter    *token = "";
764251881Speter
765251881Speter  return SVN_NO_ERROR;
766251881Speter}
767251881Speter
768251881Speter
769251881Spetersvn_error_t  *
770251881Spetersvn_repos__hooks_post_lock(svn_repos_t *repos,
771251881Speter                           apr_hash_t *hooks_env,
772251881Speter                           const apr_array_header_t *paths,
773251881Speter                           const char *username,
774251881Speter                           apr_pool_t *pool)
775251881Speter{
776251881Speter  const char *hook = svn_repos_post_lock_hook(repos, pool);
777251881Speter  svn_boolean_t broken_link;
778251881Speter
779251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
780251881Speter    {
781251881Speter      return hook_symlink_error(hook);
782251881Speter    }
783251881Speter  else if (hook)
784251881Speter    {
785251881Speter      const char *args[5];
786251881Speter      apr_file_t *stdin_handle = NULL;
787251881Speter      svn_string_t *paths_str = svn_string_create(svn_cstring_join
788251881Speter                                                  (paths, "\n", pool),
789251881Speter                                                  pool);
790251881Speter
791251881Speter      SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
792251881Speter
793251881Speter      args[0] = hook;
794251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
795251881Speter      args[2] = username;
796251881Speter      args[3] = NULL;
797251881Speter      args[4] = NULL;
798251881Speter
799251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_LOCK, hook, args,
800251881Speter                           hooks_env, stdin_handle, pool));
801251881Speter
802251881Speter      SVN_ERR(svn_io_file_close(stdin_handle, pool));
803251881Speter    }
804251881Speter
805251881Speter  return SVN_NO_ERROR;
806251881Speter}
807251881Speter
808251881Speter
809251881Spetersvn_error_t  *
810251881Spetersvn_repos__hooks_pre_unlock(svn_repos_t *repos,
811251881Speter                            apr_hash_t *hooks_env,
812251881Speter                            const char *path,
813251881Speter                            const char *username,
814251881Speter                            const char *token,
815251881Speter                            svn_boolean_t break_lock,
816251881Speter                            apr_pool_t *pool)
817251881Speter{
818251881Speter  const char *hook = svn_repos_pre_unlock_hook(repos, pool);
819251881Speter  svn_boolean_t broken_link;
820251881Speter
821251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
822251881Speter    {
823251881Speter      return hook_symlink_error(hook);
824251881Speter    }
825251881Speter  else if (hook)
826251881Speter    {
827251881Speter      const char *args[7];
828251881Speter
829251881Speter      args[0] = hook;
830251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
831251881Speter      args[2] = path;
832251881Speter      args[3] = username ? username : "";
833251881Speter      args[4] = token ? token : "";
834251881Speter      args[5] = break_lock ? "1" : "0";
835251881Speter      args[6] = NULL;
836251881Speter
837251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args,
838251881Speter                           hooks_env, NULL, pool));
839251881Speter    }
840251881Speter
841251881Speter  return SVN_NO_ERROR;
842251881Speter}
843251881Speter
844251881Speter
845251881Spetersvn_error_t  *
846251881Spetersvn_repos__hooks_post_unlock(svn_repos_t *repos,
847251881Speter                             apr_hash_t *hooks_env,
848251881Speter                             const apr_array_header_t *paths,
849251881Speter                             const char *username,
850251881Speter                             apr_pool_t *pool)
851251881Speter{
852251881Speter  const char *hook = svn_repos_post_unlock_hook(repos, pool);
853251881Speter  svn_boolean_t broken_link;
854251881Speter
855251881Speter  if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
856251881Speter    {
857251881Speter      return hook_symlink_error(hook);
858251881Speter    }
859251881Speter  else if (hook)
860251881Speter    {
861251881Speter      const char *args[5];
862251881Speter      apr_file_t *stdin_handle = NULL;
863251881Speter      svn_string_t *paths_str = svn_string_create(svn_cstring_join
864251881Speter                                                  (paths, "\n", pool),
865251881Speter                                                  pool);
866251881Speter
867251881Speter      SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
868251881Speter
869251881Speter      args[0] = hook;
870251881Speter      args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
871251881Speter      args[2] = username ? username : "";
872251881Speter      args[3] = NULL;
873251881Speter      args[4] = NULL;
874251881Speter
875251881Speter      SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_UNLOCK, hook, args,
876251881Speter                           hooks_env, stdin_handle, pool));
877251881Speter
878251881Speter      SVN_ERR(svn_io_file_close(stdin_handle, pool));
879251881Speter    }
880251881Speter
881251881Speter  return SVN_NO_ERROR;
882251881Speter}
883251881Speter
884251881Speter
885251881Speter
886251881Speter/*
887251881Speter * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq
888251881Speter * vim:isk=a-z,A-Z,48-57,_,.,-,>
889251881Speter * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0
890251881Speter */
891