1/* error.c:  common exception handling for Subversion
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23
24
25#include <stdarg.h>
26
27#include <apr_general.h>
28#include <apr_pools.h>
29#include <apr_strings.h>
30
31#include <zlib.h>
32
33#ifndef SVN_ERR__TRACING
34#define SVN_ERR__TRACING
35#endif
36#include "svn_cmdline.h"
37#include "svn_error.h"
38#include "svn_pools.h"
39#include "svn_utf.h"
40
41#ifdef SVN_DEBUG
42/* XXX FIXME: These should be protected by a thread mutex.
43   svn_error__locate and make_error_internal should cooperate
44   in locking and unlocking it. */
45
46/* XXX TODO: Define mutex here #if APR_HAS_THREADS */
47static const char * volatile error_file = NULL;
48static long volatile error_line = -1;
49
50/* file_line for the non-debug case. */
51static const char SVN_FILE_LINE_UNDEFINED[] = "svn:<undefined>";
52#endif /* SVN_DEBUG */
53
54#include "svn_private_config.h"
55#include "private/svn_error_private.h"
56
57
58/*
59 * Undefine the helpers for creating errors.
60 *
61 * *NOTE*: Any use of these functions in any other function may need
62 * to call svn_error__locate() because the macro that would otherwise
63 * do this is being undefined and the filename and line number will
64 * not be properly set in the static error_file and error_line
65 * variables.
66 */
67#undef svn_error_create
68#undef svn_error_createf
69#undef svn_error_quick_wrap
70#undef svn_error_quick_wrapf
71#undef svn_error_wrap_apr
72
73/* Note: Although this is a "__" function, it was historically in the
74 * public ABI, so we can never change it or remove its signature, even
75 * though it is now only used in SVN_DEBUG mode. */
76void
77svn_error__locate(const char *file, long line)
78{
79#if defined(SVN_DEBUG)
80  /* XXX TODO: Lock mutex here */
81  error_file = file;
82  error_line = line;
83#endif
84}
85
86
87/* Cleanup function for errors.  svn_error_clear () removes this so
88   errors that are properly handled *don't* hit this code. */
89static apr_status_t err_abort(void *data)
90{
91  svn_error_t *err = data;  /* For easy viewing in a debugger */
92  SVN_UNUSED(err);
93
94  if (!getenv("SVN_DBG_NO_ABORT_ON_ERROR_LEAK"))
95    abort();
96  return APR_SUCCESS;
97}
98
99
100static svn_error_t *
101make_error_internal(apr_status_t apr_err,
102                    svn_error_t *child)
103{
104  apr_pool_t *pool;
105  svn_error_t *new_error;
106
107  /* Reuse the child's pool, or create our own. */
108  if (child)
109    pool = child->pool;
110  else
111    {
112      pool = svn_pool_create(NULL);
113      if (!pool)
114        abort();
115    }
116
117  /* Create the new error structure */
118  new_error = apr_pcalloc(pool, sizeof(*new_error));
119
120  /* Fill 'er up. */
121  new_error->apr_err = apr_err;
122  new_error->child   = child;
123  new_error->pool    = pool;
124#if defined(SVN_DEBUG)
125  new_error->file    = error_file;
126  new_error->line    = error_line;
127  /* XXX TODO: Unlock mutex here */
128
129  if (! child)
130      apr_pool_cleanup_register(pool, new_error,
131                                err_abort,
132                                apr_pool_cleanup_null);
133#endif
134
135  return new_error;
136}
137
138
139
140/*** Creating and destroying errors. ***/
141
142svn_error_t *
143svn_error_create(apr_status_t apr_err,
144                 svn_error_t *child,
145                 const char *message)
146{
147  svn_error_t *err;
148
149  err = make_error_internal(apr_err, child);
150
151  if (message)
152    err->message = apr_pstrdup(err->pool, message);
153
154  return err;
155}
156
157
158svn_error_t *
159svn_error_createf(apr_status_t apr_err,
160                  svn_error_t *child,
161                  const char *fmt,
162                  ...)
163{
164  svn_error_t *err;
165  va_list ap;
166
167  err = make_error_internal(apr_err, child);
168
169  va_start(ap, fmt);
170  err->message = apr_pvsprintf(err->pool, fmt, ap);
171  va_end(ap);
172
173  return err;
174}
175
176
177svn_error_t *
178svn_error_wrap_apr(apr_status_t status,
179                   const char *fmt,
180                   ...)
181{
182  svn_error_t *err, *utf8_err;
183  va_list ap;
184  char errbuf[255];
185  const char *msg_apr, *msg;
186
187  err = make_error_internal(status, NULL);
188
189  if (fmt)
190    {
191      /* Grab the APR error message. */
192      apr_strerror(status, errbuf, sizeof(errbuf));
193      utf8_err = svn_utf_cstring_to_utf8(&msg_apr, errbuf, err->pool);
194      if (utf8_err)
195        msg_apr = NULL;
196      svn_error_clear(utf8_err);
197
198      /* Append it to the formatted message. */
199      va_start(ap, fmt);
200      msg = apr_pvsprintf(err->pool, fmt, ap);
201      va_end(ap);
202      if (msg_apr)
203        {
204          err->message = apr_pstrcat(err->pool, msg, ": ", msg_apr,
205                                     SVN_VA_NULL);
206        }
207      else
208        {
209          err->message = msg;
210        }
211    }
212
213  return err;
214}
215
216
217svn_error_t *
218svn_error_quick_wrap(svn_error_t *child, const char *new_msg)
219{
220  if (child == SVN_NO_ERROR)
221    return SVN_NO_ERROR;
222
223  return svn_error_create(child->apr_err,
224                          child,
225                          new_msg);
226}
227
228svn_error_t *
229svn_error_quick_wrapf(svn_error_t *child,
230                      const char *fmt,
231                      ...)
232{
233  svn_error_t *err;
234  va_list ap;
235
236  if (child == SVN_NO_ERROR)
237    return SVN_NO_ERROR;
238
239  err = make_error_internal(child->apr_err, child);
240
241  va_start(ap, fmt);
242  err->message = apr_pvsprintf(err->pool, fmt, ap);
243  va_end(ap);
244
245  return err;
246}
247
248/* Messages in tracing errors all point to this static string. */
249static const char error_tracing_link[] = "traced call";
250
251svn_error_t *
252svn_error__trace(const char *file, long line, svn_error_t *err)
253{
254#ifndef SVN_DEBUG
255
256  /* We shouldn't even be here, but whatever. Just return the error as-is.  */
257  return err;
258
259#else
260
261  /* Only do the work when an error occurs.  */
262  if (err)
263    {
264      svn_error_t *trace;
265      svn_error__locate(file, line);
266      trace = make_error_internal(err->apr_err, err);
267      trace->message = error_tracing_link;
268      return trace;
269    }
270  return SVN_NO_ERROR;
271
272#endif
273}
274
275
276svn_error_t *
277svn_error_compose_create(svn_error_t *err1,
278                         svn_error_t *err2)
279{
280  if (err1 && err2)
281    {
282      svn_error_compose(err1,
283                        svn_error_create(SVN_ERR_COMPOSED_ERROR, err2, NULL));
284      return err1;
285    }
286  return err1 ? err1 : err2;
287}
288
289
290void
291svn_error_compose(svn_error_t *chain, svn_error_t *new_err)
292{
293  apr_pool_t *pool = chain->pool;
294  apr_pool_t *oldpool = new_err->pool;
295
296  while (chain->child)
297    chain = chain->child;
298
299#if defined(SVN_DEBUG)
300  /* Kill existing handler since the end of the chain is going to change */
301  apr_pool_cleanup_kill(pool, chain, err_abort);
302#endif
303
304  /* Copy the new error chain into the old chain's pool. */
305  while (new_err)
306    {
307      chain->child = apr_palloc(pool, sizeof(*chain->child));
308      chain = chain->child;
309      *chain = *new_err;
310      if (chain->message)
311        chain->message = apr_pstrdup(pool, new_err->message);
312      if (chain->file)
313        chain->file = apr_pstrdup(pool, new_err->file);
314      chain->pool = pool;
315#if defined(SVN_DEBUG)
316      if (! new_err->child)
317        apr_pool_cleanup_kill(oldpool, new_err, err_abort);
318#endif
319      new_err = new_err->child;
320    }
321
322#if defined(SVN_DEBUG)
323  apr_pool_cleanup_register(pool, chain,
324                            err_abort,
325                            apr_pool_cleanup_null);
326#endif
327
328  /* Destroy the new error chain. */
329  svn_pool_destroy(oldpool);
330}
331
332svn_error_t *
333svn_error_root_cause(svn_error_t *err)
334{
335  while (err)
336    {
337      /* I don't think we can change the behavior here, but the additional
338         error chain doesn't define the root cause. Perhaps we should rev
339         this function. */
340      if (err->child /*&& err->child->apr_err != SVN_ERR_COMPOSED_ERROR*/)
341        err = err->child;
342      else
343        break;
344    }
345
346  return err;
347}
348
349svn_error_t *
350svn_error_find_cause(svn_error_t *err, apr_status_t apr_err)
351{
352  svn_error_t *child;
353
354  for (child = err; child; child = child->child)
355    if (child->apr_err == apr_err)
356      return child;
357
358  return SVN_NO_ERROR;
359}
360
361svn_error_t *
362svn_error_dup(const svn_error_t *err)
363{
364  apr_pool_t *pool;
365  svn_error_t *new_err = NULL, *tmp_err = NULL;
366
367  if (!err)
368    return SVN_NO_ERROR;
369
370  pool = svn_pool_create(NULL);
371  if (!pool)
372    abort();
373
374  for (; err; err = err->child)
375    {
376      if (! new_err)
377        {
378          new_err = apr_palloc(pool, sizeof(*new_err));
379          tmp_err = new_err;
380        }
381      else
382        {
383          tmp_err->child = apr_palloc(pool, sizeof(*tmp_err->child));
384          tmp_err = tmp_err->child;
385        }
386      *tmp_err = *err;
387      tmp_err->pool = pool;
388      if (tmp_err->message)
389        tmp_err->message = apr_pstrdup(pool, tmp_err->message);
390      if (tmp_err->file)
391        tmp_err->file = apr_pstrdup(pool, tmp_err->file);
392    }
393
394#if defined(SVN_DEBUG)
395  apr_pool_cleanup_register(pool, tmp_err,
396                            err_abort,
397                            apr_pool_cleanup_null);
398#endif
399
400  return new_err;
401}
402
403void
404svn_error_clear(svn_error_t *err)
405{
406  if (err)
407    {
408#if defined(SVN_DEBUG)
409      while (err->child)
410        err = err->child;
411      apr_pool_cleanup_kill(err->pool, err, err_abort);
412#endif
413      svn_pool_destroy(err->pool);
414    }
415}
416
417svn_boolean_t
418svn_error__is_tracing_link(const svn_error_t *err)
419{
420#ifdef SVN_ERR__TRACING
421  /* ### A strcmp()?  Really?  I think it's the best we can do unless
422     ### we add a boolean field to svn_error_t that's set only for
423     ### these "placeholder error chain" items.  Not such a bad idea,
424     ### really...  */
425  return (err && err->message && !strcmp(err->message, error_tracing_link));
426#else
427  return FALSE;
428#endif
429}
430
431svn_error_t *
432svn_error_purge_tracing(svn_error_t *err)
433{
434#ifdef SVN_ERR__TRACING
435  svn_error_t *new_err = NULL, *new_err_leaf = NULL;
436
437  if (! err)
438    return SVN_NO_ERROR;
439
440  do
441    {
442      svn_error_t *tmp_err;
443
444      /* Skip over any trace-only links. */
445      while (err && svn_error__is_tracing_link(err))
446        err = err->child;
447
448      /* The link must be a real link in the error chain, otherwise an
449         error chain with trace only links would map into SVN_NO_ERROR. */
450      if (! err)
451        return svn_error_create(
452                 SVN_ERR_ASSERTION_ONLY_TRACING_LINKS,
453                 svn_error__malfunction(TRUE, __FILE__, __LINE__,
454                                        NULL /* ### say something? */),
455                 NULL);
456
457      /* Copy the current error except for its child error pointer
458         into the new error.  Share any message and source filename
459         strings from the error. */
460      tmp_err = apr_palloc(err->pool, sizeof(*tmp_err));
461      *tmp_err = *err;
462      tmp_err->child = NULL;
463
464      /* Add a new link to the new chain (creating the chain if necessary). */
465      if (! new_err)
466        {
467          new_err = tmp_err;
468          new_err_leaf = tmp_err;
469        }
470      else
471        {
472          new_err_leaf->child = tmp_err;
473          new_err_leaf = tmp_err;
474        }
475
476      /* Advance to the next link in the original chain. */
477      err = err->child;
478    } while (err);
479
480  return new_err;
481#else  /* SVN_ERR__TRACING */
482  return err;
483#endif /* SVN_ERR__TRACING */
484}
485
486/* ### The logic around omitting (sic) apr_err= in maintainer mode is tightly
487   ### coupled to the current sole caller.*/
488static void
489print_error(svn_error_t *err, FILE *stream, const char *prefix)
490{
491  char errbuf[256];
492  const char *err_string;
493  svn_error_t *temp_err = NULL;  /* ensure initialized even if
494                                    err->file == NULL */
495  /* Pretty-print the error */
496  /* Note: we can also log errors here someday. */
497
498#ifdef SVN_DEBUG
499  /* Note: err->file is _not_ in UTF-8, because it's expanded from
500           the __FILE__ preprocessor macro. */
501  const char *file_utf8;
502
503  if (err->file
504      && !(temp_err = svn_utf_cstring_to_utf8(&file_utf8, err->file,
505                                              err->pool)))
506    svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
507                                        "%s:%ld", err->file, err->line));
508  else
509    {
510      svn_error_clear(svn_cmdline_fputs(SVN_FILE_LINE_UNDEFINED,
511                                        stream, err->pool));
512      svn_error_clear(temp_err);
513    }
514
515  {
516    const char *symbolic_name;
517    if (svn_error__is_tracing_link(err))
518      /* Skip it; the error code will be printed by the real link. */
519      svn_error_clear(svn_cmdline_fprintf(stream, err->pool, ",\n"));
520    else if ((symbolic_name = svn_error_symbolic_name(err->apr_err)))
521      svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
522                                          ": (apr_err=%s)\n", symbolic_name));
523    else
524      svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
525                                          ": (apr_err=%d)\n", err->apr_err));
526  }
527#endif /* SVN_DEBUG */
528
529  /* "traced call" */
530  if (svn_error__is_tracing_link(err))
531    {
532      /* Skip it.  We already printed the file-line coordinates. */
533    }
534  /* Only print the same APR error string once. */
535  else if (err->message)
536    {
537      svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
538                                          "%sE%06d: %s\n",
539                                          prefix, err->apr_err, err->message));
540    }
541  else
542    {
543      /* Is this a Subversion-specific error code? */
544      if ((err->apr_err > APR_OS_START_USEERR)
545          && (err->apr_err <= APR_OS_START_CANONERR))
546        err_string = svn_strerror(err->apr_err, errbuf, sizeof(errbuf));
547      /* Otherwise, this must be an APR error code. */
548      else if ((temp_err = svn_utf_cstring_to_utf8
549                (&err_string, apr_strerror(err->apr_err, errbuf,
550                                           sizeof(errbuf)), err->pool)))
551        {
552          svn_error_clear(temp_err);
553          err_string = _("Can't recode error string from APR");
554        }
555
556      svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
557                                          "%sE%06d: %s\n",
558                                          prefix, err->apr_err, err_string));
559    }
560}
561
562void
563svn_handle_error2(svn_error_t *err,
564                  FILE *stream,
565                  svn_boolean_t fatal,
566                  const char *prefix)
567{
568  /* In a long error chain, there may be multiple errors with the same
569     error code and no custom message.  We only want to print the
570     default message for that code once; printing it multiple times
571     would add no useful information.  The 'empties' array below
572     remembers the codes of empty errors already seen in the chain.
573
574     We could allocate it in err->pool, but there's no telling how
575     long err will live or how many times it will get handled.  So we
576     use a subpool. */
577  apr_pool_t *subpool;
578  apr_array_header_t *empties;
579  svn_error_t *tmp_err;
580
581  subpool = svn_pool_create(err->pool);
582  empties = apr_array_make(subpool, 0, sizeof(apr_status_t));
583
584  tmp_err = err;
585  while (tmp_err)
586    {
587      svn_boolean_t printed_already = FALSE;
588
589      if (! tmp_err->message)
590        {
591          int i;
592
593          for (i = 0; i < empties->nelts; i++)
594            {
595              if (tmp_err->apr_err == APR_ARRAY_IDX(empties, i, apr_status_t) )
596                {
597                  printed_already = TRUE;
598                  break;
599                }
600            }
601        }
602
603      if (! printed_already)
604        {
605          print_error(tmp_err, stream, prefix);
606          if (! tmp_err->message)
607            {
608              APR_ARRAY_PUSH(empties, apr_status_t) = tmp_err->apr_err;
609            }
610        }
611
612      tmp_err = tmp_err->child;
613    }
614
615  svn_pool_destroy(subpool);
616
617  fflush(stream);
618  if (fatal)
619    {
620      /* Avoid abort()s in maintainer mode. */
621      svn_error_clear(err);
622
623      /* We exit(1) here instead of abort()ing so that atexit handlers
624         get called. */
625      exit(EXIT_FAILURE);
626    }
627}
628
629void
630svn_handle_warning2(FILE *stream, const svn_error_t *err, const char *prefix)
631{
632  char buf[256];
633#ifdef SVN_DEBUG
634  const char *symbolic_name = svn_error_symbolic_name(err->apr_err);
635#endif
636
637#ifdef SVN_DEBUG
638  if (symbolic_name)
639    svn_error_clear(
640      svn_cmdline_fprintf(stream, err->pool, "%swarning: apr_err=%s\n",
641                          prefix, symbolic_name));
642#endif
643
644  svn_error_clear(svn_cmdline_fprintf
645                  (stream, err->pool,
646                   _("%swarning: W%06d: %s\n"),
647                   prefix, err->apr_err,
648                   svn_err_best_message(err, buf, sizeof(buf))));
649  fflush(stream);
650}
651
652const char *
653svn_err_best_message(const svn_error_t *err, char *buf, apr_size_t bufsize)
654{
655  /* Skip over any trace records.  */
656  while (svn_error__is_tracing_link(err))
657    err = err->child;
658  if (err->message)
659    return err->message;
660  else
661    return svn_strerror(err->apr_err, buf, bufsize);
662}
663
664
665/* svn_strerror() and helpers */
666
667/* Duplicate of the same typedef in tests/libsvn_subr/error-code-test.c */
668typedef struct err_defn {
669  svn_errno_t errcode; /* 160004 */
670  const char *errname; /* SVN_ERR_FS_CORRUPT */
671  const char *errdesc; /* default message */
672} err_defn;
673
674/* To understand what is going on here, read svn_error_codes.h. */
675#define SVN_ERROR_BUILD_ARRAY
676#include "svn_error_codes.h"
677
678char *
679svn_strerror(apr_status_t statcode, char *buf, apr_size_t bufsize)
680{
681  const err_defn *defn;
682
683  for (defn = error_table; defn->errdesc != NULL; ++defn)
684    if (defn->errcode == (svn_errno_t)statcode)
685      {
686        apr_cpystrn(buf, _(defn->errdesc), bufsize);
687        return buf;
688      }
689
690  return apr_strerror(statcode, buf, bufsize);
691}
692
693#ifdef SVN_DEBUG
694/* Defines svn__errno and svn__apr_errno */
695#include "errorcode.inc"
696#endif
697
698const char *
699svn_error_symbolic_name(apr_status_t statcode)
700{
701  const err_defn *defn;
702#ifdef SVN_DEBUG
703  int i;
704#endif /* SVN_DEBUG */
705
706  for (defn = error_table; defn->errdesc != NULL; ++defn)
707    if (defn->errcode == (svn_errno_t)statcode)
708      return defn->errname;
709
710  /* "No error" is not in error_table. */
711  if (statcode == APR_SUCCESS)
712    return "SVN_NO_ERROR";
713
714#ifdef SVN_DEBUG
715  /* Try errno.h symbols. */
716  /* Linear search through a sorted array */
717  for (i = 0; i < sizeof(svn__errno) / sizeof(svn__errno[0]); i++)
718    if (svn__errno[i].errcode == (int)statcode)
719      return svn__errno[i].errname;
720
721  /* Try APR errors. */
722  /* Linear search through a sorted array */
723  for (i = 0; i < sizeof(svn__apr_errno) / sizeof(svn__apr_errno[0]); i++)
724    if (svn__apr_errno[i].errcode == (int)statcode)
725      return svn__apr_errno[i].errname;
726#endif /* SVN_DEBUG */
727
728  /* ### TODO: do we need APR_* error macros?  What about APR_TO_OS_ERROR()? */
729
730  return NULL;
731}
732
733
734
735/* Malfunctions. */
736
737svn_error_t *
738svn_error_raise_on_malfunction(svn_boolean_t can_return,
739                               const char *file, int line,
740                               const char *expr)
741{
742  if (!can_return)
743    abort(); /* Nothing else we can do as a library */
744
745  /* The filename and line number of the error source needs to be set
746     here because svn_error_createf() is not the macro defined in
747     svn_error.h but the real function. */
748  svn_error__locate(file, line);
749
750  if (expr)
751    return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL,
752                             _("In file '%s' line %d: assertion failed (%s)"),
753                             file, line, expr);
754  else
755    return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL,
756                             _("In file '%s' line %d: internal malfunction"),
757                             file, line);
758}
759
760svn_error_t *
761svn_error_abort_on_malfunction(svn_boolean_t can_return,
762                               const char *file, int line,
763                               const char *expr)
764{
765  svn_error_t *err = svn_error_raise_on_malfunction(TRUE, file, line, expr);
766
767  svn_handle_error2(err, stderr, FALSE, "svn: ");
768  abort();
769  return err;  /* Not reached. */
770}
771
772/* The current handler for reporting malfunctions, and its default setting. */
773static svn_error_malfunction_handler_t malfunction_handler
774  = svn_error_abort_on_malfunction;
775
776svn_error_malfunction_handler_t
777svn_error_set_malfunction_handler(svn_error_malfunction_handler_t func)
778{
779  svn_error_malfunction_handler_t old_malfunction_handler
780    = malfunction_handler;
781
782  malfunction_handler = func;
783  return old_malfunction_handler;
784}
785
786svn_error_malfunction_handler_t
787svn_error_get_malfunction_handler(void)
788{
789  return malfunction_handler;
790}
791
792/* Note: Although this is a "__" function, it is in the public ABI, so
793 * we can never remove it or change its signature. */
794svn_error_t *
795svn_error__malfunction(svn_boolean_t can_return,
796                       const char *file, int line,
797                       const char *expr)
798{
799  return malfunction_handler(can_return, file, line, expr);
800}
801
802
803/* Misc. */
804
805svn_error_t *
806svn_error__wrap_zlib(int zerr, const char *function, const char *message)
807{
808  apr_status_t status;
809  const char *zmsg;
810
811  if (zerr == Z_OK)
812    return SVN_NO_ERROR;
813
814  switch (zerr)
815    {
816    case Z_STREAM_ERROR:
817      status = SVN_ERR_STREAM_MALFORMED_DATA;
818      zmsg = _("stream error");
819      break;
820
821    case Z_MEM_ERROR:
822      status = APR_ENOMEM;
823      zmsg = _("out of memory");
824      break;
825
826    case Z_BUF_ERROR:
827      status = APR_ENOMEM;
828      zmsg = _("buffer error");
829      break;
830
831    case Z_VERSION_ERROR:
832      status = SVN_ERR_STREAM_UNRECOGNIZED_DATA;
833      zmsg = _("version error");
834      break;
835
836    case Z_DATA_ERROR:
837      status = SVN_ERR_STREAM_MALFORMED_DATA;
838      zmsg = _("corrupt data");
839      break;
840
841    default:
842      status = SVN_ERR_STREAM_UNRECOGNIZED_DATA;
843      zmsg = _("unknown error");
844      break;
845    }
846
847  if (message != NULL)
848    return svn_error_createf(status, NULL, "zlib (%s): %s: %s", function,
849                             zmsg, message);
850  else
851    return svn_error_createf(status, NULL, "zlib (%s): %s", function, zmsg);
852}
853