~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/svnmucc/svnmucc.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
40
40
 
41
41
#include <apr_lib.h>
42
42
 
 
43
#include "svn_private_config.h"
43
44
#include "svn_hash.h"
44
45
#include "svn_client.h"
 
46
#include "private/svn_client_mtcc.h"
45
47
#include "svn_cmdline.h"
46
48
#include "svn_config.h"
47
49
#include "svn_error.h"
48
50
#include "svn_path.h"
49
51
#include "svn_pools.h"
50
52
#include "svn_props.h"
51
 
#include "svn_ra.h"
52
53
#include "svn_string.h"
53
54
#include "svn_subst.h"
54
55
#include "svn_utf.h"
55
56
#include "svn_version.h"
56
57
 
57
58
#include "private/svn_cmdline_private.h"
58
 
#include "private/svn_ra_private.h"
59
 
#include "private/svn_string_private.h"
60
59
#include "private/svn_subr_private.h"
61
60
 
62
 
#include "svn_private_config.h"
63
 
 
64
 
static void handle_error(svn_error_t *err, apr_pool_t *pool)
65
 
{
66
 
  if (err)
67
 
    svn_handle_error2(err, stderr, FALSE, "svnmucc: ");
68
 
  svn_error_clear(err);
69
 
  if (pool)
70
 
    svn_pool_destroy(pool);
71
 
  exit(EXIT_FAILURE);
72
 
}
73
 
 
74
 
static apr_pool_t *
75
 
init(const char *application)
76
 
{
77
 
  svn_error_t *err;
78
 
  const svn_version_checklist_t checklist[] = {
79
 
    {"svn_client", svn_client_version},
80
 
    {"svn_subr", svn_subr_version},
81
 
    {"svn_ra", svn_ra_version},
82
 
    {NULL, NULL}
83
 
  };
 
61
/* Version compatibility check */
 
62
static svn_error_t *
 
63
check_lib_versions(void)
 
64
{
 
65
  static const svn_version_checklist_t checklist[] =
 
66
    {
 
67
      { "svn_client", svn_client_version },
 
68
      { "svn_subr",   svn_subr_version },
 
69
      { "svn_ra",     svn_ra_version },
 
70
      { NULL, NULL }
 
71
    };
84
72
  SVN_VERSION_DEFINE(my_version);
85
73
 
86
 
  if (svn_cmdline_init(application, stderr))
87
 
    exit(EXIT_FAILURE);
88
 
 
89
 
  err = svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
90
 
  if (err)
91
 
    handle_error(err, NULL);
92
 
 
93
 
  return apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
94
 
}
95
 
 
96
 
static svn_error_t *
97
 
open_tmp_file(apr_file_t **fp,
98
 
              void *callback_baton,
99
 
              apr_pool_t *pool)
100
 
{
101
 
  /* Open a unique file;  use APR_DELONCLOSE. */
102
 
  return svn_io_open_unique_file3(fp, NULL, NULL, svn_io_file_del_on_close,
103
 
                                  pool, pool);
104
 
}
105
 
 
106
 
static svn_error_t *
107
 
create_ra_callbacks(svn_ra_callbacks2_t **callbacks,
108
 
                    const char *username,
109
 
                    const char *password,
110
 
                    const char *config_dir,
111
 
                    svn_config_t *cfg_config,
112
 
                    svn_boolean_t non_interactive,
113
 
                    svn_boolean_t trust_server_cert,
114
 
                    svn_boolean_t no_auth_cache,
115
 
                    apr_pool_t *pool)
116
 
{
117
 
  SVN_ERR(svn_ra_create_callbacks(callbacks, pool));
118
 
 
119
 
  SVN_ERR(svn_cmdline_create_auth_baton(&(*callbacks)->auth_baton,
120
 
                                        non_interactive,
121
 
                                        username, password, config_dir,
122
 
                                        no_auth_cache,
123
 
                                        trust_server_cert,
124
 
                                        cfg_config, NULL, NULL, pool));
125
 
 
126
 
  (*callbacks)->open_tmp_file = open_tmp_file;
127
 
 
128
 
  return SVN_NO_ERROR;
129
 
}
130
 
 
131
 
 
 
74
  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
 
75
}
132
76
 
133
77
static svn_error_t *
134
78
commit_callback(const svn_commit_info_t *commit_info,
154
98
  ACTION_RM
155
99
} action_code_t;
156
100
 
157
 
struct operation {
158
 
  enum {
159
 
    OP_OPEN,
160
 
    OP_DELETE,
161
 
    OP_ADD,
162
 
    OP_REPLACE,
163
 
    OP_PROPSET           /* only for files for which no other operation is
164
 
                            occuring; directories are OP_OPEN with non-empty
165
 
                            props */
166
 
  } operation;
167
 
  svn_node_kind_t kind;  /* to copy, mkdir, put or set revprops */
168
 
  svn_revnum_t rev;      /* to copy, valid for add and replace */
169
 
  const char *url;       /* to copy, valid for add and replace */
170
 
  const char *src_file;  /* for put, the source file for contents */
171
 
  apr_hash_t *children;  /* const char *path -> struct operation * */
172
 
  apr_hash_t *prop_mods; /* const char *prop_name ->
173
 
                            const svn_string_t *prop_value */
174
 
  apr_array_header_t *prop_dels; /* const char *prop_name deletions */
175
 
  void *baton;           /* as returned by the commit editor */
176
 
};
177
 
 
178
 
 
179
 
/* An iterator (for use via apr_table_do) which sets node properties.
180
 
   REC is a pointer to a struct driver_state. */
181
 
static svn_error_t *
182
 
change_props(const svn_delta_editor_t *editor,
183
 
             void *baton,
184
 
             struct operation *child,
185
 
             apr_pool_t *pool)
186
 
{
187
 
  apr_pool_t *iterpool = svn_pool_create(pool);
188
 
 
189
 
  if (child->prop_dels)
190
 
    {
191
 
      int i;
192
 
      for (i = 0; i < child->prop_dels->nelts; i++)
193
 
        {
194
 
          const char *prop_name;
195
 
 
196
 
          svn_pool_clear(iterpool);
197
 
          prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *);
198
 
          if (child->kind == svn_node_dir)
199
 
            SVN_ERR(editor->change_dir_prop(baton, prop_name,
200
 
                                            NULL, iterpool));
201
 
          else
202
 
            SVN_ERR(editor->change_file_prop(baton, prop_name,
203
 
                                             NULL, iterpool));
204
 
        }
205
 
    }
206
 
  if (apr_hash_count(child->prop_mods))
207
 
    {
208
 
      apr_hash_index_t *hi;
209
 
      for (hi = apr_hash_first(pool, child->prop_mods);
210
 
           hi; hi = apr_hash_next(hi))
211
 
        {
212
 
          const char *propname = svn__apr_hash_index_key(hi);
213
 
          const svn_string_t *val = svn__apr_hash_index_val(hi);
214
 
 
215
 
          svn_pool_clear(iterpool);
216
 
          if (child->kind == svn_node_dir)
217
 
            SVN_ERR(editor->change_dir_prop(baton, propname, val, iterpool));
218
 
          else
219
 
            SVN_ERR(editor->change_file_prop(baton, propname, val, iterpool));
220
 
        }
221
 
    }
222
 
 
223
 
  svn_pool_destroy(iterpool);
224
 
  return SVN_NO_ERROR;
225
 
}
226
 
 
227
 
 
228
 
/* Drive EDITOR to affect the change represented by OPERATION.  HEAD
229
 
   is the last-known youngest revision in the repository. */
230
 
static svn_error_t *
231
 
drive(struct operation *operation,
232
 
      svn_revnum_t head,
233
 
      const svn_delta_editor_t *editor,
234
 
      apr_pool_t *pool)
235
 
{
236
 
  apr_pool_t *subpool = svn_pool_create(pool);
237
 
  apr_hash_index_t *hi;
238
 
 
239
 
  for (hi = apr_hash_first(pool, operation->children);
240
 
       hi; hi = apr_hash_next(hi))
241
 
    {
242
 
      const char *key = svn__apr_hash_index_key(hi);
243
 
      struct operation *child = svn__apr_hash_index_val(hi);
244
 
      void *file_baton = NULL;
245
 
 
246
 
      svn_pool_clear(subpool);
247
 
 
248
 
      /* Deletes and replacements are simple -- delete something. */
249
 
      if (child->operation == OP_DELETE || child->operation == OP_REPLACE)
250
 
        {
251
 
          SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool));
252
 
        }
253
 
      /* Opens could be for directories or files. */
254
 
      if (child->operation == OP_OPEN || child->operation == OP_PROPSET)
255
 
        {
256
 
          if (child->kind == svn_node_dir)
257
 
            {
258
 
              SVN_ERR(editor->open_directory(key, operation->baton, head,
259
 
                                             subpool, &child->baton));
260
 
            }
261
 
          else
262
 
            {
263
 
              SVN_ERR(editor->open_file(key, operation->baton, head,
264
 
                                        subpool, &file_baton));
265
 
            }
266
 
        }
267
 
      /* Adds and replacements could also be for directories or files. */
268
 
      if (child->operation == OP_ADD || child->operation == OP_REPLACE)
269
 
        {
270
 
          if (child->kind == svn_node_dir)
271
 
            {
272
 
              SVN_ERR(editor->add_directory(key, operation->baton,
273
 
                                            child->url, child->rev,
274
 
                                            subpool, &child->baton));
275
 
            }
276
 
          else
277
 
            {
278
 
              SVN_ERR(editor->add_file(key, operation->baton, child->url,
279
 
                                       child->rev, subpool, &file_baton));
280
 
            }
281
 
        }
282
 
      /* If there's a source file and an open file baton, we get to
283
 
         change textual contents. */
284
 
      if ((child->src_file) && (file_baton))
285
 
        {
286
 
          svn_txdelta_window_handler_t handler;
287
 
          void *handler_baton;
288
 
          svn_stream_t *contents;
289
 
 
290
 
          SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool,
291
 
                                          &handler, &handler_baton));
292
 
          if (strcmp(child->src_file, "-") != 0)
293
 
            {
294
 
              SVN_ERR(svn_stream_open_readonly(&contents, child->src_file,
295
 
                                               pool, pool));
296
 
            }
297
 
          else
298
 
            {
299
 
              SVN_ERR(svn_stream_for_stdin(&contents, pool));
300
 
            }
301
 
          SVN_ERR(svn_txdelta_send_stream(contents, handler,
302
 
                                          handler_baton, NULL, pool));
303
 
        }
304
 
      /* If we opened a file, we need to apply outstanding propmods,
305
 
         then close it. */
306
 
      if (file_baton)
307
 
        {
308
 
          if (child->kind == svn_node_file)
309
 
            {
310
 
              SVN_ERR(change_props(editor, file_baton, child, subpool));
311
 
            }
312
 
          SVN_ERR(editor->close_file(file_baton, NULL, subpool));
313
 
        }
314
 
      /* If we opened, added, or replaced a directory, we need to
315
 
         recurse, apply outstanding propmods, and then close it. */
316
 
      if ((child->kind == svn_node_dir)
317
 
          && child->operation != OP_DELETE)
318
 
        {
319
 
          SVN_ERR(change_props(editor, child->baton, child, subpool));
320
 
 
321
 
          SVN_ERR(drive(child, head, editor, subpool));
322
 
 
323
 
          SVN_ERR(editor->close_directory(child->baton, subpool));
324
 
        }
325
 
    }
326
 
  svn_pool_destroy(subpool);
327
 
  return SVN_NO_ERROR;
328
 
}
329
 
 
330
 
 
331
 
/* Find the operation associated with PATH, which is a single-path
332
 
   component representing a child of the path represented by
333
 
   OPERATION.  If no such child operation exists, create a new one of
334
 
   type OP_OPEN. */
335
 
static struct operation *
336
 
get_operation(const char *path,
337
 
              struct operation *operation,
338
 
              apr_pool_t *pool)
339
 
{
340
 
  struct operation *child = svn_hash_gets(operation->children, path);
341
 
  if (! child)
342
 
    {
343
 
      child = apr_pcalloc(pool, sizeof(*child));
344
 
      child->children = apr_hash_make(pool);
345
 
      child->operation = OP_OPEN;
346
 
      child->rev = SVN_INVALID_REVNUM;
347
 
      child->kind = svn_node_dir;
348
 
      child->prop_mods = apr_hash_make(pool);
349
 
      child->prop_dels = apr_array_make(pool, 1, sizeof(const char *));
350
 
      svn_hash_sets(operation->children, path, child);
351
 
    }
352
 
  return child;
353
 
}
354
 
 
355
101
/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */
356
102
static const char *
357
103
subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool)
359
105
  return svn_uri_skip_ancestor(anchor, url, pool);
360
106
}
361
107
 
362
 
/* Add PATH to the operations tree rooted at OPERATION, creating any
363
 
   intermediate nodes that are required.  Here's what's expected for
364
 
   each action type:
365
 
 
366
 
      ACTION          URL    REV      SRC-FILE  PROPNAME
367
 
      ------------    -----  -------  --------  --------
368
 
      ACTION_MKDIR    NULL   invalid  NULL      NULL
369
 
      ACTION_CP       valid  valid    NULL      NULL
370
 
      ACTION_PUT      NULL   invalid  valid     NULL
371
 
      ACTION_RM       NULL   invalid  NULL      NULL
372
 
      ACTION_PROPSET  valid  invalid  NULL      valid
373
 
      ACTION_PROPDEL  valid  invalid  NULL      valid
374
 
 
375
 
   Node type information is obtained for any copy source (to determine
376
 
   whether to create a file or directory) and for any deleted path (to
377
 
   ensure it exists since svn_delta_editor_t->delete_entry doesn't
378
 
   return an error on non-existent nodes). */
379
 
static svn_error_t *
380
 
build(action_code_t action,
381
 
      const char *path,
382
 
      const char *url,
383
 
      svn_revnum_t rev,
384
 
      const char *prop_name,
385
 
      const svn_string_t *prop_value,
386
 
      const char *src_file,
387
 
      svn_revnum_t head,
388
 
      const char *anchor,
389
 
      svn_ra_session_t *session,
390
 
      struct operation *operation,
391
 
      apr_pool_t *pool)
392
 
{
393
 
  apr_array_header_t *path_bits = svn_path_decompose(path, pool);
394
 
  const char *path_so_far = "";
395
 
  const char *copy_src = NULL;
396
 
  svn_revnum_t copy_rev = SVN_INVALID_REVNUM;
397
 
  int i;
398
 
 
399
 
  /* Look for any previous operations we've recognized for PATH.  If
400
 
     any of PATH's ancestors have not yet been traversed, we'll be
401
 
     creating OP_OPEN operations for them as we walk down PATH's path
402
 
     components. */
403
 
  for (i = 0; i < path_bits->nelts; ++i)
404
 
    {
405
 
      const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *);
406
 
      path_so_far = svn_relpath_join(path_so_far, path_bit, pool);
407
 
      operation = get_operation(path_so_far, operation, pool);
408
 
 
409
 
      /* If we cross a replace- or add-with-history, remember the
410
 
      source of those things in case we need to lookup the node kind
411
 
      of one of their children.  And if this isn't such a copy,
412
 
      but we've already seen one in of our parent paths, we just need
413
 
      to extend that copy source path by our current path
414
 
      component. */
415
 
      if (operation->url
416
 
          && SVN_IS_VALID_REVNUM(operation->rev)
417
 
          && (operation->operation == OP_REPLACE
418
 
              || operation->operation == OP_ADD))
419
 
        {
420
 
          copy_src = subtract_anchor(anchor, operation->url, pool);
421
 
          copy_rev = operation->rev;
422
 
        }
423
 
      else if (copy_src)
424
 
        {
425
 
          copy_src = svn_relpath_join(copy_src, path_bit, pool);
426
 
        }
427
 
    }
428
 
 
429
 
  /* Handle property changes. */
430
 
  if (prop_name)
431
 
    {
432
 
      if (operation->operation == OP_DELETE)
433
 
        return svn_error_createf(SVN_ERR_BAD_URL, NULL,
434
 
                                 "cannot set properties on a location being"
435
 
                                 " deleted ('%s')", path);
436
 
      /* If we're not adding this thing ourselves, check for existence.  */
437
 
      if (! ((operation->operation == OP_ADD) ||
438
 
             (operation->operation == OP_REPLACE)))
439
 
        {
440
 
          SVN_ERR(svn_ra_check_path(session,
441
 
                                    copy_src ? copy_src : path,
442
 
                                    copy_src ? copy_rev : head,
443
 
                                    &operation->kind, pool));
444
 
          if (operation->kind == svn_node_none)
445
 
            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
446
 
                                     "propset: '%s' not found", path);
447
 
          else if ((operation->kind == svn_node_file)
448
 
                   && (operation->operation == OP_OPEN))
449
 
            operation->operation = OP_PROPSET;
450
 
        }
451
 
      if (! prop_value)
452
 
        APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name;
453
 
      else
454
 
        svn_hash_sets(operation->prop_mods, prop_name, prop_value);
455
 
      if (!operation->rev)
456
 
        operation->rev = rev;
457
 
      return SVN_NO_ERROR;
458
 
    }
459
 
 
460
 
  /* We won't fuss about multiple operations on the same path in the
461
 
     following cases:
462
 
 
463
 
       - the prior operation was, in fact, a no-op (open)
464
 
       - the prior operation was a propset placeholder
465
 
       - the prior operation was a deletion
466
 
 
467
 
     Note: while the operation structure certainly supports the
468
 
     ability to do a copy of a file followed by a put of new contents
469
 
     for the file, we don't let that happen (yet).
470
 
  */
471
 
  if (operation->operation != OP_OPEN
472
 
      && operation->operation != OP_PROPSET
473
 
      && operation->operation != OP_DELETE)
474
 
    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
475
 
                             "unsupported multiple operations on '%s'", path);
476
 
 
477
 
  /* For deletions, we validate that there's actually something to
478
 
     delete.  If this is a deletion of the child of a copied
479
 
     directory, we need to remember to look in the copy source tree to
480
 
     verify that this thing actually exists. */
481
 
  if (action == ACTION_RM)
482
 
    {
483
 
      operation->operation = OP_DELETE;
484
 
      SVN_ERR(svn_ra_check_path(session,
485
 
                                copy_src ? copy_src : path,
486
 
                                copy_src ? copy_rev : head,
487
 
                                &operation->kind, pool));
488
 
      if (operation->kind == svn_node_none)
489
 
        {
490
 
          if (copy_src && strcmp(path, copy_src))
491
 
            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
492
 
                                     "'%s' (from '%s:%ld') not found",
493
 
                                     path, copy_src, copy_rev);
494
 
          else
495
 
            return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found",
496
 
                                     path);
497
 
        }
498
 
    }
499
 
  /* Handle copy operations (which can be adds or replacements). */
500
 
  else if (action == ACTION_CP)
501
 
    {
502
 
      if (rev > head)
503
 
        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
504
 
                                "Copy source revision cannot be younger "
505
 
                                "than base revision");
506
 
      operation->operation =
507
 
        operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
508
 
      if (operation->operation == OP_ADD)
509
 
        {
510
 
          /* There is a bug in the current version of mod_dav_svn
511
 
             which incorrectly replaces existing directories.
512
 
             Therefore we need to check if the target exists
513
 
             and raise an error here. */
514
 
          SVN_ERR(svn_ra_check_path(session,
515
 
                                    copy_src ? copy_src : path,
516
 
                                    copy_src ? copy_rev : head,
517
 
                                    &operation->kind, pool));
518
 
          if (operation->kind != svn_node_none)
519
 
            {
520
 
              if (copy_src && strcmp(path, copy_src))
521
 
                return svn_error_createf(SVN_ERR_BAD_URL, NULL,
522
 
                                         "'%s' (from '%s:%ld') already exists",
523
 
                                         path, copy_src, copy_rev);
524
 
              else
525
 
                return svn_error_createf(SVN_ERR_BAD_URL, NULL,
526
 
                                         "'%s' already exists", path);
527
 
            }
528
 
        }
529
 
      SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool),
530
 
                                rev, &operation->kind, pool));
531
 
      if (operation->kind == svn_node_none)
532
 
        return svn_error_createf(SVN_ERR_BAD_URL, NULL,
533
 
                                 "'%s' not found",
534
 
                                  subtract_anchor(anchor, url, pool));
535
 
      operation->url = url;
536
 
      operation->rev = rev;
537
 
    }
538
 
  /* Handle mkdir operations (which can be adds or replacements). */
539
 
  else if (action == ACTION_MKDIR)
540
 
    {
541
 
      operation->operation =
542
 
        operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
543
 
      operation->kind = svn_node_dir;
544
 
    }
545
 
  /* Handle put operations (which can be adds, replacements, or opens). */
546
 
  else if (action == ACTION_PUT)
547
 
    {
548
 
      if (operation->operation == OP_DELETE)
549
 
        {
550
 
          operation->operation = OP_REPLACE;
551
 
        }
552
 
      else
553
 
        {
554
 
          SVN_ERR(svn_ra_check_path(session,
555
 
                                    copy_src ? copy_src : path,
556
 
                                    copy_src ? copy_rev : head,
557
 
                                    &operation->kind, pool));
558
 
          if (operation->kind == svn_node_file)
559
 
            operation->operation = OP_OPEN;
560
 
          else if (operation->kind == svn_node_none)
561
 
            operation->operation = OP_ADD;
562
 
          else
563
 
            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
564
 
                                     "'%s' is not a file", path);
565
 
        }
566
 
      operation->kind = svn_node_file;
567
 
      operation->src_file = src_file;
568
 
    }
569
 
  else
570
 
    {
571
 
      /* We shouldn't get here. */
572
 
      SVN_ERR_MALFUNCTION();
573
 
    }
574
 
 
575
 
  return SVN_NO_ERROR;
576
 
}
577
108
 
578
109
struct action {
579
110
  action_code_t action;
597
128
  const svn_string_t *prop_value;
598
129
};
599
130
 
600
 
struct fetch_baton
601
 
{
602
 
  svn_ra_session_t *session;
603
 
  svn_revnum_t head;
604
 
};
605
 
 
606
 
static svn_error_t *
607
 
fetch_base_func(const char **filename,
608
 
                void *baton,
609
 
                const char *path,
610
 
                svn_revnum_t base_revision,
611
 
                apr_pool_t *result_pool,
612
 
                apr_pool_t *scratch_pool)
613
 
{
614
 
  struct fetch_baton *fb = baton;
615
 
  svn_stream_t *fstream;
616
 
  svn_error_t *err;
617
 
 
618
 
  if (! SVN_IS_VALID_REVNUM(base_revision))
619
 
    base_revision = fb->head;
620
 
 
621
 
  SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
622
 
                                 svn_io_file_del_on_pool_cleanup,
623
 
                                 result_pool, scratch_pool));
624
 
 
625
 
  err = svn_ra_get_file(fb->session, path, base_revision, fstream, NULL, NULL,
626
 
                         scratch_pool);
627
 
  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
628
 
    {
629
 
      svn_error_clear(err);
630
 
      SVN_ERR(svn_stream_close(fstream));
631
 
 
632
 
      *filename = NULL;
633
 
      return SVN_NO_ERROR;
634
 
    }
635
 
  else if (err)
636
 
    return svn_error_trace(err);
637
 
 
638
 
  SVN_ERR(svn_stream_close(fstream));
639
 
 
640
 
  return SVN_NO_ERROR;
641
 
}
642
 
 
643
 
static svn_error_t *
644
 
fetch_props_func(apr_hash_t **props,
645
 
                 void *baton,
646
 
                 const char *path,
647
 
                 svn_revnum_t base_revision,
648
 
                 apr_pool_t *result_pool,
649
 
                 apr_pool_t *scratch_pool)
650
 
{
651
 
  struct fetch_baton *fb = baton;
652
 
  svn_node_kind_t node_kind;
653
 
 
654
 
  if (! SVN_IS_VALID_REVNUM(base_revision))
655
 
    base_revision = fb->head;
656
 
 
657
 
  SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, &node_kind,
658
 
                            scratch_pool));
659
 
 
660
 
  if (node_kind == svn_node_file)
661
 
    {
662
 
      SVN_ERR(svn_ra_get_file(fb->session, path, base_revision, NULL, NULL,
663
 
                              props, result_pool));
664
 
    }
665
 
  else if (node_kind == svn_node_dir)
666
 
    {
667
 
      apr_array_header_t *tmp_props;
668
 
 
669
 
      SVN_ERR(svn_ra_get_dir2(fb->session, NULL, NULL, props, path,
670
 
                              base_revision, 0 /* Dirent fields */,
671
 
                              result_pool));
672
 
      tmp_props = svn_prop_hash_to_array(*props, result_pool);
673
 
      SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
674
 
                                   result_pool));
675
 
      *props = svn_prop_array_to_hash(tmp_props, result_pool);
676
 
    }
677
 
  else
678
 
    {
679
 
      *props = apr_hash_make(result_pool);
680
 
    }
681
 
 
682
 
  return SVN_NO_ERROR;
683
 
}
684
 
 
685
 
static svn_error_t *
686
 
fetch_kind_func(svn_node_kind_t *kind,
687
 
                void *baton,
688
 
                const char *path,
689
 
                svn_revnum_t base_revision,
690
 
                apr_pool_t *scratch_pool)
691
 
{
692
 
  struct fetch_baton *fb = baton;
693
 
 
694
 
  if (! SVN_IS_VALID_REVNUM(base_revision))
695
 
    base_revision = fb->head;
696
 
 
697
 
  SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, kind,
698
 
                             scratch_pool));
699
 
 
700
 
  return SVN_NO_ERROR;
701
 
}
702
 
 
703
 
static svn_delta_shim_callbacks_t *
704
 
get_shim_callbacks(svn_ra_session_t *session,
705
 
                   svn_revnum_t head,
706
 
                   apr_pool_t *result_pool)
707
 
{
708
 
  svn_delta_shim_callbacks_t *callbacks =
709
 
                            svn_delta_shim_callbacks_default(result_pool);
710
 
  struct fetch_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
711
 
 
712
 
  fb->session = session;
713
 
  fb->head = head;
714
 
 
715
 
  callbacks->fetch_props_func = fetch_props_func;
716
 
  callbacks->fetch_kind_func = fetch_kind_func;
717
 
  callbacks->fetch_base_func = fetch_base_func;
718
 
  callbacks->fetch_baton = fb;
719
 
 
720
 
  return callbacks;
721
 
}
722
 
 
723
131
static svn_error_t *
724
132
execute(const apr_array_header_t *actions,
725
133
        const char *anchor,
726
134
        apr_hash_t *revprops,
727
 
        const char *username,
728
 
        const char *password,
729
 
        const char *config_dir,
730
 
        const apr_array_header_t *config_options,
731
 
        svn_boolean_t non_interactive,
732
 
        svn_boolean_t trust_server_cert,
733
 
        svn_boolean_t no_auth_cache,
734
135
        svn_revnum_t base_revision,
 
136
        svn_client_ctx_t *ctx,
735
137
        apr_pool_t *pool)
736
138
{
737
 
  svn_ra_session_t *session;
738
 
  svn_ra_session_t *aux_session;
739
 
  const char *repos_root;
740
 
  svn_revnum_t head;
741
 
  const svn_delta_editor_t *editor;
742
 
  svn_ra_callbacks2_t *ra_callbacks;
743
 
  void *editor_baton;
744
 
  struct operation root;
 
139
  svn_client__mtcc_t *mtcc;
 
140
  apr_pool_t *iterpool = svn_pool_create(pool);
745
141
  svn_error_t *err;
746
 
  apr_hash_t *config;
747
 
  svn_config_t *cfg_config;
748
142
  int i;
749
143
 
750
 
  SVN_ERR(svn_config_get_config(&config, config_dir, pool));
751
 
  SVN_ERR(svn_cmdline__apply_config_options(config, config_options,
752
 
                                            "svnmucc: ", "--config-option"));
753
 
  cfg_config = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
754
 
 
755
 
  if (! svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))
756
 
    {
757
 
      svn_string_t *msg = svn_string_create("", pool);
758
 
 
759
 
      /* If we can do so, try to pop up $EDITOR to fetch a log message. */
760
 
      if (non_interactive)
761
 
        {
762
 
          return svn_error_create
763
 
            (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
764
 
             _("Cannot invoke editor to get log message "
765
 
               "when non-interactive"));
766
 
        }
767
 
      else
768
 
        {
769
 
          SVN_ERR(svn_cmdline__edit_string_externally(
770
 
                      &msg, NULL, NULL, "", msg, "svnmucc-commit", config,
771
 
                      TRUE, NULL, apr_hash_pool_get(revprops)));
772
 
        }
773
 
 
774
 
      svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, msg);
775
 
    }
776
 
 
777
 
  SVN_ERR(create_ra_callbacks(&ra_callbacks, username, password, config_dir,
778
 
                              cfg_config, non_interactive, trust_server_cert,
779
 
                              no_auth_cache, pool));
780
 
  SVN_ERR(svn_ra_open4(&session, NULL, anchor, NULL, ra_callbacks,
781
 
                       NULL, config, pool));
782
 
  /* Open, then reparent to avoid AUTHZ errors when opening the reposroot */
783
 
  SVN_ERR(svn_ra_open4(&aux_session, NULL, anchor, NULL, ra_callbacks,
784
 
                       NULL, config, pool));
785
 
  SVN_ERR(svn_ra_get_repos_root2(aux_session, &repos_root, pool));
786
 
  SVN_ERR(svn_ra_reparent(aux_session, repos_root, pool));
787
 
  SVN_ERR(svn_ra_get_latest_revnum(session, &head, pool));
788
 
 
789
 
  /* Reparent to ANCHOR's dir, if ANCHOR is not a directory. */
790
 
  {
791
 
    svn_node_kind_t kind;
792
 
 
793
 
    SVN_ERR(svn_ra_check_path(aux_session,
794
 
                              svn_uri_skip_ancestor(repos_root, anchor, pool),
795
 
                              head, &kind, pool));
796
 
    if (kind != svn_node_dir)
797
 
      {
798
 
        anchor = svn_uri_dirname(anchor, pool);
799
 
        SVN_ERR(svn_ra_reparent(session, anchor, pool));
800
 
      }
801
 
  }
802
 
 
803
 
  if (SVN_IS_VALID_REVNUM(base_revision))
804
 
    {
805
 
      if (base_revision > head)
806
 
        return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
807
 
                                 "No such revision %ld (youngest is %ld)",
808
 
                                 base_revision, head);
809
 
      head = base_revision;
810
 
    }
811
 
 
812
 
  memset(&root, 0, sizeof(root));
813
 
  root.children = apr_hash_make(pool);
814
 
  root.operation = OP_OPEN;
815
 
  root.kind = svn_node_dir; /* For setting properties */
816
 
  root.prop_mods = apr_hash_make(pool);
817
 
  root.prop_dels = apr_array_make(pool, 1, sizeof(const char *));
 
144
  SVN_ERR(svn_client__mtcc_create(&mtcc, anchor,
 
145
                                  SVN_IS_VALID_REVNUM(base_revision)
 
146
                                     ? base_revision
 
147
                                     : SVN_INVALID_REVNUM,
 
148
                                  ctx, pool, iterpool));
818
149
 
819
150
  for (i = 0; i < actions->nelts; ++i)
820
151
    {
821
152
      struct action *action = APR_ARRAY_IDX(actions, i, struct action *);
822
153
      const char *path1, *path2;
 
154
      svn_node_kind_t kind;
 
155
 
 
156
      svn_pool_clear(iterpool);
 
157
 
823
158
      switch (action->action)
824
159
        {
825
160
        case ACTION_MV:
826
161
          path1 = subtract_anchor(anchor, action->path[0], pool);
827
162
          path2 = subtract_anchor(anchor, action->path[1], pool);
828
 
          SVN_ERR(build(ACTION_RM, path1, NULL,
829
 
                        SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
830
 
                        session, &root, pool));
831
 
          SVN_ERR(build(ACTION_CP, path2, action->path[0],
832
 
                        head, NULL, NULL, NULL, head, anchor,
833
 
                        session, &root, pool));
 
163
          SVN_ERR(svn_client__mtcc_add_move(path1, path2, mtcc, iterpool));
834
164
          break;
835
165
        case ACTION_CP:
 
166
          path1 = subtract_anchor(anchor, action->path[0], pool);
836
167
          path2 = subtract_anchor(anchor, action->path[1], pool);
837
 
          if (action->rev == SVN_INVALID_REVNUM)
838
 
            action->rev = head;
839
 
          SVN_ERR(build(ACTION_CP, path2, action->path[0],
840
 
                        action->rev, NULL, NULL, NULL, head, anchor,
841
 
                        session, &root, pool));
 
168
          SVN_ERR(svn_client__mtcc_add_copy(path1, action->rev, path2,
 
169
                                            mtcc, iterpool));
842
170
          break;
843
171
        case ACTION_RM:
844
172
          path1 = subtract_anchor(anchor, action->path[0], pool);
845
 
          SVN_ERR(build(ACTION_RM, path1, NULL,
846
 
                        SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
847
 
                        session, &root, pool));
 
173
          SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, iterpool));
848
174
          break;
849
175
        case ACTION_MKDIR:
850
176
          path1 = subtract_anchor(anchor, action->path[0], pool);
851
 
          SVN_ERR(build(ACTION_MKDIR, path1, action->path[0],
852
 
                        SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
853
 
                        session, &root, pool));
 
177
          SVN_ERR(svn_client__mtcc_add_mkdir(path1, mtcc, iterpool));
854
178
          break;
855
179
        case ACTION_PUT:
856
180
          path1 = subtract_anchor(anchor, action->path[0], pool);
857
 
          SVN_ERR(build(ACTION_PUT, path1, action->path[0],
858
 
                        SVN_INVALID_REVNUM, NULL, NULL, action->path[1],
859
 
                        head, anchor, session, &root, pool));
 
181
          SVN_ERR(svn_client__mtcc_check_path(&kind, path1, TRUE, mtcc, pool));
 
182
 
 
183
          if (kind == svn_node_dir)
 
184
            {
 
185
              SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, pool));
 
186
              kind = svn_node_none;
 
187
            }
 
188
 
 
189
          {
 
190
            svn_stream_t *src;
 
191
 
 
192
            if (strcmp(action->path[1], "-") != 0)
 
193
              SVN_ERR(svn_stream_open_readonly(&src, action->path[1],
 
194
                                               pool, iterpool));
 
195
            else
 
196
              SVN_ERR(svn_stream_for_stdin(&src, pool));
 
197
 
 
198
 
 
199
            if (kind == svn_node_file)
 
200
              SVN_ERR(svn_client__mtcc_add_update_file(path1, src, NULL,
 
201
                                                       NULL, NULL,
 
202
                                                       mtcc, iterpool));
 
203
            else if (kind == svn_node_none)
 
204
              SVN_ERR(svn_client__mtcc_add_add_file(path1, src, NULL,
 
205
                                                    mtcc, iterpool));
 
206
          }
860
207
          break;
861
208
        case ACTION_PROPSET:
862
209
        case ACTION_PROPDEL:
863
210
          path1 = subtract_anchor(anchor, action->path[0], pool);
864
 
          SVN_ERR(build(action->action, path1, action->path[0],
865
 
                        SVN_INVALID_REVNUM,
866
 
                        action->prop_name, action->prop_value,
867
 
                        NULL, head, anchor, session, &root, pool));
 
211
          SVN_ERR(svn_client__mtcc_add_propset(path1, action->prop_name,
 
212
                                               action->prop_value, FALSE,
 
213
                                               mtcc, iterpool));
868
214
          break;
869
215
        case ACTION_PROPSETF:
870
216
        default:
872
218
        }
873
219
    }
874
220
 
875
 
  SVN_ERR(svn_ra__register_editor_shim_callbacks(session,
876
 
                            get_shim_callbacks(aux_session, head, pool)));
877
 
  SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &editor_baton, revprops,
878
 
                                    commit_callback, NULL, NULL, FALSE, pool));
879
 
 
880
 
  SVN_ERR(editor->open_root(editor_baton, head, pool, &root.baton));
881
 
  err = change_props(editor, root.baton, &root, pool);
882
 
  if (!err)
883
 
    err = drive(&root, head, editor, pool);
884
 
  if (!err)
885
 
    err = editor->close_directory(root.baton, pool);
886
 
  if (!err)
887
 
    err = editor->close_edit(editor_baton, pool);
888
 
 
889
 
  if (err)
890
 
    err = svn_error_compose_create(err,
891
 
                                   editor->abort_edit(editor_baton, pool));
892
 
 
893
 
  return err;
 
221
  err = svn_client__mtcc_commit(revprops, commit_callback, NULL,
 
222
                                mtcc, iterpool);
 
223
 
 
224
  svn_pool_destroy(iterpool);
 
225
  return svn_error_trace(err);
894
226
}
895
227
 
896
228
static svn_error_t *
920
252
}
921
253
 
922
254
static void
923
 
usage(apr_pool_t *pool, int exit_val)
924
 
{
925
 
  FILE *stream = exit_val == EXIT_SUCCESS ? stdout : stderr;
 
255
usage(apr_pool_t *pool)
 
256
{
 
257
  svn_error_clear(svn_cmdline_fprintf
 
258
                  (stderr, pool, _("Type 'svnmucc --help' for usage.\n")));
 
259
}
 
260
 
 
261
/* Print a usage message on STREAM. */
 
262
static void
 
263
help(FILE *stream, apr_pool_t *pool)
 
264
{
926
265
  svn_error_clear(svn_cmdline_fputs(
927
 
    _("Subversion multiple URL command client\n"
928
 
      "usage: svnmucc ACTION...\n"
 
266
    _("usage: svnmucc ACTION...\n"
 
267
      "Subversion multiple URL command client.\n"
 
268
      "Type 'svnmucc --version' to see the program version and RA modules.\n"
929
269
      "\n"
930
270
      "  Perform one or more Subversion repository URL-based ACTIONs, committing\n"
931
271
      "  the result as a (single) new revision.\n"
955
295
      "                           prompt only if standard input is a terminal)\n"
956
296
      "  --force-interactive    : do interactive prompting even if standard\n"
957
297
      "                           input is not a terminal\n"
958
 
      "  --trust-server-cert    : accept SSL server certificates from unknown\n"
959
 
      "                           certificate authorities without prompting (but\n"
960
 
      "                           only with '--non-interactive')\n"
 
298
      "  --trust-server-cert    : deprecated;\n"
 
299
      "                           same as --trust-server-cert-failures=unknown-ca\n"
 
300
      "  --trust-server-cert-failures ARG\n"
 
301
      "                           with --non-interactive, accept SSL server\n"
 
302
      "                           certificates with failures; ARG is comma-separated\n"
 
303
      "                           list of 'unknown-ca' (Unknown Authority),\n"
 
304
      "                           'cn-mismatch' (Hostname mismatch), 'expired'\n"
 
305
      "                           (Expired certificate),'not-yet-valid' (Not yet\n"
 
306
      "                           valid certificate) and 'other' (all other not\n"
 
307
      "                           separately classified certificate errors).\n"
961
308
      "  -X [--extra-args] ARG  : append arguments from file ARG (one per line;\n"
962
309
      "                           use \"-\" to read from standard input)\n"
963
310
      "  --config-dir ARG       : use ARG to override the config directory\n"
965
312
      "  --no-auth-cache        : do not cache authentication tokens\n"
966
313
      "  --version              : print version information\n"),
967
314
                  stream, pool));
968
 
  svn_pool_destroy(pool);
969
 
  exit(exit_val);
970
315
}
971
316
 
972
 
static void
973
 
insufficient(apr_pool_t *pool)
 
317
static svn_error_t *
 
318
insufficient(void)
974
319
{
975
 
  handle_error(svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
976
 
                                "insufficient arguments"),
977
 
               pool);
 
320
  return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
 
321
                          "insufficient arguments");
978
322
}
979
323
 
980
324
static svn_error_t *
981
 
display_version(apr_getopt_t *os, apr_pool_t *pool)
 
325
display_version(apr_pool_t *pool)
982
326
{
983
327
  const char *ra_desc_start
984
328
    = "The following repository access (RA) modules are available:\n\n";
987
331
  version_footer = svn_stringbuf_create(ra_desc_start, pool);
988
332
  SVN_ERR(svn_ra_print_modules(version_footer, pool));
989
333
 
990
 
  SVN_ERR(svn_opt_print_help4(os, "svnmucc", TRUE, FALSE, FALSE,
 
334
  SVN_ERR(svn_opt_print_help4(NULL, "svnmucc", TRUE, FALSE, FALSE,
991
335
                              version_footer->data,
992
336
                              NULL, NULL, NULL, NULL, NULL, pool));
993
337
 
1005
349
                            "exclusive"));
1006
350
}
1007
351
 
1008
 
/* Ensure that the REVPROPS hash contains a command-line-provided log
1009
 
   message, if any, and that there was but one source of such a thing
1010
 
   provided on that command-line.  */
 
352
/* Obtain the log message from multiple sources, producing an error
 
353
   if there are multiple sources. Store the result in *FINAL_MESSAGE.  */
1011
354
static svn_error_t *
1012
 
sanitize_log_sources(apr_hash_t *revprops,
 
355
sanitize_log_sources(const char **final_message,
1013
356
                     const char *message,
1014
 
                     svn_stringbuf_t *filedata)
 
357
                     apr_hash_t *revprops,
 
358
                     svn_stringbuf_t *filedata,
 
359
                     apr_pool_t *result_pool,
 
360
                     apr_pool_t *scratch_pool)
1015
361
{
1016
 
  apr_pool_t *hash_pool = apr_hash_pool_get(revprops);
 
362
  svn_string_t *msg;
1017
363
 
 
364
  *final_message = NULL;
1018
365
  /* If we already have a log message in the revprop hash, then just
1019
366
     make sure the user didn't try to also use -m or -F.  Otherwise,
1020
367
     we need to consult -m or -F to find a log message, if any. */
1021
 
  if (svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))
 
368
  msg = svn_hash_gets(revprops, SVN_PROP_REVISION_LOG);
 
369
  if (msg)
1022
370
    {
1023
371
      if (filedata || message)
1024
372
        return mutually_exclusive_logs_error();
 
373
 
 
374
      *final_message = apr_pstrdup(result_pool, msg->data);
 
375
 
 
376
      /* Will be re-added by libsvn_client */
 
377
      svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, NULL);
1025
378
    }
1026
379
  else if (filedata)
1027
380
    {
1028
381
      if (message)
1029
382
        return mutually_exclusive_logs_error();
1030
383
 
1031
 
      SVN_ERR(svn_utf_cstring_to_utf8(&message, filedata->data, hash_pool));
1032
 
      svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
1033
 
                    svn_stringbuf__morph_into_string(filedata));
 
384
      *final_message = apr_pstrdup(result_pool, filedata->data);
1034
385
    }
1035
386
  else if (message)
1036
387
    {
1037
 
      svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
1038
 
                    svn_string_create(message, hash_pool));
 
388
      *final_message = apr_pstrdup(result_pool, message);
1039
389
    }
1040
390
 
1041
391
  return SVN_NO_ERROR;
1042
392
}
1043
393
 
1044
 
int
1045
 
main(int argc, const char **argv)
1046
 
{
1047
 
  apr_pool_t *pool = init("svnmucc");
 
394
/* Baton for log_message_func */
 
395
struct log_message_baton
 
396
{
 
397
  svn_boolean_t non_interactive;
 
398
  const char *log_message;
 
399
  svn_client_ctx_t *ctx;
 
400
};
 
401
 
 
402
/* Implements svn_client_get_commit_log3_t */
 
403
static svn_error_t *
 
404
log_message_func(const char **log_msg,
 
405
                 const char **tmp_file,
 
406
                 const apr_array_header_t *commit_items,
 
407
                 void *baton,
 
408
                 apr_pool_t *pool)
 
409
{
 
410
  struct log_message_baton *lmb = baton;
 
411
 
 
412
  *tmp_file = NULL;
 
413
 
 
414
  if (lmb->log_message)
 
415
    {
 
416
      svn_string_t *message = svn_string_create(lmb->log_message, pool);
 
417
 
 
418
      SVN_ERR_W(svn_subst_translate_string2(&message, NULL, NULL,
 
419
                                            message, NULL, FALSE,
 
420
                                            pool, pool),
 
421
                _("Error normalizing log message to internal format"));
 
422
 
 
423
      *log_msg = message->data;
 
424
 
 
425
      return SVN_NO_ERROR;
 
426
    }
 
427
 
 
428
  if (lmb->non_interactive)
 
429
    {
 
430
      return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
 
431
                              _("Cannot invoke editor to get log message "
 
432
                                "when non-interactive"));
 
433
    }
 
434
  else
 
435
    {
 
436
      svn_string_t *msg = svn_string_create("", pool);
 
437
 
 
438
      SVN_ERR(svn_cmdline__edit_string_externally(
 
439
                      &msg, NULL, NULL, "", msg, "svnmucc-commit",
 
440
                      lmb->ctx->config, TRUE, NULL, pool));
 
441
 
 
442
      if (msg && msg->data)
 
443
        *log_msg = msg->data;
 
444
      else
 
445
        *log_msg = NULL;
 
446
 
 
447
      return SVN_NO_ERROR;
 
448
    }
 
449
}
 
450
 
 
451
/*
 
452
 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
 
453
 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
 
454
 * return SVN_NO_ERROR.
 
455
 */
 
456
static svn_error_t *
 
457
sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
 
458
{
1048
459
  apr_array_header_t *actions = apr_array_make(pool, 1,
1049
460
                                               sizeof(struct action *));
1050
461
  const char *anchor = NULL;
1058
469
    with_revprop_opt,
1059
470
    non_interactive_opt,
1060
471
    force_interactive_opt,
1061
 
    trust_server_cert_opt
 
472
    trust_server_cert_opt,
 
473
    trust_server_cert_failures_opt,
1062
474
  };
1063
475
  static const apr_getopt_option_t options[] = {
1064
476
    {"message", 'm', 1, ""},
1074
486
    {"non-interactive", non_interactive_opt, 0, ""},
1075
487
    {"force-interactive", force_interactive_opt, 0, ""},
1076
488
    {"trust-server-cert", trust_server_cert_opt, 0, ""},
 
489
    {"trust-server-cert-failures", trust_server_cert_failures_opt, 1, ""},
1077
490
    {"config-dir", config_dir_opt, 1, ""},
1078
491
    {"config-option",  config_inline_opt, 1, ""},
1079
492
    {"no-auth-cache",  no_auth_cache_opt, 0, ""},
1088
501
  apr_array_header_t *config_options;
1089
502
  svn_boolean_t non_interactive = FALSE;
1090
503
  svn_boolean_t force_interactive = FALSE;
1091
 
  svn_boolean_t trust_server_cert = FALSE;
 
504
  svn_boolean_t trust_unknown_ca = FALSE;
 
505
  svn_boolean_t trust_cn_mismatch = FALSE;
 
506
  svn_boolean_t trust_expired = FALSE;
 
507
  svn_boolean_t trust_not_yet_valid = FALSE;
 
508
  svn_boolean_t trust_other_failure = FALSE;
1092
509
  svn_boolean_t no_auth_cache = FALSE;
 
510
  svn_boolean_t show_version = FALSE;
 
511
  svn_boolean_t show_help = FALSE;
1093
512
  svn_revnum_t base_revision = SVN_INVALID_REVNUM;
1094
513
  apr_array_header_t *action_args;
1095
514
  apr_hash_t *revprops = apr_hash_make(pool);
 
515
  apr_hash_t *cfg_hash;
 
516
  svn_config_t *cfg_config;
 
517
  svn_client_ctx_t *ctx;
 
518
  struct log_message_baton lmb;
1096
519
  int i;
1097
520
 
 
521
  /* Check library versions */
 
522
  SVN_ERR(check_lib_versions());
 
523
 
1098
524
  config_options = apr_array_make(pool, 0,
1099
525
                                  sizeof(svn_cmdline__config_argument_t*));
1100
526
 
1110
536
      if (APR_STATUS_IS_EOF(status))
1111
537
        break;
1112
538
      if (status != APR_SUCCESS)
1113
 
        handle_error(svn_error_wrap_apr(status, "getopt failure"), pool);
 
539
        {
 
540
          usage(pool);
 
541
          *exit_code = EXIT_FAILURE;
 
542
          return SVN_NO_ERROR;
 
543
        }
1114
544
      switch(opt)
1115
545
        {
1116
546
        case 'm':
1117
 
          err = svn_utf_cstring_to_utf8(&message, arg, pool);
1118
 
          if (err)
1119
 
            handle_error(err, pool);
 
547
          SVN_ERR(svn_utf_cstring_to_utf8(&message, arg, pool));
1120
548
          break;
1121
549
        case 'F':
1122
550
          {
1123
551
            const char *arg_utf8;
1124
 
            err = svn_utf_cstring_to_utf8(&arg_utf8, arg, pool);
1125
 
            if (! err)
1126
 
              err = svn_stringbuf_from_file2(&filedata, arg, pool);
1127
 
            if (err)
1128
 
              handle_error(err, pool);
 
552
            SVN_ERR(svn_utf_cstring_to_utf8(&arg_utf8, arg, pool));
 
553
            SVN_ERR(svn_stringbuf_from_file2(&filedata, arg, pool));
1129
554
          }
1130
555
          break;
1131
556
        case 'u':
1135
560
          password = apr_pstrdup(pool, arg);
1136
561
          break;
1137
562
        case 'U':
1138
 
          err = svn_utf_cstring_to_utf8(&root_url, arg, pool);
1139
 
          if (err)
1140
 
            handle_error(err, pool);
 
563
          SVN_ERR(svn_utf_cstring_to_utf8(&root_url, arg, pool));
1141
564
          if (! svn_path_is_url(root_url))
1142
 
            handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1143
 
                                           "'%s' is not a URL\n", root_url),
1144
 
                         pool);
 
565
            return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
 
566
                                     "'%s' is not a URL\n", root_url);
1145
567
          root_url = sanitize_url(root_url, pool);
1146
568
          break;
1147
569
        case 'r':
1148
570
          {
 
571
            const char *saved_arg = arg;
1149
572
            char *digits_end = NULL;
 
573
            while (*arg == 'r')
 
574
              arg++;
1150
575
            base_revision = strtol(arg, &digits_end, 10);
1151
576
            if ((! SVN_IS_VALID_REVNUM(base_revision))
1152
577
                || (! digits_end)
1153
578
                || *digits_end)
1154
 
              handle_error(svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR,
1155
 
                                            NULL, "Invalid revision number"),
1156
 
                           pool);
 
579
              return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
 
580
                                       _("Invalid revision number '%s'"),
 
581
                                       saved_arg);
1157
582
          }
1158
583
          break;
1159
584
        case with_revprop_opt:
1160
 
          err = svn_opt_parse_revprop(&revprops, arg, pool);
1161
 
          if (err != SVN_NO_ERROR)
1162
 
            handle_error(err, pool);
 
585
          SVN_ERR(svn_opt_parse_revprop(&revprops, arg, pool));
1163
586
          break;
1164
587
        case 'X':
1165
588
          extra_args_file = apr_pstrdup(pool, arg);
1170
593
        case force_interactive_opt:
1171
594
          force_interactive = TRUE;
1172
595
          break;
1173
 
        case trust_server_cert_opt:
1174
 
          trust_server_cert = TRUE;
 
596
        case trust_server_cert_opt: /* backward compat */
 
597
          trust_unknown_ca = TRUE;
 
598
          break;
 
599
        case trust_server_cert_failures_opt:
 
600
          SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
 
601
          SVN_ERR(svn_cmdline__parse_trust_options(
 
602
                      &trust_unknown_ca,
 
603
                      &trust_cn_mismatch,
 
604
                      &trust_expired,
 
605
                      &trust_not_yet_valid,
 
606
                      &trust_other_failure,
 
607
                      opt_arg, pool));
1175
608
          break;
1176
609
        case config_dir_opt:
1177
 
          err = svn_utf_cstring_to_utf8(&config_dir, arg, pool);
1178
 
          if (err)
1179
 
            handle_error(err, pool);
 
610
          SVN_ERR(svn_utf_cstring_to_utf8(&config_dir, arg, pool));
1180
611
          break;
1181
612
        case config_inline_opt:
1182
 
          err = svn_utf_cstring_to_utf8(&opt_arg, arg, pool);
1183
 
          if (err)
1184
 
            handle_error(err, pool);
1185
 
 
1186
 
          err = svn_cmdline__parse_config_option(config_options, opt_arg,
1187
 
                                                 pool);
1188
 
          if (err)
1189
 
            handle_error(err, pool);
 
613
          SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
 
614
          SVN_ERR(svn_cmdline__parse_config_option(config_options, opt_arg,
 
615
                                                   "svnmucc: ", 
 
616
                                                   pool));
1190
617
          break;
1191
618
        case no_auth_cache_opt:
1192
619
          no_auth_cache = TRUE;
1193
620
          break;
1194
621
        case version_opt:
1195
 
          SVN_INT_ERR(display_version(opts, pool));
1196
 
          exit(EXIT_SUCCESS);
 
622
          show_version = TRUE;
1197
623
          break;
1198
624
        case 'h':
1199
625
        case '?':
1200
 
          usage(pool, EXIT_SUCCESS);
 
626
          show_help = TRUE;
1201
627
          break;
1202
628
        }
1203
629
    }
1204
630
 
 
631
  if (show_help)
 
632
    {
 
633
      help(stdout, pool);
 
634
      return SVN_NO_ERROR;
 
635
    }
 
636
 
 
637
  if (show_version)
 
638
    {
 
639
      SVN_ERR(display_version(pool));
 
640
      return SVN_NO_ERROR;
 
641
    }
 
642
 
1205
643
  if (non_interactive && force_interactive)
1206
644
    {
1207
 
      err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1208
 
                             _("--non-interactive and --force-interactive "
1209
 
                               "are mutually exclusive"));
1210
 
      return svn_cmdline_handle_exit_error(err, pool, "svnmucc: ");
 
645
      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
 
646
                              _("--non-interactive and --force-interactive "
 
647
                                "are mutually exclusive"));
1211
648
    }
1212
649
  else
1213
650
    non_interactive = !svn_cmdline__be_interactive(non_interactive,
1214
651
                                                   force_interactive);
1215
652
 
1216
 
  if (trust_server_cert && !non_interactive)
 
653
  if (!non_interactive)
1217
654
    {
1218
 
      err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1219
 
                             _("--trust-server-cert requires "
1220
 
                               "--non-interactive"));
1221
 
      return svn_cmdline_handle_exit_error(err, pool, "svnmucc: ");
 
655
      if (trust_unknown_ca || trust_cn_mismatch || trust_expired
 
656
          || trust_not_yet_valid || trust_other_failure)
 
657
        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
 
658
                                _("--trust-server-cert-failures requires "
 
659
                                  "--non-interactive"));
1222
660
    }
1223
661
 
1224
 
  /* Make sure we have a log message to use. */
1225
 
  err = sanitize_log_sources(revprops, message, filedata);
1226
 
  if (err)
1227
 
    handle_error(err, pool);
1228
 
 
1229
662
  /* Copy the rest of our command-line arguments to an array,
1230
663
     UTF-8-ing them along the way. */
1231
664
  action_args = apr_array_make(pool, opts->argc, sizeof(const char *));
1232
665
  while (opts->ind < opts->argc)
1233
666
    {
1234
667
      const char *arg = opts->argv[opts->ind++];
1235
 
      if ((err = svn_utf_cstring_to_utf8(&(APR_ARRAY_PUSH(action_args,
1236
 
                                                          const char *)),
1237
 
                                         arg, pool)))
1238
 
        handle_error(err, pool);
 
668
      SVN_ERR(svn_utf_cstring_to_utf8(&APR_ARRAY_PUSH(action_args,
 
669
                                                      const char *),
 
670
                                      arg, pool));
1239
671
    }
1240
672
 
1241
673
  /* If there are extra arguments in a supplementary file, tack those
1245
677
      const char *extra_args_file_utf8;
1246
678
      svn_stringbuf_t *contents, *contents_utf8;
1247
679
 
1248
 
      err = svn_utf_cstring_to_utf8(&extra_args_file_utf8,
1249
 
                                    extra_args_file, pool);
1250
 
      if (! err)
1251
 
        err = svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool);
1252
 
      if (! err)
1253
 
        err = svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool);
1254
 
      if (err)
1255
 
        handle_error(err, pool);
 
680
      SVN_ERR(svn_utf_cstring_to_utf8(&extra_args_file_utf8,
 
681
                                      extra_args_file, pool));
 
682
      SVN_ERR(svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool));
 
683
      SVN_ERR(svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool));
1256
684
      svn_cstring_split_append(action_args, contents_utf8->data, "\n\r",
1257
685
                               FALSE, pool);
1258
686
    }
1259
687
 
 
688
  /* Now initialize the client context */
 
689
 
 
690
  err = svn_config_get_config(&cfg_hash, config_dir, pool);
 
691
  if (err)
 
692
    {
 
693
      /* Fallback to default config if the config directory isn't readable
 
694
         or is not a directory. */
 
695
      if (APR_STATUS_IS_EACCES(err->apr_err)
 
696
          || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
 
697
        {
 
698
          svn_handle_warning2(stderr, err, "svnmucc: ");
 
699
          svn_error_clear(err);
 
700
 
 
701
          SVN_ERR(svn_config__get_default_config(&cfg_hash, pool));
 
702
        }
 
703
      else
 
704
        return err;
 
705
    }
 
706
 
 
707
  if (config_options)
 
708
    {
 
709
      svn_error_clear(
 
710
          svn_cmdline__apply_config_options(cfg_hash, config_options,
 
711
                                            "svnmucc: ", "--config-option"));
 
712
    }
 
713
 
 
714
  SVN_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
 
715
 
 
716
  cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);
 
717
  SVN_ERR(svn_cmdline_create_auth_baton2(
 
718
            &ctx->auth_baton,
 
719
            non_interactive,
 
720
            username,
 
721
            password,
 
722
            config_dir,
 
723
            no_auth_cache,
 
724
            trust_unknown_ca,
 
725
            trust_cn_mismatch,
 
726
            trust_expired,
 
727
            trust_not_yet_valid,
 
728
            trust_other_failure,
 
729
            cfg_config,
 
730
            ctx->cancel_func,
 
731
            ctx->cancel_baton,
 
732
            pool));
 
733
 
 
734
  lmb.non_interactive = non_interactive;
 
735
  lmb.ctx = ctx;
 
736
    /* Make sure we have a log message to use. */
 
737
  SVN_ERR(sanitize_log_sources(&lmb.log_message, message, revprops, filedata,
 
738
                               pool, pool));
 
739
 
 
740
  ctx->log_msg_func3 = log_message_func;
 
741
  ctx->log_msg_baton3 = &lmb;
 
742
 
1260
743
  /* Now, we iterate over the combined set of arguments -- our actions. */
1261
744
  for (i = 0; i < action_args->nelts; )
1262
745
    {
1283
766
        action->action = ACTION_PROPDEL;
1284
767
      else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h")
1285
768
               || ! strcmp(action_string, "help"))
1286
 
        usage(pool, EXIT_SUCCESS);
 
769
        {
 
770
          help(stdout, pool);
 
771
          return SVN_NO_ERROR;
 
772
        }
1287
773
      else
1288
 
        handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1289
 
                                       "'%s' is not an action\n",
1290
 
                                       action_string), pool);
 
774
        return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
 
775
                                 "'%s' is not an action\n",
 
776
                                 action_string);
1291
777
      if (++i == action_args->nelts)
1292
 
        insufficient(pool);
 
778
        return insufficient();
1293
779
 
1294
780
      /* For copies, there should be a revision number next. */
1295
781
      if (action->action == ACTION_CP)
1308
794
 
1309
795
              action->rev = strtol(rev_str, &end, 0);
1310
796
              if (*end)
1311
 
                handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1312
 
                                               "'%s' is not a revision\n",
1313
 
                                               rev_str), pool);
 
797
                return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
 
798
                                         "'%s' is not a revision\n",
 
799
                                         rev_str);
1314
800
            }
1315
801
          if (++i == action_args->nelts)
1316
 
            insufficient(pool);
 
802
            return insufficient();
1317
803
        }
1318
804
      else
1319
805
        {
1327
813
            svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
1328
814
                                                    const char *), pool);
1329
815
          if (++i == action_args->nelts)
1330
 
            insufficient(pool);
 
816
            return insufficient();
1331
817
        }
1332
818
 
1333
819
      /* For propset, propsetf, and propdel, a property name (and
1338
824
        {
1339
825
          action->prop_name = APR_ARRAY_IDX(action_args, i, const char *);
1340
826
          if (++i == action_args->nelts)
1341
 
            insufficient(pool);
 
827
            return insufficient();
1342
828
 
1343
829
          if (action->action == ACTION_PROPDEL)
1344
830
            {
1350
836
                svn_string_create(APR_ARRAY_IDX(action_args, i,
1351
837
                                                const char *), pool);
1352
838
              if (++i == action_args->nelts)
1353
 
                insufficient(pool);
 
839
                return insufficient();
1354
840
            }
1355
841
          else
1356
842
            {
1359
845
                                                        const char *), pool);
1360
846
 
1361
847
              if (++i == action_args->nelts)
1362
 
                insufficient(pool);
 
848
                return insufficient();
1363
849
 
1364
 
              err = read_propvalue_file(&(action->prop_value),
1365
 
                                        propval_file, pool);
1366
 
              if (err)
1367
 
                handle_error(err, pool);
 
850
              SVN_ERR(read_propvalue_file(&(action->prop_value),
 
851
                                          propval_file, pool));
1368
852
 
1369
853
              action->action = ACTION_PROPSET;
1370
854
            }
1373
857
              && svn_prop_needs_translation(action->prop_name))
1374
858
            {
1375
859
              svn_string_t *translated_value;
1376
 
              err = svn_subst_translate_string2(&translated_value, NULL,
1377
 
                                                NULL, action->prop_value, NULL,
1378
 
                                                FALSE, pool, pool);
1379
 
              if (err)
1380
 
                handle_error(
1381
 
                    svn_error_quick_wrap(err,
1382
 
                                         "Error normalizing property value"),
1383
 
                    pool);
 
860
              SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
 
861
                                                    NULL, action->prop_value,
 
862
                                                    NULL, FALSE, pool, pool),
 
863
                        "Error normalizing property value");
1384
864
              action->prop_value = translated_value;
1385
865
            }
1386
866
        }
1408
888
          if (! svn_path_is_url(url))
1409
889
            {
1410
890
              if (! root_url)
1411
 
                handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1412
 
                                               "'%s' is not a URL, and "
1413
 
                                               "--root-url (-U) not provided\n",
1414
 
                                               url), pool);
 
891
                return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
 
892
                                         "'%s' is not a URL, and "
 
893
                                         "--root-url (-U) not provided\n",
 
894
                                         url);
1415
895
              /* ### These relpaths are already URI-encoded. */
1416
896
              url = apr_pstrcat(pool, root_url, "/",
1417
897
                                svn_relpath_canonicalize(url, pool),
1418
 
                                (char *)NULL);
 
898
                                SVN_VA_NULL);
1419
899
            }
1420
900
          url = sanitize_url(url, pool);
1421
901
          action->path[j] = url;
1433
913
            {
1434
914
              anchor = svn_uri_get_longest_ancestor(anchor, url, pool);
1435
915
              if (!anchor || !anchor[0])
1436
 
                handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1437
 
                                               "URLs in the action list do not "
1438
 
                                               "share a common ancestor"),
1439
 
                             pool);
 
916
                return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
 
917
                                         "URLs in the action list do not "
 
918
                                         "share a common ancestor");
1440
919
            }
1441
920
 
1442
921
          if ((++i == action_args->nelts) && (j + 1 < num_url_args))
1443
 
            insufficient(pool);
 
922
            return insufficient();
1444
923
        }
 
924
 
1445
925
      APR_ARRAY_PUSH(actions, struct action *) = action;
1446
926
    }
1447
927
 
1448
928
  if (! actions->nelts)
1449
 
    usage(pool, EXIT_FAILURE);
 
929
    {
 
930
      *exit_code = EXIT_FAILURE;
 
931
      help(stderr, pool);
 
932
      return SVN_NO_ERROR;
 
933
    }
1450
934
 
1451
 
  if ((err = execute(actions, anchor, revprops, username, password,
1452
 
                     config_dir, config_options, non_interactive,
1453
 
                     trust_server_cert, no_auth_cache, base_revision, pool)))
 
935
  if ((err = execute(actions, anchor, revprops, base_revision, ctx, pool)))
1454
936
    {
1455
937
      if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
1456
938
        err = svn_error_quick_wrap(err,
1457
939
                                   _("Authentication failed and interactive"
1458
940
                                     " prompting is disabled; see the"
1459
941
                                     " --force-interactive option"));
1460
 
      handle_error(err, pool);
1461
 
    }
1462
 
 
1463
 
  /* Ensure that stdout is flushed, so the user will see all results. */
1464
 
  svn_error_clear(svn_cmdline_fflush(stdout));
 
942
      return err;
 
943
    }
 
944
 
 
945
  return SVN_NO_ERROR;
 
946
}
 
947
 
 
948
int
 
949
main(int argc, const char *argv[])
 
950
{
 
951
  apr_pool_t *pool;
 
952
  int exit_code = EXIT_SUCCESS;
 
953
  svn_error_t *err;
 
954
 
 
955
  /* Initialize the app. */
 
956
  if (svn_cmdline_init("svnmucc", stderr) != EXIT_SUCCESS)
 
957
    return EXIT_FAILURE;
 
958
 
 
959
  /* Create our top-level pool.  Use a separate mutexless allocator,
 
960
   * given this application is single threaded.
 
961
   */
 
962
  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
 
963
 
 
964
  err = sub_main(&exit_code, argc, argv, pool);
 
965
 
 
966
  /* Flush stdout and report if it fails. It would be flushed on exit anyway
 
967
     but this makes sure that output is not silently lost if it fails. */
 
968
  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
 
969
 
 
970
  if (err)
 
971
    {
 
972
      exit_code = EXIT_FAILURE;
 
973
      svn_cmdline_handle_exit_error(err, NULL, "svnmucc: ");
 
974
    }
1465
975
 
1466
976
  svn_pool_destroy(pool);
1467
 
  return EXIT_SUCCESS;
 
977
  return exit_code;
1468
978
}