io.c revision 262253
1/*
2 * io.c:   shared file reading, writing, and probing code.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#include <stdio.h>
27
28#ifndef WIN32
29#include <unistd.h>
30#endif
31
32#ifndef APR_STATUS_IS_EPERM
33#include <errno.h>
34#ifdef EPERM
35#define APR_STATUS_IS_EPERM(s)   ((s) == EPERM)
36#else
37#define APR_STATUS_IS_EPERM(s)   (0)
38#endif
39#endif
40
41#include <apr_lib.h>
42#include <apr_pools.h>
43#include <apr_file_io.h>
44#include <apr_file_info.h>
45#include <apr_general.h>
46#include <apr_strings.h>
47#include <apr_portable.h>
48#include <apr_md5.h>
49
50#ifdef WIN32
51#include <arch/win32/apr_arch_file_io.h>
52#endif
53
54#include "svn_hash.h"
55#include "svn_types.h"
56#include "svn_dirent_uri.h"
57#include "svn_path.h"
58#include "svn_string.h"
59#include "svn_error.h"
60#include "svn_io.h"
61#include "svn_pools.h"
62#include "svn_utf.h"
63#include "svn_config.h"
64#include "svn_private_config.h"
65#include "svn_ctype.h"
66
67#include "private/svn_atomic.h"
68#include "private/svn_io_private.h"
69
70#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
71
72/*
73  Windows is 'aided' by a number of types of applications that
74  follow other applications around and open up files they have
75  changed for various reasons (the most intrusive are virus
76  scanners).  So, if one of these other apps has glommed onto
77  our file we may get an 'access denied' error.
78
79  This retry loop does not completely solve the problem (who
80  knows how long the other app is going to hold onto it for), but
81  goes a long way towards minimizing it.  It is not an infinite
82  loop because there might really be an error.
83
84  Another reason for retrying delete operations on Windows
85  is that they are asynchronous -- the file or directory is not
86  actually deleted until the last handle to it is closed.  The
87  retry loop cannot completely solve this problem either, but can
88  help mitigate it.
89*/
90#define RETRY_MAX_ATTEMPTS 100
91#define RETRY_INITIAL_SLEEP 1000
92#define RETRY_MAX_SLEEP 128000
93
94#define RETRY_LOOP(err, expr, retry_test, sleep_test)                      \
95  do                                                                       \
96    {                                                                      \
97      apr_status_t os_err = APR_TO_OS_ERROR(err);                          \
98      int sleep_count = RETRY_INITIAL_SLEEP;                               \
99      int retries;                                                         \
100      for (retries = 0;                                                    \
101           retries < RETRY_MAX_ATTEMPTS && (retry_test);                   \
102           os_err = APR_TO_OS_ERROR(err))                                  \
103        {                                                                  \
104          if (sleep_test)                                                  \
105            {                                                              \
106              ++retries;                                                   \
107              apr_sleep(sleep_count);                                      \
108              if (sleep_count < RETRY_MAX_SLEEP)                           \
109                sleep_count *= 2;                                          \
110            }                                                              \
111          (err) = (expr);                                                  \
112        }                                                                  \
113    }                                                                      \
114  while (0)
115
116#if defined(EDEADLK) && APR_HAS_THREADS
117#define FILE_LOCK_RETRY_LOOP(err, expr)                                    \
118  RETRY_LOOP(err,                                                          \
119             expr,                                                         \
120             (APR_STATUS_IS_EINTR(err) || os_err == EDEADLK),              \
121             (!APR_STATUS_IS_EINTR(err)))
122#else
123#define FILE_LOCK_RETRY_LOOP(err, expr)                                    \
124  RETRY_LOOP(err,                                                          \
125             expr,                                                         \
126             (APR_STATUS_IS_EINTR(err)),                                   \
127             0)
128#endif
129
130#ifndef WIN32_RETRY_LOOP
131#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP)
132#define WIN32_RETRY_LOOP(err, expr)                                        \
133  RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED                     \
134                         || os_err == ERROR_SHARING_VIOLATION              \
135                         || os_err == ERROR_DIR_NOT_EMPTY),                \
136             1)
137#else
138#define WIN32_RETRY_LOOP(err, expr) ((void)0)
139#endif
140#endif
141
142/* Forward declaration */
143static apr_status_t
144dir_is_empty(const char *dir, apr_pool_t *pool);
145static APR_INLINE svn_error_t *
146do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
147                           const char *msg, const char *msg_no_name,
148                           apr_pool_t *pool);
149
150/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on
151 * operating systems where APR always uses utf-8 as native path format */
152static svn_error_t *
153cstring_to_utf8(const char **path_utf8,
154                const char *path_apr,
155                apr_pool_t *pool)
156{
157#if defined(WIN32) || defined(DARWIN)
158  *path_utf8 = path_apr;
159  return SVN_NO_ERROR;
160#else
161  return svn_path_cstring_to_utf8(path_utf8, path_apr, pool);
162#endif
163}
164
165/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on
166 * operating systems where APR always uses utf-8 as native path format */
167static svn_error_t *
168cstring_from_utf8(const char **path_apr,
169                  const char *path_utf8,
170                  apr_pool_t *pool)
171{
172#if defined(WIN32) || defined(DARWIN)
173  *path_apr = path_utf8;
174  return SVN_NO_ERROR;
175#else
176  return svn_path_cstring_from_utf8(path_apr, path_utf8, pool);
177#endif
178}
179
180/* Helper function that allows to convert an APR-level PATH to something
181 * that we can pass the svn_error_wrap_apr. Since we use it in context
182 * of error reporting, having *some* path info may be more useful than
183 * having none.  Therefore, we use a best effort approach here.
184 *
185 * This is different from svn_io_file_name_get in that it uses a different
186 * signature style and will never fail.
187 */
188static const char *
189try_utf8_from_internal_style(const char *path, apr_pool_t *pool)
190{
191  svn_error_t *error;
192  const char *path_utf8;
193
194  /* Special case. */
195  if (path == NULL)
196    return "(NULL)";
197
198  /* (try to) convert PATH to UTF-8. If that fails, continue with the plain
199   * PATH because it is the best we have. It may actually be UTF-8 already.
200   */
201  error = cstring_to_utf8(&path_utf8, path, pool);
202  if (error)
203    {
204      /* fallback to best representation we have */
205
206      svn_error_clear(error);
207      path_utf8 = path;
208    }
209
210  /* Toggle (back-)slashes etc. as necessary.
211   */
212  return svn_dirent_local_style(path_utf8, pool);
213}
214
215
216/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
217 * NAME is in the internal encoding used by APR; PARENT is in
218 * UTF-8 and in internal (not local) style.
219 *
220 * Use PARENT only for generating an error string if the conversion
221 * fails because NAME could not be represented in UTF-8.  In that
222 * case, return a two-level error in which the outer error's message
223 * mentions PARENT, but the inner error's message does not mention
224 * NAME (except possibly in hex) since NAME may not be printable.
225 * Such a compound error at least allows the user to go looking in the
226 * right directory for the problem.
227 *
228 * If there is any other error, just return that error directly.
229 *
230 * If there is any error, the effect on *NAME_P is undefined.
231 *
232 * *NAME_P and NAME may refer to the same storage.
233 */
234static svn_error_t *
235entry_name_to_utf8(const char **name_p,
236                   const char *name,
237                   const char *parent,
238                   apr_pool_t *pool)
239{
240#if defined(WIN32) || defined(DARWIN)
241  *name_p = apr_pstrdup(pool, name);
242  return SVN_NO_ERROR;
243#else
244  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
245  if (err && err->apr_err == APR_EINVAL)
246    {
247      return svn_error_createf(err->apr_err, err,
248                               _("Error converting entry "
249                                 "in directory '%s' to UTF-8"),
250                               svn_dirent_local_style(parent, pool));
251    }
252  return err;
253#endif
254}
255
256
257
258static void
259map_apr_finfo_to_node_kind(svn_node_kind_t *kind,
260                           svn_boolean_t *is_special,
261                           apr_finfo_t *finfo)
262{
263  *is_special = FALSE;
264
265  if (finfo->filetype == APR_REG)
266    *kind = svn_node_file;
267  else if (finfo->filetype == APR_DIR)
268    *kind = svn_node_dir;
269  else if (finfo->filetype == APR_LNK)
270    {
271      *is_special = TRUE;
272      *kind = svn_node_file;
273    }
274  else
275    *kind = svn_node_unknown;
276}
277
278/* Helper for svn_io_check_path() and svn_io_check_resolved_path();
279   essentially the same semantics as those two, with the obvious
280   interpretation for RESOLVE_SYMLINKS. */
281static svn_error_t *
282io_check_path(const char *path,
283              svn_boolean_t resolve_symlinks,
284              svn_boolean_t *is_special_p,
285              svn_node_kind_t *kind,
286              apr_pool_t *pool)
287{
288  apr_int32_t flags;
289  apr_finfo_t finfo;
290  apr_status_t apr_err;
291  const char *path_apr;
292  svn_boolean_t is_special = FALSE;
293
294  if (path[0] == '\0')
295    path = ".";
296
297  /* Not using svn_io_stat() here because we want to check the
298     apr_err return explicitly. */
299  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
300
301  flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
302  apr_err = apr_stat(&finfo, path_apr, flags, pool);
303
304  if (APR_STATUS_IS_ENOENT(apr_err))
305    *kind = svn_node_none;
306  else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err))
307    *kind = svn_node_none;
308  else if (apr_err)
309    return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"),
310                              svn_dirent_local_style(path, pool));
311  else
312    map_apr_finfo_to_node_kind(kind, &is_special, &finfo);
313
314  *is_special_p = is_special;
315
316  return SVN_NO_ERROR;
317}
318
319
320/* Wrapper for apr_file_open(), taking an APR-encoded filename. */
321static apr_status_t
322file_open(apr_file_t **f,
323          const char *fname_apr,
324          apr_int32_t flag,
325          apr_fileperms_t perm,
326          svn_boolean_t retry_on_failure,
327          apr_pool_t *pool)
328{
329  apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool);
330
331  if (retry_on_failure)
332    {
333      WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool));
334    }
335  return status;
336}
337
338
339svn_error_t *
340svn_io_check_resolved_path(const char *path,
341                           svn_node_kind_t *kind,
342                           apr_pool_t *pool)
343{
344  svn_boolean_t ignored;
345  return io_check_path(path, TRUE, &ignored, kind, pool);
346}
347
348svn_error_t *
349svn_io_check_path(const char *path,
350                  svn_node_kind_t *kind,
351                  apr_pool_t *pool)
352{
353  svn_boolean_t ignored;
354  return io_check_path(path, FALSE, &ignored, kind, pool);
355}
356
357svn_error_t *
358svn_io_check_special_path(const char *path,
359                          svn_node_kind_t *kind,
360                          svn_boolean_t *is_special,
361                          apr_pool_t *pool)
362{
363  return io_check_path(path, FALSE, is_special, kind, pool);
364}
365
366struct temp_file_cleanup_s
367{
368  apr_pool_t *pool;
369  /* The (APR-encoded) full path of the file to be removed, or NULL if
370   * nothing to do. */
371  const char *fname_apr;
372};
373
374
375static apr_status_t
376temp_file_plain_cleanup_handler(void *baton)
377{
378  struct  temp_file_cleanup_s *b = baton;
379  apr_status_t apr_err = APR_SUCCESS;
380
381  if (b->fname_apr)
382    {
383      apr_err = apr_file_remove(b->fname_apr, b->pool);
384      WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool));
385    }
386
387  return apr_err;
388}
389
390
391static apr_status_t
392temp_file_child_cleanup_handler(void *baton)
393{
394  struct  temp_file_cleanup_s *b = baton;
395
396  apr_pool_cleanup_kill(b->pool, b,
397                        temp_file_plain_cleanup_handler);
398
399  return APR_SUCCESS;
400}
401
402
403svn_error_t *
404svn_io_open_uniquely_named(apr_file_t **file,
405                           const char **unique_path,
406                           const char *dirpath,
407                           const char *filename,
408                           const char *suffix,
409                           svn_io_file_del_t delete_when,
410                           apr_pool_t *result_pool,
411                           apr_pool_t *scratch_pool)
412{
413  const char *path;
414  unsigned int i;
415  struct temp_file_cleanup_s *baton = NULL;
416
417  /* At the beginning, we don't know whether unique_path will need
418     UTF8 conversion */
419  svn_boolean_t needs_utf8_conversion = TRUE;
420
421  SVN_ERR_ASSERT(file || unique_path);
422
423  if (dirpath == NULL)
424    SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
425  if (filename == NULL)
426    filename = "tempfile";
427  if (suffix == NULL)
428    suffix = ".tmp";
429
430  path = svn_dirent_join(dirpath, filename, scratch_pool);
431
432  if (delete_when == svn_io_file_del_on_pool_cleanup)
433    {
434      baton = apr_palloc(result_pool, sizeof(*baton));
435
436      baton->pool = result_pool;
437      baton->fname_apr = NULL;
438
439      /* Because cleanups are run LIFO, we need to make sure to register
440         our cleanup before the apr_file_close cleanup:
441
442         On Windows, you can't remove an open file.
443      */
444      apr_pool_cleanup_register(result_pool, baton,
445                                temp_file_plain_cleanup_handler,
446                                temp_file_child_cleanup_handler);
447    }
448
449  for (i = 1; i <= 99999; i++)
450    {
451      const char *unique_name;
452      const char *unique_name_apr;
453      apr_file_t *try_file;
454      apr_status_t apr_err;
455      apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL
456                          | APR_BUFFERED | APR_BINARY);
457
458      if (delete_when == svn_io_file_del_on_close)
459        flag |= APR_DELONCLOSE;
460
461      /* Special case the first attempt -- if we can avoid having a
462         generated numeric portion at all, that's best.  So first we
463         try with just the suffix; then future tries add a number
464         before the suffix.  (A do-while loop could avoid the repeated
465         conditional, but it's not worth the clarity loss.)
466
467         If the first attempt fails, the first number will be "2".
468         This is good, since "1" would misleadingly imply that
469         the second attempt was actually the first... and if someone's
470         got conflicts on their conflicts, we probably don't want to
471         add to their confusion :-). */
472      if (i == 1)
473        unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix);
474      else
475        unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix);
476
477      /* Hmmm.  Ideally, we would append to a native-encoding buf
478         before starting iteration, then convert back to UTF-8 for
479         return. But I suppose that would make the appending code
480         sensitive to i18n in a way it shouldn't be... Oh well. */
481      if (needs_utf8_conversion)
482        {
483          SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name,
484                                    scratch_pool));
485          if (i == 1)
486            {
487              /* The variable parts of unique_name will not require UTF8
488                 conversion. Therefore, if UTF8 conversion had no effect
489                 on it in the first iteration, it won't require conversion
490                 in any future iteration. */
491              needs_utf8_conversion = strcmp(unique_name_apr, unique_name);
492            }
493        }
494      else
495        unique_name_apr = unique_name;
496
497      apr_err = file_open(&try_file, unique_name_apr, flag,
498                          APR_OS_DEFAULT, FALSE, result_pool);
499
500      if (APR_STATUS_IS_EEXIST(apr_err))
501        continue;
502      else if (apr_err)
503        {
504          /* On Win32, CreateFile fails with an "Access Denied" error
505             code, rather than "File Already Exists", if the colliding
506             name belongs to a directory. */
507          if (APR_STATUS_IS_EACCES(apr_err))
508            {
509              apr_finfo_t finfo;
510              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
511                                                APR_FINFO_TYPE, scratch_pool);
512
513              if (!apr_err_2 && finfo.filetype == APR_DIR)
514                continue;
515
516#ifdef WIN32
517              apr_err_2 = APR_TO_OS_ERROR(apr_err);
518
519              if (apr_err_2 == ERROR_ACCESS_DENIED ||
520                  apr_err_2 == ERROR_SHARING_VIOLATION)
521                {
522                  /* The file is in use by another process or is hidden;
523                     create a new name, but don't do this 99999 times in
524                     case the folder is not writable */
525                  i += 797;
526                  continue;
527                }
528#endif
529
530              /* Else fall through and return the original error. */
531            }
532
533          if (file)
534            *file = NULL;
535          if (unique_path)
536            *unique_path = NULL;
537          return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
538                                    svn_dirent_local_style(unique_name,
539                                                         scratch_pool));
540        }
541      else
542        {
543          if (delete_when == svn_io_file_del_on_pool_cleanup)
544            baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr);
545
546          if (file)
547            *file = try_file;
548          else
549            apr_file_close(try_file);
550          if (unique_path)
551            *unique_path = apr_pstrdup(result_pool, unique_name);
552
553          return SVN_NO_ERROR;
554        }
555    }
556
557  if (file)
558    *file = NULL;
559  if (unique_path)
560    *unique_path = NULL;
561  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
562                           NULL,
563                           _("Unable to make name for '%s'"),
564                           svn_dirent_local_style(path, scratch_pool));
565}
566
567svn_error_t *
568svn_io_create_unique_link(const char **unique_name_p,
569                          const char *path,
570                          const char *dest,
571                          const char *suffix,
572                          apr_pool_t *pool)
573{
574#ifdef HAVE_SYMLINK
575  unsigned int i;
576  const char *unique_name;
577  const char *unique_name_apr;
578  const char *dest_apr;
579  int rv;
580
581  SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool));
582  for (i = 1; i <= 99999; i++)
583    {
584      apr_status_t apr_err;
585
586      /* Special case the first attempt -- if we can avoid having a
587         generated numeric portion at all, that's best.  So first we
588         try with just the suffix; then future tries add a number
589         before the suffix.  (A do-while loop could avoid the repeated
590         conditional, but it's not worth the clarity loss.)
591
592         If the first attempt fails, the first number will be "2".
593         This is good, since "1" would misleadingly imply that
594         the second attempt was actually the first... and if someone's
595         got conflicts on their conflicts, we probably don't want to
596         add to their confusion :-). */
597      if (i == 1)
598        unique_name = apr_psprintf(pool, "%s%s", path, suffix);
599      else
600        unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
601
602      /* Hmmm.  Ideally, we would append to a native-encoding buf
603         before starting iteration, then convert back to UTF-8 for
604         return. But I suppose that would make the appending code
605         sensitive to i18n in a way it shouldn't be... Oh well. */
606      SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool));
607      do {
608        rv = symlink(dest_apr, unique_name_apr);
609      } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
610
611      apr_err = apr_get_os_error();
612
613      if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err))
614        continue;
615      else if (rv == -1 && apr_err)
616        {
617          /* On Win32, CreateFile fails with an "Access Denied" error
618             code, rather than "File Already Exists", if the colliding
619             name belongs to a directory. */
620          if (APR_STATUS_IS_EACCES(apr_err))
621            {
622              apr_finfo_t finfo;
623              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
624                                                APR_FINFO_TYPE, pool);
625
626              if (!apr_err_2
627                  && (finfo.filetype == APR_DIR))
628                continue;
629
630              /* Else ignore apr_err_2; better to fall through and
631                 return the original error. */
632            }
633
634          *unique_name_p = NULL;
635          return svn_error_wrap_apr(apr_err,
636                                    _("Can't create symbolic link '%s'"),
637                                    svn_dirent_local_style(unique_name, pool));
638        }
639      else
640        {
641          *unique_name_p = unique_name;
642          return SVN_NO_ERROR;
643        }
644    }
645
646  *unique_name_p = NULL;
647  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
648                           NULL,
649                           _("Unable to make name for '%s'"),
650                           svn_dirent_local_style(path, pool));
651#else
652  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
653                          _("Symbolic links are not supported on this "
654                            "platform"));
655#endif
656}
657
658svn_error_t *
659svn_io_read_link(svn_string_t **dest,
660                 const char *path,
661                 apr_pool_t *pool)
662{
663#ifdef HAVE_READLINK
664  svn_string_t dest_apr;
665  const char *path_apr;
666  char buf[1025];
667  ssize_t rv;
668
669  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
670  do {
671    rv = readlink(path_apr, buf, sizeof(buf) - 1);
672  } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
673
674  if (rv == -1)
675    return svn_error_wrap_apr(apr_get_os_error(),
676                              _("Can't read contents of link"));
677
678  buf[rv] = '\0';
679  dest_apr.data = buf;
680  dest_apr.len = rv;
681
682  /* ### Cast needed, one of these interfaces is wrong */
683  return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool);
684#else
685  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
686                          _("Symbolic links are not supported on this "
687                            "platform"));
688#endif
689}
690
691
692svn_error_t *
693svn_io_copy_link(const char *src,
694                 const char *dst,
695                 apr_pool_t *pool)
696
697{
698#ifdef HAVE_READLINK
699  svn_string_t *link_dest;
700  const char *dst_tmp;
701
702  /* Notice what the link is pointing at... */
703  SVN_ERR(svn_io_read_link(&link_dest, src, pool));
704
705  /* Make a tmp-link pointing at the same thing. */
706  SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data,
707                                    ".tmp", pool));
708
709  /* Move the tmp-link to link. */
710  return svn_io_file_rename(dst_tmp, dst, pool);
711
712#else
713  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
714                          _("Symbolic links are not supported on this "
715                            "platform"));
716#endif
717}
718
719/* Temporary directory name cache for svn_io_temp_dir() */
720static volatile svn_atomic_t temp_dir_init_state = 0;
721static const char *temp_dir;
722
723/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */
724static svn_error_t *
725init_temp_dir(void *baton, apr_pool_t *scratch_pool)
726{
727  /* Global pool for the temp path */
728  apr_pool_t *global_pool = svn_pool_create(NULL);
729  const char *dir;
730
731  apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool);
732
733  if (apr_err)
734    return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory"));
735
736  SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool));
737
738  dir = svn_dirent_internal_style(dir, scratch_pool);
739
740  SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool));
741
742  return SVN_NO_ERROR;
743}
744
745
746svn_error_t *
747svn_io_temp_dir(const char **dir,
748                apr_pool_t *pool)
749{
750  SVN_ERR(svn_atomic__init_once(&temp_dir_init_state,
751                                init_temp_dir, NULL, pool));
752
753  *dir = apr_pstrdup(pool, temp_dir);
754
755  return SVN_NO_ERROR;
756}
757
758
759
760
761/*** Creating, copying and appending files. ***/
762
763/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary
764 * allocations.
765 *
766 * NOTE: We don't use apr_copy_file() for this, since it takes filenames
767 * as parameters.  Since we want to copy to a temporary file
768 * and rename for atomicity (see below), this would require an extra
769 * close/open pair, which can be expensive, especially on
770 * remote file systems.
771 */
772static apr_status_t
773copy_contents(apr_file_t *from_file,
774              apr_file_t *to_file,
775              apr_pool_t *pool)
776{
777  /* Copy bytes till the cows come home. */
778  while (1)
779    {
780      char buf[SVN__STREAM_CHUNK_SIZE];
781      apr_size_t bytes_this_time = sizeof(buf);
782      apr_status_t read_err;
783      apr_status_t write_err;
784
785      /* Read 'em. */
786      read_err = apr_file_read(from_file, buf, &bytes_this_time);
787      if (read_err && !APR_STATUS_IS_EOF(read_err))
788        {
789          return read_err;
790        }
791
792      /* Write 'em. */
793      write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL);
794      if (write_err)
795        {
796          return write_err;
797        }
798
799      if (read_err && APR_STATUS_IS_EOF(read_err))
800        {
801          /* Return the results of this close: an error, or success. */
802          return APR_SUCCESS;
803        }
804    }
805  /* NOTREACHED */
806}
807
808
809svn_error_t *
810svn_io_copy_file(const char *src,
811                 const char *dst,
812                 svn_boolean_t copy_perms,
813                 apr_pool_t *pool)
814{
815  apr_file_t *from_file, *to_file;
816  apr_status_t apr_err;
817  const char *dst_tmp;
818  svn_error_t *err;
819
820  /* ### NOTE: sometimes src == dst. In this case, because we copy to a
821     ###   temporary file, and then rename over the top of the destination,
822     ###   the net result is resetting the permissions on src/dst.
823     ###
824     ### Note: specifically, this can happen during a switch when the desired
825     ###   permissions for a file change from one branch to another. See
826     ###   switch_tests 17.
827     ###
828     ### ... yes, we should avoid copying to the same file, and we should
829     ###     make the "reset perms" explicit. The switch *happens* to work
830     ###     because of this copy-to-temp-then-rename implementation. If it
831     ###     weren't for that, the switch would break.
832  */
833#ifdef CHECK_FOR_SAME_FILE
834  if (strcmp(src, dst) == 0)
835    return SVN_NO_ERROR;
836#endif
837
838  SVN_ERR(svn_io_file_open(&from_file, src, APR_READ,
839                           APR_OS_DEFAULT, pool));
840
841  /* For atomicity, we copy to a tmp file and then rename the tmp
842     file over the real destination. */
843
844  SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp,
845                                   svn_dirent_dirname(dst, pool),
846                                   svn_io_file_del_none, pool, pool));
847
848  apr_err = copy_contents(from_file, to_file, pool);
849
850  if (apr_err)
851    {
852      err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"),
853                               svn_dirent_local_style(src, pool),
854                               svn_dirent_local_style(dst_tmp, pool));
855    }
856   else
857     err = NULL;
858
859  err = svn_error_compose_create(err,
860                                 svn_io_file_close(from_file, pool));
861
862  err = svn_error_compose_create(err,
863                                 svn_io_file_close(to_file, pool));
864
865  if (err)
866    {
867      return svn_error_compose_create(
868                                 err,
869                                 svn_io_remove_file2(dst_tmp, TRUE, pool));
870    }
871
872  /* If copying perms, set the perms on dst_tmp now, so they will be
873     atomically inherited in the upcoming rename.  But note that we
874     had to wait until now to set perms, because if they say
875     read-only, then we'd have failed filling dst_tmp's contents. */
876  if (copy_perms)
877    SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool));
878
879  return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool));
880}
881
882#if !defined(WIN32) && !defined(__OS2__)
883/* Wrapper for apr_file_perms_set(), taking a UTF8-encoded filename. */
884static svn_error_t *
885file_perms_set(const char *fname, apr_fileperms_t perms,
886               apr_pool_t *pool)
887{
888  const char *fname_apr;
889  apr_status_t status;
890
891  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
892
893  status = apr_file_perms_set(fname_apr, perms);
894  if (status)
895    return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
896                              fname);
897  else
898    return SVN_NO_ERROR;
899}
900
901/* Set permissions PERMS on the FILE. This is a cheaper variant of the
902 * file_perms_set wrapper() function because no locale-dependent string
903 * conversion is required. POOL will be used for allocations.
904 */
905static svn_error_t *
906file_perms_set2(apr_file_t* file, apr_fileperms_t perms, apr_pool_t *pool)
907{
908  const char *fname_apr;
909  apr_status_t status;
910
911  status = apr_file_name_get(&fname_apr, file);
912  if (status)
913    return svn_error_wrap_apr(status, _("Can't get file name"));
914
915  status = apr_file_perms_set(fname_apr, perms);
916  if (status)
917    return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
918                              try_utf8_from_internal_style(fname_apr, pool));
919  else
920    return SVN_NO_ERROR;
921}
922
923#endif /* !WIN32 && !__OS2__ */
924
925svn_error_t *
926svn_io_copy_perms(const char *src,
927                  const char *dst,
928                  apr_pool_t *pool)
929{
930  /* ### On Windows or OS/2, apr_file_perms_set always returns APR_ENOTIMPL,
931         and the path passed to apr_file_perms_set must be encoded
932         in the platform-specific path encoding; not necessary UTF-8.
933         We need a platform-specific implementation to get the
934         permissions right. */
935
936#if !defined(WIN32) && !defined(__OS2__)
937  {
938    apr_finfo_t finfo;
939    svn_node_kind_t kind;
940    svn_boolean_t is_special;
941    svn_error_t *err;
942
943    /* If DST is a symlink, don't bother copying permissions. */
944    SVN_ERR(svn_io_check_special_path(dst, &kind, &is_special, pool));
945    if (is_special)
946      return SVN_NO_ERROR;
947
948    SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_PROT, pool));
949    err = file_perms_set(dst, finfo.protection, pool);
950    if (err)
951      {
952        /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL
953           here under normal circumstances, because the perms themselves
954           came from a call to apr_file_info_get(), and we already know
955           this is the non-Win32 case.  But if it does happen, it's not
956           an error. */
957        if (APR_STATUS_IS_INCOMPLETE(err->apr_err) ||
958            APR_STATUS_IS_ENOTIMPL(err->apr_err))
959          svn_error_clear(err);
960        else
961          {
962            const char *message;
963            message = apr_psprintf(pool, _("Can't set permissions on '%s'"),
964                                   svn_dirent_local_style(dst, pool));
965            return svn_error_quick_wrap(err, message);
966          }
967      }
968  }
969#endif /* !WIN32 && !__OS2__ */
970
971  return SVN_NO_ERROR;
972}
973
974
975svn_error_t *
976svn_io_append_file(const char *src, const char *dst, apr_pool_t *pool)
977{
978  apr_status_t apr_err;
979  const char *src_apr, *dst_apr;
980
981  SVN_ERR(cstring_from_utf8(&src_apr, src, pool));
982  SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool));
983
984  apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool);
985
986  if (apr_err)
987    return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"),
988                              svn_dirent_local_style(src, pool),
989                              svn_dirent_local_style(dst, pool));
990
991  return SVN_NO_ERROR;
992}
993
994
995svn_error_t *svn_io_copy_dir_recursively(const char *src,
996                                         const char *dst_parent,
997                                         const char *dst_basename,
998                                         svn_boolean_t copy_perms,
999                                         svn_cancel_func_t cancel_func,
1000                                         void *cancel_baton,
1001                                         apr_pool_t *pool)
1002{
1003  svn_node_kind_t kind;
1004  apr_status_t status;
1005  const char *dst_path;
1006  apr_dir_t *this_dir;
1007  apr_finfo_t this_entry;
1008  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
1009
1010  /* Make a subpool for recursion */
1011  apr_pool_t *subpool = svn_pool_create(pool);
1012
1013  /* The 'dst_path' is simply dst_parent/dst_basename */
1014  dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
1015
1016  /* Sanity checks:  SRC and DST_PARENT are directories, and
1017     DST_BASENAME doesn't already exist in DST_PARENT. */
1018  SVN_ERR(svn_io_check_path(src, &kind, subpool));
1019  if (kind != svn_node_dir)
1020    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1021                             _("Source '%s' is not a directory"),
1022                             svn_dirent_local_style(src, pool));
1023
1024  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
1025  if (kind != svn_node_dir)
1026    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1027                             _("Destination '%s' is not a directory"),
1028                             svn_dirent_local_style(dst_parent, pool));
1029
1030  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
1031  if (kind != svn_node_none)
1032    return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
1033                             _("Destination '%s' already exists"),
1034                             svn_dirent_local_style(dst_path, pool));
1035
1036  /* Create the new directory. */
1037  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
1038  SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool));
1039
1040  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
1041  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
1042
1043  for (status = apr_dir_read(&this_entry, flags, this_dir);
1044       status == APR_SUCCESS;
1045       status = apr_dir_read(&this_entry, flags, this_dir))
1046    {
1047      if ((this_entry.name[0] == '.')
1048          && ((this_entry.name[1] == '\0')
1049              || ((this_entry.name[1] == '.')
1050                  && (this_entry.name[2] == '\0'))))
1051        {
1052          continue;
1053        }
1054      else
1055        {
1056          const char *src_target, *entryname_utf8;
1057
1058          if (cancel_func)
1059            SVN_ERR(cancel_func(cancel_baton));
1060
1061          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
1062                                     src, subpool));
1063          src_target = svn_dirent_join(src, entryname_utf8, subpool);
1064
1065          if (this_entry.filetype == APR_REG) /* regular file */
1066            {
1067              const char *dst_target = svn_dirent_join(dst_path,
1068                                                       entryname_utf8,
1069                                                       subpool);
1070              SVN_ERR(svn_io_copy_file(src_target, dst_target,
1071                                       copy_perms, subpool));
1072            }
1073          else if (this_entry.filetype == APR_LNK) /* symlink */
1074            {
1075              const char *dst_target = svn_dirent_join(dst_path,
1076                                                       entryname_utf8,
1077                                                       subpool);
1078              SVN_ERR(svn_io_copy_link(src_target, dst_target,
1079                                       subpool));
1080            }
1081          else if (this_entry.filetype == APR_DIR) /* recurse */
1082            {
1083              /* Prevent infinite recursion by filtering off our
1084                 newly created destination path. */
1085              if (strcmp(src, dst_parent) == 0
1086                  && strcmp(entryname_utf8, dst_basename) == 0)
1087                continue;
1088
1089              SVN_ERR(svn_io_copy_dir_recursively
1090                      (src_target,
1091                       dst_path,
1092                       entryname_utf8,
1093                       copy_perms,
1094                       cancel_func,
1095                       cancel_baton,
1096                       subpool));
1097            }
1098          /* ### support other APR node types someday?? */
1099
1100        }
1101    }
1102
1103  if (! (APR_STATUS_IS_ENOENT(status)))
1104    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
1105                              svn_dirent_local_style(src, pool));
1106
1107  status = apr_dir_close(this_dir);
1108  if (status)
1109    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
1110                              svn_dirent_local_style(src, pool));
1111
1112  /* Free any memory used by recursion */
1113  svn_pool_destroy(subpool);
1114
1115  return SVN_NO_ERROR;
1116}
1117
1118
1119svn_error_t *
1120svn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
1121{
1122  const char *path_apr;
1123  apr_status_t apr_err;
1124
1125  if (svn_path_is_empty(path))
1126    /* Empty path (current dir) is assumed to always exist,
1127       so we do nothing, per docs. */
1128    return SVN_NO_ERROR;
1129
1130  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1131
1132  apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool);
1133  WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr,
1134                                                   APR_OS_DEFAULT, pool));
1135
1136  if (apr_err)
1137    return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"),
1138                              svn_dirent_local_style(path, pool));
1139
1140  return SVN_NO_ERROR;
1141}
1142
1143svn_error_t *svn_io_file_create(const char *file,
1144                                const char *contents,
1145                                apr_pool_t *pool)
1146{
1147  apr_file_t *f;
1148  apr_size_t written;
1149  svn_error_t *err = SVN_NO_ERROR;
1150
1151  SVN_ERR(svn_io_file_open(&f, file,
1152                           (APR_WRITE | APR_CREATE | APR_EXCL),
1153                           APR_OS_DEFAULT,
1154                           pool));
1155  if (contents && *contents)
1156    err = svn_io_file_write_full(f, contents, strlen(contents),
1157                                 &written, pool);
1158
1159
1160  return svn_error_trace(
1161                        svn_error_compose_create(err,
1162                                                 svn_io_file_close(f, pool)));
1163}
1164
1165svn_error_t *svn_io_dir_file_copy(const char *src_path,
1166                                  const char *dest_path,
1167                                  const char *file,
1168                                  apr_pool_t *pool)
1169{
1170  const char *file_dest_path = svn_dirent_join(dest_path, file, pool);
1171  const char *file_src_path = svn_dirent_join(src_path, file, pool);
1172
1173  return svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool);
1174}
1175
1176
1177/*** Modtime checking. ***/
1178
1179svn_error_t *
1180svn_io_file_affected_time(apr_time_t *apr_time,
1181                          const char *path,
1182                          apr_pool_t *pool)
1183{
1184  apr_finfo_t finfo;
1185
1186  SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1187
1188  *apr_time = finfo.mtime;
1189
1190  return SVN_NO_ERROR;
1191}
1192
1193
1194svn_error_t *
1195svn_io_set_file_affected_time(apr_time_t apr_time,
1196                              const char *path,
1197                              apr_pool_t *pool)
1198{
1199  apr_status_t status;
1200  const char *native_path;
1201
1202  SVN_ERR(cstring_from_utf8(&native_path, path, pool));
1203  status = apr_file_mtime_set(native_path, apr_time, pool);
1204
1205  if (status)
1206    return svn_error_wrap_apr(status, _("Can't set access time of '%s'"),
1207                              svn_dirent_local_style(path, pool));
1208
1209  return SVN_NO_ERROR;
1210}
1211
1212
1213void
1214svn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool)
1215{
1216  apr_time_t now, then;
1217  svn_error_t *err;
1218  char *sleep_env_var;
1219
1220  sleep_env_var = getenv(SVN_SLEEP_ENV_VAR);
1221
1222  if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0)
1223    return; /* Allow skipping for testing */
1224
1225  now = apr_time_now();
1226
1227  /* Calculate 0.02 seconds after the next second wallclock tick. */
1228  then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50);
1229
1230  /* Worst case is waiting one second, so we can use that time to determine
1231     if we can sleep shorter than that */
1232  if (path)
1233    {
1234      apr_finfo_t finfo;
1235
1236      err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool);
1237
1238      if (err)
1239        {
1240          svn_error_clear(err); /* Fall back on original behavior */
1241        }
1242      else if (finfo.mtime % APR_USEC_PER_SEC)
1243        {
1244          /* Very simplistic but safe approach:
1245              If the filesystem has < sec mtime we can be reasonably sure
1246              that the filesystem has <= millisecond precision.
1247
1248             ## Perhaps find a better algorithm here. This will fail once
1249                in every 1000 cases on a millisecond precision filesystem.
1250
1251                But better to fail once in every thousand cases than every
1252                time, like we did before.
1253                (All tested filesystems I know have at least microsecond precision.)
1254
1255             Note for further research on algorithm:
1256               FAT32 has < 1 sec precision on ctime, but 2 sec on mtime */
1257
1258          /* Sleep for at least 1 millisecond.
1259             (t < 1000 will be round to 0 in apr) */
1260          apr_sleep(1000);
1261
1262          return;
1263        }
1264
1265      now = apr_time_now(); /* Extract the time used for the path stat */
1266
1267      if (now >= then)
1268        return; /* Passing negative values may suspend indefinitely (Windows) */
1269    }
1270
1271  apr_sleep(then - now);
1272}
1273
1274
1275svn_error_t *
1276svn_io_filesizes_different_p(svn_boolean_t *different_p,
1277                             const char *file1,
1278                             const char *file2,
1279                             apr_pool_t *pool)
1280{
1281  apr_finfo_t finfo1;
1282  apr_finfo_t finfo2;
1283  apr_status_t status;
1284  const char *file1_apr, *file2_apr;
1285
1286  /* Not using svn_io_stat() because don't want to generate
1287     svn_error_t objects for non-error conditions. */
1288
1289  SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool));
1290  SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool));
1291
1292  /* Stat both files */
1293  status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool);
1294  if (status)
1295    {
1296      /* If we got an error stat'ing a file, it could be because the
1297         file was removed... or who knows.  Whatever the case, we
1298         don't know if the filesizes are definitely different, so
1299         assume that they're not. */
1300      *different_p = FALSE;
1301      return SVN_NO_ERROR;
1302    }
1303
1304  status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool);
1305  if (status)
1306    {
1307      /* See previous comment. */
1308      *different_p = FALSE;
1309      return SVN_NO_ERROR;
1310    }
1311
1312  /* Examine file sizes */
1313  if (finfo1.size == finfo2.size)
1314    *different_p = FALSE;
1315  else
1316    *different_p = TRUE;
1317
1318  return SVN_NO_ERROR;
1319}
1320
1321
1322svn_error_t *
1323svn_io_filesizes_three_different_p(svn_boolean_t *different_p12,
1324                                   svn_boolean_t *different_p23,
1325                                   svn_boolean_t *different_p13,
1326                                   const char *file1,
1327                                   const char *file2,
1328                                   const char *file3,
1329                                   apr_pool_t *scratch_pool)
1330{
1331  apr_finfo_t finfo1, finfo2, finfo3;
1332  apr_status_t status1, status2, status3;
1333  const char *file1_apr, *file2_apr, *file3_apr;
1334
1335  /* Not using svn_io_stat() because don't want to generate
1336     svn_error_t objects for non-error conditions. */
1337
1338  SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool));
1339  SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool));
1340  SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool));
1341
1342  /* Stat all three files */
1343  status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool);
1344  status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool);
1345  status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool);
1346
1347  /* If we got an error stat'ing a file, it could be because the
1348     file was removed... or who knows.  Whatever the case, we
1349     don't know if the filesizes are definitely different, so
1350     assume that they're not. */
1351  *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size;
1352  *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size;
1353  *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size;
1354
1355  return SVN_NO_ERROR;
1356}
1357
1358
1359svn_error_t *
1360svn_io_file_checksum2(svn_checksum_t **checksum,
1361                      const char *file,
1362                      svn_checksum_kind_t kind,
1363                      apr_pool_t *pool)
1364{
1365  svn_stream_t *file_stream;
1366  svn_stream_t *checksum_stream;
1367  apr_file_t* f;
1368
1369  SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool));
1370  file_stream = svn_stream_from_aprfile2(f, FALSE, pool);
1371  checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind,
1372                                            TRUE, pool);
1373
1374  /* Because the checksummed stream will force the reading (and
1375     checksumming) of all the file's bytes, we can just close the stream
1376     and let its magic work. */
1377  return svn_stream_close(checksum_stream);
1378}
1379
1380
1381svn_error_t *
1382svn_io_file_checksum(unsigned char digest[],
1383                     const char *file,
1384                     apr_pool_t *pool)
1385{
1386  svn_checksum_t *checksum;
1387
1388  SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool));
1389  memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE);
1390
1391  return SVN_NO_ERROR;
1392}
1393
1394
1395
1396/*** Permissions and modes. ***/
1397
1398#if !defined(WIN32) && !defined(__OS2__)
1399/* Given the file specified by PATH, attempt to create an
1400   identical version of it owned by the current user.  This is done by
1401   moving it to a temporary location, copying the file back to its old
1402   path, then deleting the temporarily moved version.  All temporary
1403   allocations are done in POOL. */
1404static svn_error_t *
1405reown_file(const char *path,
1406           apr_pool_t *pool)
1407{
1408  const char *unique_name;
1409
1410  SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name,
1411                                   svn_dirent_dirname(path, pool),
1412                                   svn_io_file_del_none, pool, pool));
1413  SVN_ERR(svn_io_file_rename(path, unique_name, pool));
1414  SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool));
1415  return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool));
1416}
1417
1418/* Determine what the PERMS for a new file should be by looking at the
1419   permissions of a temporary file that we create.
1420   Unfortunately, umask() as defined in POSIX provides no thread-safe way
1421   to get at the current value of the umask, so what we're doing here is
1422   the only way we have to determine which combination of write bits
1423   (User/Group/World) should be set by default.
1424   Make temporary allocations in SCRATCH_POOL.  */
1425static svn_error_t *
1426get_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool)
1427{
1428  /* the default permissions as read from the temp folder */
1429  static apr_fileperms_t default_perms = 0;
1430
1431  /* Technically, this "racy": Multiple threads may use enter here and
1432     try to figure out the default permission concurrently. That's fine
1433     since they will end up with the same results. Even more technical,
1434     apr_fileperms_t is an atomic type on 32+ bit machines.
1435   */
1436  if (default_perms == 0)
1437    {
1438      apr_finfo_t finfo;
1439      apr_file_t *fd;
1440      const char *fname_base, *fname;
1441      apr_uint32_t randomish;
1442      svn_error_t *err;
1443
1444      /* Get the perms for a newly created file to find out what bits
1445        should be set.
1446
1447        Explictly delete the file because we want this file to be as
1448        short-lived as possible since its presence means other
1449        processes may have to try multiple names.
1450
1451        Using svn_io_open_uniquely_named() here because other tempfile
1452        creation functions tweak the permission bits of files they create.
1453      */
1454      randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool
1455                   + (apr_uint32_t)apr_time_now());
1456      fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish);
1457
1458      SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base,
1459                                         NULL, svn_io_file_del_none,
1460                                         scratch_pool, scratch_pool));
1461      err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool);
1462      err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool));
1463      err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE,
1464                                                              scratch_pool));
1465      SVN_ERR(err);
1466      *perms = finfo.protection;
1467      default_perms = finfo.protection;
1468    }
1469  else
1470    *perms = default_perms;
1471
1472  return SVN_NO_ERROR;
1473}
1474
1475/* OR together permission bits of the file FD and the default permissions
1476   of a file as determined by get_default_file_perms(). Do temporary
1477   allocations in SCRATCH_POOL. */
1478static svn_error_t *
1479merge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms,
1480                         apr_pool_t *scratch_pool)
1481{
1482  apr_finfo_t finfo;
1483  apr_fileperms_t default_perms;
1484
1485  SVN_ERR(get_default_file_perms(&default_perms, scratch_pool));
1486  SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool));
1487
1488  /* Glom the perms together. */
1489  *perms = default_perms | finfo.protection;
1490  return SVN_NO_ERROR;
1491}
1492
1493/* This is a helper function for the svn_io_set_file_read* functions
1494   that attempts to honor the users umask when dealing with
1495   permission changes.  It is a no-op when invoked on a symlink. */
1496static svn_error_t *
1497io_set_file_perms(const char *path,
1498                  svn_boolean_t change_readwrite,
1499                  svn_boolean_t enable_write,
1500                  svn_boolean_t change_executable,
1501                  svn_boolean_t executable,
1502                  svn_boolean_t ignore_enoent,
1503                  apr_pool_t *pool)
1504{
1505  apr_status_t status;
1506  const char *path_apr;
1507  apr_finfo_t finfo;
1508  apr_fileperms_t perms_to_set;
1509
1510  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1511
1512  /* Try to change only a minimal amount of the perms first
1513     by getting the current perms and adding bits
1514     only on where read perms are granted.  If this fails
1515     fall through to just setting file attributes. */
1516  status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool);
1517  if (status)
1518    {
1519      if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1520        return SVN_NO_ERROR;
1521      else if (status != APR_ENOTIMPL)
1522        return svn_error_wrap_apr(status,
1523                                  _("Can't change perms of file '%s'"),
1524                                  svn_dirent_local_style(path, pool));
1525      return SVN_NO_ERROR;
1526    }
1527
1528  if (finfo.filetype == APR_LNK)
1529    return SVN_NO_ERROR;
1530
1531  perms_to_set = finfo.protection;
1532  if (change_readwrite)
1533    {
1534      if (enable_write) /* Make read-write. */
1535        {
1536          /* Tweak the owner bits only. The group/other bits aren't safe to
1537           * touch because we may end up setting them in undesired ways. */
1538          perms_to_set |= (APR_UREAD|APR_UWRITE);
1539        }
1540      else
1541        {
1542          if (finfo.protection & APR_UREAD)
1543            perms_to_set &= ~APR_UWRITE;
1544          if (finfo.protection & APR_GREAD)
1545            perms_to_set &= ~APR_GWRITE;
1546          if (finfo.protection & APR_WREAD)
1547            perms_to_set &= ~APR_WWRITE;
1548        }
1549    }
1550
1551  if (change_executable)
1552    {
1553      if (executable)
1554        {
1555          if (finfo.protection & APR_UREAD)
1556            perms_to_set |= APR_UEXECUTE;
1557          if (finfo.protection & APR_GREAD)
1558            perms_to_set |= APR_GEXECUTE;
1559          if (finfo.protection & APR_WREAD)
1560            perms_to_set |= APR_WEXECUTE;
1561        }
1562      else
1563        {
1564          if (finfo.protection & APR_UREAD)
1565            perms_to_set &= ~APR_UEXECUTE;
1566          if (finfo.protection & APR_GREAD)
1567            perms_to_set &= ~APR_GEXECUTE;
1568          if (finfo.protection & APR_WREAD)
1569            perms_to_set &= ~APR_WEXECUTE;
1570        }
1571    }
1572
1573  /* If we aren't changing anything then just return, this saves
1574     some system calls and helps with shared working copies */
1575  if (perms_to_set == finfo.protection)
1576    return SVN_NO_ERROR;
1577
1578  status = apr_file_perms_set(path_apr, perms_to_set);
1579  if (!status)
1580    return SVN_NO_ERROR;
1581
1582  if (APR_STATUS_IS_EPERM(status))
1583    {
1584      /* We don't have permissions to change the
1585         permissions!  Try a move, copy, and delete
1586         workaround to see if we can get the file owned by
1587         us.  If these succeed, try the permissions set
1588         again.
1589
1590         Note that we only attempt this in the
1591         stat-available path.  This assumes that the
1592         move-copy workaround will only be helpful on
1593         platforms that implement apr_stat. */
1594      SVN_ERR(reown_file(path, pool));
1595      status = apr_file_perms_set(path_apr, perms_to_set);
1596    }
1597
1598  if (!status)
1599    return SVN_NO_ERROR;
1600
1601  if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1602    return SVN_NO_ERROR;
1603  else if (status == APR_ENOTIMPL)
1604    {
1605      /* At least try to set the attributes. */
1606      apr_fileattrs_t attrs = 0;
1607      apr_fileattrs_t attrs_values = 0;
1608
1609      if (change_readwrite)
1610        {
1611          attrs = APR_FILE_ATTR_READONLY;
1612          if (!enable_write)
1613            attrs_values = APR_FILE_ATTR_READONLY;
1614        }
1615      if (change_executable)
1616        {
1617          attrs = APR_FILE_ATTR_EXECUTABLE;
1618          if (executable)
1619            attrs_values = APR_FILE_ATTR_EXECUTABLE;
1620        }
1621      status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
1622    }
1623
1624  return svn_error_wrap_apr(status,
1625                            _("Can't change perms of file '%s'"),
1626                            svn_dirent_local_style(path, pool));
1627}
1628#endif /* !WIN32 && !__OS2__ */
1629
1630#ifdef WIN32
1631#if APR_HAS_UNICODE_FS
1632/* copy of the apr function utf8_to_unicode_path since apr doesn't export this one */
1633static apr_status_t io_utf8_to_unicode_path(apr_wchar_t* retstr, apr_size_t retlen,
1634                                            const char* srcstr)
1635{
1636    /* TODO: The computations could preconvert the string to determine
1637     * the true size of the retstr, but that's a memory over speed
1638     * tradeoff that isn't appropriate this early in development.
1639     *
1640     * Allocate the maximum string length based on leading 4
1641     * characters of \\?\ (allowing nearly unlimited path lengths)
1642     * plus the trailing null, then transform /'s into \\'s since
1643     * the \\?\ form doesn't allow '/' path separators.
1644     *
1645     * Note that the \\?\ form only works for local drive paths, and
1646     * \\?\UNC\ is needed UNC paths.
1647     */
1648    apr_size_t srcremains = strlen(srcstr) + 1;
1649    apr_wchar_t *t = retstr;
1650    apr_status_t rv;
1651
1652    /* This is correct, we don't twist the filename if it will
1653     * definitely be shorter than 248 characters.  It merits some
1654     * performance testing to see if this has any effect, but there
1655     * seem to be applications that get confused by the resulting
1656     * Unicode \\?\ style file names, especially if they use argv[0]
1657     * or call the Win32 API functions such as GetModuleName, etc.
1658     * Not every application is prepared to handle such names.
1659     *
1660     * Note also this is shorter than MAX_PATH, as directory paths
1661     * are actually limited to 248 characters.
1662     *
1663     * Note that a utf-8 name can never result in more wide chars
1664     * than the original number of utf-8 narrow chars.
1665     */
1666    if (srcremains > 248) {
1667        if (srcstr[1] == ':' && (srcstr[2] == '/' || srcstr[2] == '\\')) {
1668            wcscpy (retstr, L"\\\\?\\");
1669            retlen -= 4;
1670            t += 4;
1671        }
1672        else if ((srcstr[0] == '/' || srcstr[0] == '\\')
1673              && (srcstr[1] == '/' || srcstr[1] == '\\')
1674              && (srcstr[2] != '?')) {
1675            /* Skip the slashes */
1676            srcstr += 2;
1677            srcremains -= 2;
1678            wcscpy (retstr, L"\\\\?\\UNC\\");
1679            retlen -= 8;
1680            t += 8;
1681        }
1682    }
1683
1684    if (rv = apr_conv_utf8_to_ucs2(srcstr, &srcremains, t, &retlen)) {
1685        return (rv == APR_INCOMPLETE) ? APR_EINVAL : rv;
1686    }
1687    if (srcremains) {
1688        return APR_ENAMETOOLONG;
1689    }
1690    for (; *t; ++t)
1691        if (*t == L'/')
1692            *t = L'\\';
1693    return APR_SUCCESS;
1694}
1695#endif
1696
1697static apr_status_t io_win_file_attrs_set(const char *fname,
1698                                          DWORD attributes,
1699                                          DWORD attr_mask,
1700                                          apr_pool_t *pool)
1701{
1702    /* this is an implementation of apr_file_attrs_set() but one
1703       that uses the proper Windows attributes instead of the apr
1704       attributes. This way, we can apply any Windows file and
1705       folder attributes even if apr doesn't implement them */
1706    DWORD flags;
1707    apr_status_t rv;
1708#if APR_HAS_UNICODE_FS
1709    apr_wchar_t wfname[APR_PATH_MAX];
1710#endif
1711
1712#if APR_HAS_UNICODE_FS
1713    IF_WIN_OS_IS_UNICODE
1714    {
1715        if (rv = io_utf8_to_unicode_path(wfname,
1716                                         sizeof(wfname) / sizeof(wfname[0]),
1717                                         fname))
1718            return rv;
1719        flags = GetFileAttributesW(wfname);
1720    }
1721#endif
1722#if APR_HAS_ANSI_FS
1723    ELSE_WIN_OS_IS_ANSI
1724    {
1725        flags = GetFileAttributesA(fname);
1726    }
1727#endif
1728
1729    if (flags == 0xFFFFFFFF)
1730        return apr_get_os_error();
1731
1732    flags &= ~attr_mask;
1733    flags |= (attributes & attr_mask);
1734
1735#if APR_HAS_UNICODE_FS
1736    IF_WIN_OS_IS_UNICODE
1737    {
1738        rv = SetFileAttributesW(wfname, flags);
1739    }
1740#endif
1741#if APR_HAS_ANSI_FS
1742    ELSE_WIN_OS_IS_ANSI
1743    {
1744        rv = SetFileAttributesA(fname, flags);
1745    }
1746#endif
1747
1748    if (rv == 0)
1749        return apr_get_os_error();
1750
1751    return APR_SUCCESS;
1752}
1753
1754#endif
1755
1756svn_error_t *
1757svn_io_set_file_read_write_carefully(const char *path,
1758                                     svn_boolean_t enable_write,
1759                                     svn_boolean_t ignore_enoent,
1760                                     apr_pool_t *pool)
1761{
1762  if (enable_write)
1763    return svn_io_set_file_read_write(path, ignore_enoent, pool);
1764  return svn_io_set_file_read_only(path, ignore_enoent, pool);
1765}
1766
1767svn_error_t *
1768svn_io_set_file_read_only(const char *path,
1769                          svn_boolean_t ignore_enoent,
1770                          apr_pool_t *pool)
1771{
1772  /* On Windows and OS/2, just set the file attributes -- on unix call
1773     our internal function which attempts to honor the umask. */
1774#if !defined(WIN32) && !defined(__OS2__)
1775  return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
1776                           ignore_enoent, pool);
1777#else
1778  apr_status_t status;
1779  const char *path_apr;
1780
1781  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1782
1783  status = apr_file_attrs_set(path_apr,
1784                              APR_FILE_ATTR_READONLY,
1785                              APR_FILE_ATTR_READONLY,
1786                              pool);
1787
1788  if (status && status != APR_ENOTIMPL)
1789    if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
1790      return svn_error_wrap_apr(status,
1791                                _("Can't set file '%s' read-only"),
1792                                svn_dirent_local_style(path, pool));
1793
1794  return SVN_NO_ERROR;
1795#endif
1796}
1797
1798
1799svn_error_t *
1800svn_io_set_file_read_write(const char *path,
1801                           svn_boolean_t ignore_enoent,
1802                           apr_pool_t *pool)
1803{
1804  /* On Windows and OS/2, just set the file attributes -- on unix call
1805     our internal function which attempts to honor the umask. */
1806#if !defined(WIN32) && !defined(__OS2__)
1807  return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
1808                           ignore_enoent, pool);
1809#else
1810  apr_status_t status;
1811  const char *path_apr;
1812
1813  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1814
1815  status = apr_file_attrs_set(path_apr,
1816                              0,
1817                              APR_FILE_ATTR_READONLY,
1818                              pool);
1819
1820  if (status && status != APR_ENOTIMPL)
1821    if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
1822      return svn_error_wrap_apr(status,
1823                                _("Can't set file '%s' read-write"),
1824                                svn_dirent_local_style(path, pool));
1825
1826  return SVN_NO_ERROR;
1827#endif
1828}
1829
1830svn_error_t *
1831svn_io_set_file_executable(const char *path,
1832                           svn_boolean_t executable,
1833                           svn_boolean_t ignore_enoent,
1834                           apr_pool_t *pool)
1835{
1836  /* On Windows and OS/2, just exit -- on unix call our internal function
1837  which attempts to honor the umask. */
1838#if (!defined(WIN32) && !defined(__OS2__))
1839  return io_set_file_perms(path, FALSE, FALSE, TRUE, executable,
1840                           ignore_enoent, pool);
1841#else
1842  return SVN_NO_ERROR;
1843#endif
1844}
1845
1846
1847svn_error_t *
1848svn_io__is_finfo_read_only(svn_boolean_t *read_only,
1849                           apr_finfo_t *file_info,
1850                           apr_pool_t *pool)
1851{
1852#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
1853  apr_status_t apr_err;
1854  apr_uid_t uid;
1855  apr_gid_t gid;
1856
1857  *read_only = FALSE;
1858
1859  apr_err = apr_uid_current(&uid, &gid, pool);
1860
1861  if (apr_err)
1862    return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
1863
1864  /* Check write bit for current user. */
1865  if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
1866    *read_only = !(file_info->protection & APR_UWRITE);
1867
1868  else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
1869    *read_only = !(file_info->protection & APR_GWRITE);
1870
1871  else
1872    *read_only = !(file_info->protection & APR_WWRITE);
1873
1874#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
1875  *read_only = (file_info->protection & APR_FREADONLY);
1876#endif
1877
1878  return SVN_NO_ERROR;
1879}
1880
1881svn_error_t *
1882svn_io__is_finfo_executable(svn_boolean_t *executable,
1883                            apr_finfo_t *file_info,
1884                            apr_pool_t *pool)
1885{
1886#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
1887  apr_status_t apr_err;
1888  apr_uid_t uid;
1889  apr_gid_t gid;
1890
1891  *executable = FALSE;
1892
1893  apr_err = apr_uid_current(&uid, &gid, pool);
1894
1895  if (apr_err)
1896    return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
1897
1898  /* Check executable bit for current user. */
1899  if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
1900    *executable = (file_info->protection & APR_UEXECUTE);
1901
1902  else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
1903    *executable = (file_info->protection & APR_GEXECUTE);
1904
1905  else
1906    *executable = (file_info->protection & APR_WEXECUTE);
1907
1908#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
1909  *executable = FALSE;
1910#endif
1911
1912  return SVN_NO_ERROR;
1913}
1914
1915svn_error_t *
1916svn_io_is_file_executable(svn_boolean_t *executable,
1917                          const char *path,
1918                          apr_pool_t *pool)
1919{
1920#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
1921  apr_finfo_t file_info;
1922
1923  SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER,
1924                      pool));
1925  SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool));
1926
1927#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
1928  *executable = FALSE;
1929#endif
1930
1931  return SVN_NO_ERROR;
1932}
1933
1934
1935/*** File locking. ***/
1936#if !defined(WIN32) && !defined(__OS2__)
1937/* Clear all outstanding locks on ARG, an open apr_file_t *. */
1938static apr_status_t
1939file_clear_locks(void *arg)
1940{
1941  apr_status_t apr_err;
1942  apr_file_t *f = arg;
1943
1944  /* Remove locks. */
1945  apr_err = apr_file_unlock(f);
1946  if (apr_err)
1947    return apr_err;
1948
1949  return 0;
1950}
1951#endif
1952
1953svn_error_t *
1954svn_io_lock_open_file(apr_file_t *lockfile_handle,
1955                      svn_boolean_t exclusive,
1956                      svn_boolean_t nonblocking,
1957                      apr_pool_t *pool)
1958{
1959  int locktype = APR_FLOCK_SHARED;
1960  apr_status_t apr_err;
1961  const char *fname;
1962
1963  if (exclusive)
1964    locktype = APR_FLOCK_EXCLUSIVE;
1965  if (nonblocking)
1966    locktype |= APR_FLOCK_NONBLOCK;
1967
1968  /* We need this only in case of an error but this is cheap to get -
1969   * so we do it here for clarity. */
1970  apr_err = apr_file_name_get(&fname, lockfile_handle);
1971  if (apr_err)
1972    return svn_error_wrap_apr(apr_err, _("Can't get file name"));
1973
1974  /* Get lock on the filehandle. */
1975  apr_err = apr_file_lock(lockfile_handle, locktype);
1976
1977  /* In deployments with two or more multithreaded servers running on
1978     the same system serving two or more fsfs repositories it is
1979     possible for a deadlock to occur when getting a write lock on
1980     db/txn-current-lock:
1981
1982     Process 1                         Process 2
1983     ---------                         ---------
1984     thread 1: get lock in repos A
1985                                       thread 1: get lock in repos B
1986                                       thread 2: block getting lock in repos A
1987     thread 2: try to get lock in B *** deadlock ***
1988
1989     Retry for a while for the deadlock to clear. */
1990  FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype));
1991
1992  if (apr_err)
1993    {
1994      switch (locktype & APR_FLOCK_TYPEMASK)
1995        {
1996        case APR_FLOCK_SHARED:
1997          return svn_error_wrap_apr(apr_err,
1998                                    _("Can't get shared lock on file '%s'"),
1999                                    try_utf8_from_internal_style(fname, pool));
2000        case APR_FLOCK_EXCLUSIVE:
2001          return svn_error_wrap_apr(apr_err,
2002                                    _("Can't get exclusive lock on file '%s'"),
2003                                    try_utf8_from_internal_style(fname, pool));
2004        default:
2005          SVN_ERR_MALFUNCTION();
2006        }
2007    }
2008
2009/* On Windows and OS/2 file locks are automatically released when
2010   the file handle closes */
2011#if !defined(WIN32) && !defined(__OS2__)
2012  apr_pool_cleanup_register(pool, lockfile_handle,
2013                            file_clear_locks,
2014                            apr_pool_cleanup_null);
2015#endif
2016
2017  return SVN_NO_ERROR;
2018}
2019
2020svn_error_t *
2021svn_io_unlock_open_file(apr_file_t *lockfile_handle,
2022                        apr_pool_t *pool)
2023{
2024  const char *fname;
2025  apr_status_t apr_err;
2026
2027  /* We need this only in case of an error but this is cheap to get -
2028   * so we do it here for clarity. */
2029  apr_err = apr_file_name_get(&fname, lockfile_handle);
2030  if (apr_err)
2031    return svn_error_wrap_apr(apr_err, _("Can't get file name"));
2032
2033  /* The actual unlock attempt. */
2034  apr_err = apr_file_unlock(lockfile_handle);
2035  if (apr_err)
2036    return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"),
2037                              try_utf8_from_internal_style(fname, pool));
2038
2039/* On Windows and OS/2 file locks are automatically released when
2040   the file handle closes */
2041#if !defined(WIN32) && !defined(__OS2__)
2042  apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks);
2043#endif
2044
2045  return SVN_NO_ERROR;
2046}
2047
2048svn_error_t *
2049svn_io_file_lock2(const char *lock_file,
2050                  svn_boolean_t exclusive,
2051                  svn_boolean_t nonblocking,
2052                  apr_pool_t *pool)
2053{
2054  int locktype = APR_FLOCK_SHARED;
2055  apr_file_t *lockfile_handle;
2056  apr_int32_t flags;
2057
2058  if (exclusive)
2059    locktype = APR_FLOCK_EXCLUSIVE;
2060
2061  flags = APR_READ;
2062  if (locktype == APR_FLOCK_EXCLUSIVE)
2063    flags |= APR_WRITE;
2064
2065  /* locktype is never read after this block, so we don't need to bother
2066     setting it.  If that were to ever change, uncomment the following
2067     block.
2068  if (nonblocking)
2069    locktype |= APR_FLOCK_NONBLOCK;
2070  */
2071
2072  SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags,
2073                           APR_OS_DEFAULT,
2074                           pool));
2075
2076  /* Get lock on the filehandle. */
2077  return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool);
2078}
2079
2080
2081
2082/* Data consistency/coherency operations. */
2083
2084svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
2085                                       apr_pool_t *pool)
2086{
2087  apr_os_file_t filehand;
2088
2089  /* First make sure that any user-space buffered data is flushed. */
2090  SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
2091                                     N_("Can't flush file '%s'"),
2092                                     N_("Can't flush stream"),
2093                                     pool));
2094
2095  apr_os_file_get(&filehand, file);
2096
2097  /* Call the operating system specific function to actually force the
2098     data to disk. */
2099  {
2100#ifdef WIN32
2101
2102    if (! FlushFileBuffers(filehand))
2103        return svn_error_wrap_apr(apr_get_os_error(),
2104                                  _("Can't flush file to disk"));
2105
2106#else
2107      int rv;
2108
2109      do {
2110        rv = fsync(filehand);
2111      } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
2112
2113      /* If the file is in a memory filesystem, fsync() may return
2114         EINVAL.  Presumably the user knows the risks, and we can just
2115         ignore the error. */
2116      if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error()))
2117        return SVN_NO_ERROR;
2118
2119      if (rv == -1)
2120        return svn_error_wrap_apr(apr_get_os_error(),
2121                                  _("Can't flush file to disk"));
2122
2123#endif
2124  }
2125  return SVN_NO_ERROR;
2126}
2127
2128
2129
2130/* TODO write test for these two functions, then refactor. */
2131
2132/* Set RESULT to an svn_stringbuf_t containing the contents of FILE.
2133   FILENAME is the FILE's on-disk APR-safe name, or NULL if that name
2134   isn't known.  If CHECK_SIZE is TRUE, the function will attempt to
2135   first stat() the file to determine it's size before sucking its
2136   contents into the stringbuf.  (Doing so can prevent unnecessary
2137   memory usage, an unwanted side effect of the stringbuf growth and
2138   reallocation mechanism.)  */
2139static svn_error_t *
2140stringbuf_from_aprfile(svn_stringbuf_t **result,
2141                       const char *filename,
2142                       apr_file_t *file,
2143                       svn_boolean_t check_size,
2144                       apr_pool_t *pool)
2145{
2146  apr_size_t len;
2147  svn_error_t *err;
2148  svn_stringbuf_t *res = NULL;
2149  apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE;
2150  char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
2151
2152  /* If our caller wants us to check the size of the file for
2153     efficient memory handling, we'll try to do so. */
2154  if (check_size)
2155    {
2156      apr_status_t status;
2157
2158      /* If our caller didn't tell us the file's name, we'll ask APR
2159         if it knows the name.  No problem if we can't figure it out.  */
2160      if (! filename)
2161        {
2162          const char *filename_apr;
2163          if (! (status = apr_file_name_get(&filename_apr, file)))
2164            filename = filename_apr;
2165        }
2166
2167      /* If we now know the filename, try to stat().  If we succeed,
2168         we know how to allocate our stringbuf.  */
2169      if (filename)
2170        {
2171          apr_finfo_t finfo;
2172          if (! (status = apr_stat(&finfo, filename, APR_FINFO_MIN, pool)))
2173            res_initial_len = (apr_size_t)finfo.size;
2174        }
2175    }
2176
2177
2178  /* XXX: We should check the incoming data for being of type binary. */
2179
2180  res = svn_stringbuf_create_ensure(res_initial_len, pool);
2181
2182  /* apr_file_read will not return data and eof in the same call. So this loop
2183   * is safe from missing read data.  */
2184  len = SVN__STREAM_CHUNK_SIZE;
2185  err = svn_io_file_read(file, buf, &len, pool);
2186  while (! err)
2187    {
2188      svn_stringbuf_appendbytes(res, buf, len);
2189      len = SVN__STREAM_CHUNK_SIZE;
2190      err = svn_io_file_read(file, buf, &len, pool);
2191    }
2192
2193  /* Having read all the data we *expect* EOF */
2194  if (err && !APR_STATUS_IS_EOF(err->apr_err))
2195    return err;
2196  svn_error_clear(err);
2197
2198  *result = res;
2199  return SVN_NO_ERROR;
2200}
2201
2202svn_error_t *
2203svn_stringbuf_from_file2(svn_stringbuf_t **result,
2204                         const char *filename,
2205                         apr_pool_t *pool)
2206{
2207  apr_file_t *f;
2208
2209  if (filename[0] == '-' && filename[1] == '\0')
2210    {
2211      apr_status_t apr_err;
2212      if ((apr_err = apr_file_open_stdin(&f, pool)))
2213        return svn_error_wrap_apr(apr_err, _("Can't open stdin"));
2214      SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool));
2215    }
2216  else
2217    {
2218      SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool));
2219      SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool));
2220    }
2221  return svn_io_file_close(f, pool);
2222}
2223
2224
2225svn_error_t *
2226svn_stringbuf_from_file(svn_stringbuf_t **result,
2227                        const char *filename,
2228                        apr_pool_t *pool)
2229{
2230  if (filename[0] == '-' && filename[1] == '\0')
2231    return svn_error_create
2232        (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2233         _("Reading from stdin is disallowed"));
2234  return svn_stringbuf_from_file2(result, filename, pool);
2235}
2236
2237svn_error_t *
2238svn_stringbuf_from_aprfile(svn_stringbuf_t **result,
2239                           apr_file_t *file,
2240                           apr_pool_t *pool)
2241{
2242  return stringbuf_from_aprfile(result, NULL, file, TRUE, pool);
2243}
2244
2245
2246
2247/* Deletion. */
2248
2249svn_error_t *
2250svn_io_remove_file2(const char *path,
2251                    svn_boolean_t ignore_enoent,
2252                    apr_pool_t *scratch_pool)
2253{
2254  apr_status_t apr_err;
2255  const char *path_apr;
2256
2257  SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool));
2258
2259  apr_err = apr_file_remove(path_apr, scratch_pool);
2260  if (!apr_err
2261      || (ignore_enoent
2262          && (APR_STATUS_IS_ENOENT(apr_err)
2263              || SVN__APR_STATUS_IS_ENOTDIR(apr_err))))
2264    return SVN_NO_ERROR;
2265
2266#ifdef WIN32
2267  /* If the target is read only NTFS reports EACCESS and FAT/FAT32
2268     reports EEXIST */
2269  if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err))
2270    {
2271      /* Set the destination file writable because Windows will not
2272         allow us to delete when path is read-only */
2273      SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool));
2274      apr_err = apr_file_remove(path_apr, scratch_pool);
2275
2276      if (!apr_err)
2277        return SVN_NO_ERROR;
2278    }
2279
2280    {
2281      apr_status_t os_err = APR_TO_OS_ERROR(apr_err);
2282      /* Check to make sure we aren't trying to delete a directory */
2283      if (os_err == ERROR_ACCESS_DENIED || os_err == ERROR_SHARING_VIOLATION)
2284        {
2285          apr_finfo_t finfo;
2286
2287          if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool)
2288              && finfo.filetype == APR_REG)
2289            {
2290              WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr,
2291                                                        scratch_pool));
2292            }
2293        }
2294
2295      /* Just return the delete error */
2296    }
2297#endif
2298
2299  if (apr_err)
2300    return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"),
2301                              svn_dirent_local_style(path, scratch_pool));
2302
2303  return SVN_NO_ERROR;
2304}
2305
2306
2307svn_error_t *
2308svn_io_remove_dir(const char *path, apr_pool_t *pool)
2309{
2310  return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
2311}
2312
2313/*
2314 Mac OS X has a bug where if you're reading the contents of a
2315 directory via readdir in a loop, and you remove one of the entries in
2316 the directory and the directory has 338 or more files in it you will
2317 skip over some of the entries in the directory.  Needless to say,
2318 this causes problems if you are using this kind of loop inside a
2319 function that is recursively deleting a directory, because when you
2320 get around to removing the directory it will still have something in
2321 it. A similar problem has been observed in other BSDs. This bug has
2322 since been fixed. See http://www.vnode.ch/fixing_seekdir for details.
2323
2324 The workaround is to delete the files only _after_ the initial
2325 directory scan.  A previous workaround involving rewinddir is
2326 problematic on Win32 and some NFS clients, notably NetBSD.
2327
2328 See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and
2329 http://subversion.tigris.org/issues/show_bug.cgi?id=3501.
2330*/
2331
2332/* Neither windows nor unix allows us to delete a non-empty
2333   directory.
2334
2335   This is a function to perform the equivalent of 'rm -rf'. */
2336svn_error_t *
2337svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
2338                   svn_cancel_func_t cancel_func, void *cancel_baton,
2339                   apr_pool_t *pool)
2340{
2341  svn_error_t *err;
2342  apr_pool_t *subpool;
2343  apr_hash_t *dirents;
2344  apr_hash_index_t *hi;
2345
2346  /* Check for pending cancellation request.
2347     If we need to bail out, do so early. */
2348
2349  if (cancel_func)
2350    SVN_ERR((*cancel_func)(cancel_baton));
2351
2352  subpool = svn_pool_create(pool);
2353
2354  err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool);
2355  if (err)
2356    {
2357      /* if the directory doesn't exist, our mission is accomplished */
2358      if (ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err))
2359        {
2360          svn_error_clear(err);
2361          return SVN_NO_ERROR;
2362        }
2363      return svn_error_trace(err);
2364    }
2365
2366  for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
2367    {
2368      const char *name = svn__apr_hash_index_key(hi);
2369      const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
2370      const char *fullpath;
2371
2372      fullpath = svn_dirent_join(path, name, subpool);
2373      if (dirent->kind == svn_node_dir)
2374        {
2375          /* Don't check for cancellation, the callee will immediately do so */
2376          SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func,
2377                                     cancel_baton, subpool));
2378        }
2379      else
2380        {
2381          if (cancel_func)
2382            SVN_ERR((*cancel_func)(cancel_baton));
2383
2384          err = svn_io_remove_file2(fullpath, FALSE, subpool);
2385          if (err)
2386            return svn_error_createf
2387              (err->apr_err, err, _("Can't remove '%s'"),
2388               svn_dirent_local_style(fullpath, subpool));
2389        }
2390    }
2391
2392  svn_pool_destroy(subpool);
2393
2394  return svn_io_dir_remove_nonrecursive(path, pool);
2395}
2396
2397svn_error_t *
2398svn_io_get_dir_filenames(apr_hash_t **dirents,
2399                         const char *path,
2400                         apr_pool_t *pool)
2401{
2402  return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE,
2403                                             pool, pool));
2404}
2405
2406svn_io_dirent2_t *
2407svn_io_dirent2_create(apr_pool_t *result_pool)
2408{
2409  svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent));
2410
2411  /*dirent->kind = svn_node_none;
2412  dirent->special = FALSE;*/
2413  dirent->filesize = SVN_INVALID_FILESIZE;
2414  /*dirent->mtime = 0;*/
2415
2416  return dirent;
2417}
2418
2419svn_io_dirent2_t *
2420svn_io_dirent2_dup(const svn_io_dirent2_t *item,
2421                   apr_pool_t *result_pool)
2422{
2423  return apr_pmemdup(result_pool,
2424                     item,
2425                     sizeof(*item));
2426}
2427
2428svn_error_t *
2429svn_io_get_dirents3(apr_hash_t **dirents,
2430                    const char *path,
2431                    svn_boolean_t only_check_type,
2432                    apr_pool_t *result_pool,
2433                    apr_pool_t *scratch_pool)
2434{
2435  apr_status_t status;
2436  apr_dir_t *this_dir;
2437  apr_finfo_t this_entry;
2438  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
2439
2440  if (!only_check_type)
2441    flags |= APR_FINFO_SIZE | APR_FINFO_MTIME;
2442
2443  *dirents = apr_hash_make(result_pool);
2444
2445  SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool));
2446
2447  for (status = apr_dir_read(&this_entry, flags, this_dir);
2448       status == APR_SUCCESS;
2449       status = apr_dir_read(&this_entry, flags, this_dir))
2450    {
2451      if ((this_entry.name[0] == '.')
2452          && ((this_entry.name[1] == '\0')
2453              || ((this_entry.name[1] == '.')
2454                  && (this_entry.name[2] == '\0'))))
2455        {
2456          continue;
2457        }
2458      else
2459        {
2460          const char *name;
2461          svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool);
2462
2463          SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool));
2464
2465          map_apr_finfo_to_node_kind(&(dirent->kind),
2466                                     &(dirent->special),
2467                                     &this_entry);
2468
2469          if (!only_check_type)
2470            {
2471              dirent->filesize = this_entry.size;
2472              dirent->mtime = this_entry.mtime;
2473            }
2474
2475          svn_hash_sets(*dirents, name, dirent);
2476        }
2477    }
2478
2479  if (! (APR_STATUS_IS_ENOENT(status)))
2480    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
2481                              svn_dirent_local_style(path, scratch_pool));
2482
2483  status = apr_dir_close(this_dir);
2484  if (status)
2485    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
2486                              svn_dirent_local_style(path, scratch_pool));
2487
2488  return SVN_NO_ERROR;
2489}
2490
2491svn_error_t *
2492svn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p,
2493                    const char *path,
2494                    svn_boolean_t verify_truename,
2495                    svn_boolean_t ignore_enoent,
2496                    apr_pool_t *result_pool,
2497                    apr_pool_t *scratch_pool)
2498{
2499  apr_finfo_t finfo;
2500  svn_io_dirent2_t *dirent;
2501  svn_error_t *err;
2502  apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK
2503                       | APR_FINFO_SIZE | APR_FINFO_MTIME;
2504
2505#if defined(WIN32) || defined(__OS2__)
2506  if (verify_truename)
2507    wanted |= APR_FINFO_NAME;
2508#endif
2509
2510  err = svn_io_stat(&finfo, path, wanted, scratch_pool);
2511
2512  if (err && ignore_enoent &&
2513      (APR_STATUS_IS_ENOENT(err->apr_err)
2514       || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
2515    {
2516      svn_error_clear(err);
2517      dirent = svn_io_dirent2_create(result_pool);
2518      SVN_ERR_ASSERT(dirent->kind == svn_node_none);
2519
2520      *dirent_p = dirent;
2521      return SVN_NO_ERROR;
2522    }
2523  SVN_ERR(err);
2524
2525#if defined(WIN32) || defined(__OS2__) || defined(DARWIN)
2526  if (verify_truename)
2527    {
2528      const char *requested_name = svn_dirent_basename(path, NULL);
2529
2530      if (requested_name[0] == '\0')
2531        {
2532          /* No parent directory. No need to stat/verify */
2533        }
2534#if defined(WIN32) || defined(__OS2__)
2535      else if (finfo.name)
2536        {
2537          const char *name_on_disk;
2538          SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path,
2539                                     scratch_pool));
2540
2541          if (strcmp(name_on_disk, requested_name) /* != 0 */)
2542            {
2543              if (ignore_enoent)
2544                {
2545                  *dirent_p = svn_io_dirent2_create(result_pool);
2546                  return SVN_NO_ERROR;
2547                }
2548              else
2549                return svn_error_createf(APR_ENOENT, NULL,
2550                          _("Path '%s' not found, case obstructed by '%s'"),
2551                          svn_dirent_local_style(path, scratch_pool),
2552                          name_on_disk);
2553            }
2554        }
2555#elif defined(DARWIN)
2556      /* Currently apr doesn't set finfo.name on DARWIN, returning
2557                   APR_INCOMPLETE.
2558         ### Can we optimize this in another way? */
2559      else
2560        {
2561          apr_hash_t *dirents;
2562
2563          err = svn_io_get_dirents3(&dirents,
2564                                    svn_dirent_dirname(path, scratch_pool),
2565                                    TRUE /* only_check_type */,
2566                                    scratch_pool, scratch_pool);
2567
2568          if (err && ignore_enoent
2569              && (APR_STATUS_IS_ENOENT(err->apr_err)
2570                  || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
2571            {
2572              svn_error_clear(err);
2573
2574              *dirent_p = svn_io_dirent2_create(result_pool);
2575              return SVN_NO_ERROR;
2576            }
2577          else
2578            SVN_ERR(err);
2579
2580          if (! svn_hash_gets(dirents, requested_name))
2581            {
2582              if (ignore_enoent)
2583                {
2584                  *dirent_p = svn_io_dirent2_create(result_pool);
2585                  return SVN_NO_ERROR;
2586                }
2587              else
2588                return svn_error_createf(APR_ENOENT, NULL,
2589                          _("Path '%s' not found"),
2590                          svn_dirent_local_style(path, scratch_pool));
2591            }
2592        }
2593#endif
2594    }
2595#endif
2596
2597  dirent = svn_io_dirent2_create(result_pool);
2598  map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo);
2599
2600  dirent->filesize = finfo.size;
2601  dirent->mtime = finfo.mtime;
2602
2603  *dirent_p = dirent;
2604
2605  return SVN_NO_ERROR;
2606}
2607
2608/* Pool userdata key for the error file passed to svn_io_start_cmd(). */
2609#define ERRFILE_KEY "svn-io-start-cmd-errfile"
2610
2611/* Handle an error from the child process (before command execution) by
2612   printing DESC and the error string corresponding to STATUS to stderr. */
2613static void
2614handle_child_process_error(apr_pool_t *pool, apr_status_t status,
2615                           const char *desc)
2616{
2617  char errbuf[256];
2618  apr_file_t *errfile;
2619  void *p;
2620
2621  /* We can't do anything if we get an error here, so just return. */
2622  if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool))
2623    return;
2624  errfile = p;
2625
2626  if (errfile)
2627    /* What we get from APR is in native encoding. */
2628    apr_file_printf(errfile, "%s: %s",
2629                    desc, apr_strerror(status, errbuf,
2630                                       sizeof(errbuf)));
2631}
2632
2633
2634svn_error_t *
2635svn_io_start_cmd3(apr_proc_t *cmd_proc,
2636                  const char *path,
2637                  const char *cmd,
2638                  const char *const *args,
2639                  const char *const *env,
2640                  svn_boolean_t inherit,
2641                  svn_boolean_t infile_pipe,
2642                  apr_file_t *infile,
2643                  svn_boolean_t outfile_pipe,
2644                  apr_file_t *outfile,
2645                  svn_boolean_t errfile_pipe,
2646                  apr_file_t *errfile,
2647                  apr_pool_t *pool)
2648{
2649  apr_status_t apr_err;
2650  apr_procattr_t *cmdproc_attr;
2651  int num_args;
2652  const char **args_native;
2653  const char *cmd_apr;
2654
2655  SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe));
2656  SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe));
2657  SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe));
2658
2659  /* Create the process attributes. */
2660  apr_err = apr_procattr_create(&cmdproc_attr, pool);
2661  if (apr_err)
2662    return svn_error_wrap_apr(apr_err,
2663                              _("Can't create process '%s' attributes"),
2664                              cmd);
2665
2666  /* Make sure we invoke cmd directly, not through a shell. */
2667  apr_err = apr_procattr_cmdtype_set(cmdproc_attr,
2668                                     inherit ? APR_PROGRAM_PATH : APR_PROGRAM);
2669  if (apr_err)
2670    return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"),
2671                              cmd);
2672
2673  /* Set the process's working directory. */
2674  if (path)
2675    {
2676      const char *path_apr;
2677
2678      SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
2679      apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr);
2680      if (apr_err)
2681        return svn_error_wrap_apr(apr_err,
2682                                  _("Can't set process '%s' directory"),
2683                                  cmd);
2684    }
2685
2686  /* Use requested inputs and outputs.
2687
2688     ### Unfortunately each of these apr functions creates a pipe and then
2689     overwrites the pipe file descriptor with the descriptor we pass
2690     in. The pipes can then never be closed. This is an APR bug. */
2691  if (infile)
2692    {
2693      apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL);
2694      if (apr_err)
2695        return svn_error_wrap_apr(apr_err,
2696                                  _("Can't set process '%s' child input"),
2697                                  cmd);
2698    }
2699  if (outfile)
2700    {
2701      apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL);
2702      if (apr_err)
2703        return svn_error_wrap_apr(apr_err,
2704                                  _("Can't set process '%s' child outfile"),
2705                                  cmd);
2706    }
2707  if (errfile)
2708    {
2709      apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL);
2710      if (apr_err)
2711        return svn_error_wrap_apr(apr_err,
2712                                  _("Can't set process '%s' child errfile"),
2713                                  cmd);
2714    }
2715
2716  /* Forward request for pipes to APR. */
2717  if (infile_pipe || outfile_pipe || errfile_pipe)
2718    {
2719      apr_err = apr_procattr_io_set(cmdproc_attr,
2720                                    infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
2721                                    outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
2722                                    errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE);
2723
2724      if (apr_err)
2725        return svn_error_wrap_apr(apr_err,
2726                                  _("Can't set process '%s' stdio pipes"),
2727                                  cmd);
2728    }
2729
2730  /* Have the child print any problems executing its program to errfile. */
2731  apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool);
2732  if (apr_err)
2733    return svn_error_wrap_apr(apr_err,
2734                              _("Can't set process '%s' child errfile for "
2735                                "error handler"),
2736                              cmd);
2737  apr_err = apr_procattr_child_errfn_set(cmdproc_attr,
2738                                         handle_child_process_error);
2739  if (apr_err)
2740    return svn_error_wrap_apr(apr_err,
2741                              _("Can't set process '%s' error handler"),
2742                              cmd);
2743
2744  /* Convert cmd and args from UTF-8 */
2745  SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool));
2746  for (num_args = 0; args[num_args]; num_args++)
2747    ;
2748  args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *));
2749  args_native[num_args] = NULL;
2750  while (num_args--)
2751    {
2752      /* ### Well, it turns out that on APR on Windows expects all
2753             program args to be in UTF-8. Callers of svn_io_run_cmd
2754             should be aware of that. */
2755      SVN_ERR(cstring_from_utf8(&args_native[num_args],
2756                                args[num_args], pool));
2757    }
2758
2759
2760  /* Start the cmd command. */
2761  apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native,
2762                            inherit ? NULL : env, cmdproc_attr, pool);
2763  if (apr_err)
2764    return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd);
2765
2766  return SVN_NO_ERROR;
2767}
2768
2769#undef ERRFILE_KEY
2770
2771svn_error_t *
2772svn_io_wait_for_cmd(apr_proc_t *cmd_proc,
2773                    const char *cmd,
2774                    int *exitcode,
2775                    apr_exit_why_e *exitwhy,
2776                    apr_pool_t *pool)
2777{
2778  apr_status_t apr_err;
2779  apr_exit_why_e exitwhy_val;
2780  int exitcode_val;
2781
2782  /* The Win32 apr_proc_wait doesn't set this... */
2783  exitwhy_val = APR_PROC_EXIT;
2784
2785  /* Wait for the cmd command to finish. */
2786  apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT);
2787  if (!APR_STATUS_IS_CHILD_DONE(apr_err))
2788    return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"),
2789                              cmd);
2790
2791  if (exitwhy)
2792    *exitwhy = exitwhy_val;
2793  else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)
2794           && APR_PROC_CHECK_CORE_DUMP(exitwhy_val))
2795    return svn_error_createf
2796      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2797       _("Process '%s' failed (signal %d, core dumped)"),
2798       cmd, exitcode_val);
2799  else if (APR_PROC_CHECK_SIGNALED(exitwhy_val))
2800    return svn_error_createf
2801      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2802       _("Process '%s' failed (signal %d)"),
2803       cmd, exitcode_val);
2804  else if (! APR_PROC_CHECK_EXIT(exitwhy_val))
2805    /* Don't really know what happened here. */
2806    return svn_error_createf
2807      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2808       _("Process '%s' failed (exitwhy %d, exitcode %d)"),
2809       cmd, exitwhy_val, exitcode_val);
2810
2811  if (exitcode)
2812    *exitcode = exitcode_val;
2813  else if (exitcode_val != 0)
2814    return svn_error_createf
2815      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2816       _("Process '%s' returned error exitcode %d"), cmd, exitcode_val);
2817
2818  return SVN_NO_ERROR;
2819}
2820
2821
2822svn_error_t *
2823svn_io_run_cmd(const char *path,
2824               const char *cmd,
2825               const char *const *args,
2826               int *exitcode,
2827               apr_exit_why_e *exitwhy,
2828               svn_boolean_t inherit,
2829               apr_file_t *infile,
2830               apr_file_t *outfile,
2831               apr_file_t *errfile,
2832               apr_pool_t *pool)
2833{
2834  apr_proc_t cmd_proc;
2835
2836  SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit,
2837                            FALSE, infile, FALSE, outfile, FALSE, errfile,
2838                            pool));
2839
2840  return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool);
2841}
2842
2843
2844svn_error_t *
2845svn_io_run_diff2(const char *dir,
2846                 const char *const *user_args,
2847                 int num_user_args,
2848                 const char *label1,
2849                 const char *label2,
2850                 const char *from,
2851                 const char *to,
2852                 int *pexitcode,
2853                 apr_file_t *outfile,
2854                 apr_file_t *errfile,
2855                 const char *diff_cmd,
2856                 apr_pool_t *pool)
2857{
2858  const char **args;
2859  int i;
2860  int exitcode;
2861  int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */
2862  apr_pool_t *subpool = svn_pool_create(pool);
2863
2864  if (pexitcode == NULL)
2865    pexitcode = &exitcode;
2866
2867  if (user_args != NULL)
2868    nargs += num_user_args;
2869  else
2870    nargs += 1; /* -u */
2871
2872  if (label1 != NULL)
2873    nargs += 2; /* the -L and the label itself */
2874  if (label2 != NULL)
2875    nargs += 2; /* the -L and the label itself */
2876
2877  args = apr_palloc(subpool, nargs * sizeof(char *));
2878
2879  i = 0;
2880  args[i++] = diff_cmd;
2881
2882  if (user_args != NULL)
2883    {
2884      int j;
2885      for (j = 0; j < num_user_args; ++j)
2886        args[i++] = user_args[j];
2887    }
2888  else
2889    args[i++] = "-u"; /* assume -u if the user didn't give us any args */
2890
2891  if (label1 != NULL)
2892    {
2893      args[i++] = "-L";
2894      args[i++] = label1;
2895    }
2896  if (label2 != NULL)
2897    {
2898      args[i++] = "-L";
2899      args[i++] = label2;
2900    }
2901
2902  args[i++] = svn_dirent_local_style(from, subpool);
2903  args[i++] = svn_dirent_local_style(to, subpool);
2904  args[i++] = NULL;
2905
2906  SVN_ERR_ASSERT(i == nargs);
2907
2908  SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE,
2909                         NULL, outfile, errfile, subpool));
2910
2911  /* The man page for (GNU) diff describes the return value as:
2912
2913       "An exit status of 0 means no differences were found, 1 means
2914        some differences were found, and 2 means trouble."
2915
2916     A return value of 2 typically occurs when diff cannot read its input
2917     or write to its output, but in any case we probably ought to return an
2918     error for anything other than 0 or 1 as the output is likely to be
2919     corrupt.
2920   */
2921  if (*pexitcode != 0 && *pexitcode != 1)
2922    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
2923                             _("'%s' returned %d"),
2924                             svn_dirent_local_style(diff_cmd, pool),
2925                             *pexitcode);
2926
2927  svn_pool_destroy(subpool);
2928
2929  return SVN_NO_ERROR;
2930}
2931
2932
2933svn_error_t *
2934svn_io_run_diff3_3(int *exitcode,
2935                   const char *dir,
2936                   const char *mine,
2937                   const char *older,
2938                   const char *yours,
2939                   const char *mine_label,
2940                   const char *older_label,
2941                   const char *yours_label,
2942                   apr_file_t *merged,
2943                   const char *diff3_cmd,
2944                   const apr_array_header_t *user_args,
2945                   apr_pool_t *pool)
2946{
2947  const char **args = apr_palloc(pool,
2948                                 sizeof(char*) * (13
2949                                                  + (user_args
2950                                                     ? user_args->nelts
2951                                                     : 1)));
2952#ifndef NDEBUG
2953  int nargs = 12;
2954#endif
2955  int i = 0;
2956
2957  /* Labels fall back to sensible defaults if not specified. */
2958  if (mine_label == NULL)
2959    mine_label = ".working";
2960  if (older_label == NULL)
2961    older_label = ".old";
2962  if (yours_label == NULL)
2963    yours_label = ".new";
2964
2965  /* Set up diff3 command line. */
2966  args[i++] = diff3_cmd;
2967  if (user_args)
2968    {
2969      int j;
2970      for (j = 0; j < user_args->nelts; ++j)
2971        args[i++] = APR_ARRAY_IDX(user_args, j, const char *);
2972#ifndef NDEBUG
2973      nargs += user_args->nelts;
2974#endif
2975    }
2976  else
2977    {
2978      args[i++] = "-E";             /* We tried "-A" here, but that caused
2979                                       overlapping identical changes to
2980                                       conflict.  See issue #682. */
2981#ifndef NDEBUG
2982      ++nargs;
2983#endif
2984    }
2985  args[i++] = "-m";
2986  args[i++] = "-L";
2987  args[i++] = mine_label;
2988  args[i++] = "-L";
2989  args[i++] = older_label;      /* note:  this label is ignored if
2990                                   using 2-part markers, which is the
2991                                   case with "-E". */
2992  args[i++] = "-L";
2993  args[i++] = yours_label;
2994#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG
2995  {
2996    svn_boolean_t has_arg;
2997
2998    /* ### FIXME: we really shouldn't be reading the config here;
2999       instead, the necessary bits should be passed in by the caller.
3000       But should we add another parameter to this function, when the
3001       whole external diff3 thing might eventually go away?  */
3002    apr_hash_t *config;
3003    svn_config_t *cfg;
3004
3005    SVN_ERR(svn_config_get_config(&config, pool));
3006    cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
3007    SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS,
3008                                SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG,
3009                                TRUE));
3010    if (has_arg)
3011      {
3012        const char *diff_cmd, *diff_utf8;
3013        svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
3014                       SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF);
3015        SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool));
3016        args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL);
3017#ifndef NDEBUG
3018        ++nargs;
3019#endif
3020      }
3021  }
3022#endif
3023  args[i++] = svn_dirent_local_style(mine, pool);
3024  args[i++] = svn_dirent_local_style(older, pool);
3025  args[i++] = svn_dirent_local_style(yours, pool);
3026  args[i++] = NULL;
3027#ifndef NDEBUG
3028  SVN_ERR_ASSERT(i == nargs);
3029#endif
3030
3031  /* Run diff3, output the merged text into the scratch file. */
3032  SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args,
3033                         exitcode, NULL,
3034                         TRUE, /* keep environment */
3035                         NULL, merged, NULL,
3036                         pool));
3037
3038  /* According to the diff3 docs, a '0' means the merge was clean, and
3039     '1' means conflict markers were found.  Anything else is real
3040     error. */
3041  if ((*exitcode != 0) && (*exitcode != 1))
3042    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
3043                             _("Error running '%s':  exitcode was %d, "
3044                               "args were:"
3045                               "\nin directory '%s', basenames:\n%s\n%s\n%s"),
3046                             svn_dirent_local_style(diff3_cmd, pool),
3047                             *exitcode,
3048                             svn_dirent_local_style(dir, pool),
3049                             /* Don't call svn_path_local_style() on
3050                                the basenames.  We don't want them to
3051                                be absolute, and we don't need the
3052                                separator conversion. */
3053                             mine, older, yours);
3054
3055  return SVN_NO_ERROR;
3056}
3057
3058
3059/* Canonicalize a string for hashing.  Modifies KEY in place. */
3060static APR_INLINE char *
3061fileext_tolower(char *key)
3062{
3063  register char *p;
3064  for (p = key; *p != 0; ++p)
3065    *p = (char)apr_tolower(*p);
3066  return key;
3067}
3068
3069
3070svn_error_t *
3071svn_io_parse_mimetypes_file(apr_hash_t **type_map,
3072                            const char *mimetypes_file,
3073                            apr_pool_t *pool)
3074{
3075  svn_error_t *err = SVN_NO_ERROR;
3076  apr_hash_t *types = apr_hash_make(pool);
3077  svn_boolean_t eof = FALSE;
3078  svn_stringbuf_t *buf;
3079  apr_pool_t *subpool = svn_pool_create(pool);
3080  apr_file_t *types_file;
3081  svn_stream_t *mimetypes_stream;
3082
3083  SVN_ERR(svn_io_file_open(&types_file, mimetypes_file,
3084                           APR_READ, APR_OS_DEFAULT, pool));
3085  mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool);
3086
3087  while (1)
3088    {
3089      apr_array_header_t *tokens;
3090      const char *type;
3091
3092      svn_pool_clear(subpool);
3093
3094      /* Read a line. */
3095      if ((err = svn_stream_readline(mimetypes_stream, &buf,
3096                                     APR_EOL_STR, &eof, subpool)))
3097        break;
3098
3099      /* Only pay attention to non-empty, non-comment lines. */
3100      if (buf->len)
3101        {
3102          int i;
3103
3104          if (buf->data[0] == '#')
3105            continue;
3106
3107          /* Tokenize (into our return pool). */
3108          tokens = svn_cstring_split(buf->data, " \t", TRUE, pool);
3109          if (tokens->nelts < 2)
3110            continue;
3111
3112          /* The first token in a multi-token line is the media type.
3113             Subsequent tokens are filename extensions associated with
3114             that media type. */
3115          type = APR_ARRAY_IDX(tokens, 0, const char *);
3116          for (i = 1; i < tokens->nelts; i++)
3117            {
3118              /* We can safely address 'ext' as a non-const string because
3119               * we know svn_cstring_split() allocated it in 'pool' for us. */
3120              char *ext = APR_ARRAY_IDX(tokens, i, char *);
3121              fileext_tolower(ext);
3122              svn_hash_sets(types, ext, type);
3123            }
3124        }
3125      if (eof)
3126        break;
3127    }
3128  svn_pool_destroy(subpool);
3129
3130  /* If there was an error above, close the file (ignoring any error
3131     from *that*) and return the originally error. */
3132  if (err)
3133    {
3134      svn_error_clear(svn_stream_close(mimetypes_stream));
3135      return err;
3136    }
3137
3138  /* Close the stream (which closes the underlying file, too). */
3139  SVN_ERR(svn_stream_close(mimetypes_stream));
3140
3141  *type_map = types;
3142  return SVN_NO_ERROR;
3143}
3144
3145
3146svn_error_t *
3147svn_io_detect_mimetype2(const char **mimetype,
3148                        const char *file,
3149                        apr_hash_t *mimetype_map,
3150                        apr_pool_t *pool)
3151{
3152  static const char * const generic_binary = "application/octet-stream";
3153
3154  svn_node_kind_t kind;
3155  apr_file_t *fh;
3156  svn_error_t *err;
3157  unsigned char block[1024];
3158  apr_size_t amt_read = sizeof(block);
3159
3160  /* Default return value is NULL. */
3161  *mimetype = NULL;
3162
3163  /* If there is a mimetype_map provided, we'll first try to look up
3164     our file's extension in the map.  Failing that, we'll run the
3165     heuristic. */
3166  if (mimetype_map)
3167    {
3168      const char *type_from_map;
3169      char *path_ext; /* Can point to physical const memory but only when
3170                         svn_path_splitext sets it to "". */
3171      svn_path_splitext(NULL, (const char **)&path_ext, file, pool);
3172      fileext_tolower(path_ext);
3173      if ((type_from_map = svn_hash_gets(mimetype_map, path_ext)))
3174        {
3175          *mimetype = type_from_map;
3176          return SVN_NO_ERROR;
3177        }
3178    }
3179
3180  /* See if this file even exists, and make sure it really is a file. */
3181  SVN_ERR(svn_io_check_path(file, &kind, pool));
3182  if (kind != svn_node_file)
3183    return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
3184                             _("Can't detect MIME type of non-file '%s'"),
3185                             svn_dirent_local_style(file, pool));
3186
3187  SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool));
3188
3189  /* Read a block of data from FILE. */
3190  err = svn_io_file_read(fh, block, &amt_read, pool);
3191  if (err && ! APR_STATUS_IS_EOF(err->apr_err))
3192    return err;
3193  svn_error_clear(err);
3194
3195  /* Now close the file.  No use keeping it open any more.  */
3196  SVN_ERR(svn_io_file_close(fh, pool));
3197
3198  if (svn_io_is_binary_data(block, amt_read))
3199    *mimetype = generic_binary;
3200
3201  return SVN_NO_ERROR;
3202}
3203
3204
3205svn_boolean_t
3206svn_io_is_binary_data(const void *data, apr_size_t len)
3207{
3208  const unsigned char *buf = data;
3209
3210  if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF)
3211    {
3212      /* This is an empty UTF-8 file which only contains the UTF-8 BOM.
3213       * Treat it as plain text. */
3214      return FALSE;
3215    }
3216
3217  /* Right now, this function is going to be really stupid.  It's
3218     going to examine the block of data, and make sure that 15%
3219     of the bytes are such that their value is in the ranges 0x07-0x0D
3220     or 0x20-0x7F, and that none of those bytes is 0x00.  If those
3221     criteria are not met, we're calling it binary.
3222
3223     NOTE:  Originally, I intended to target 85% of the bytes being in
3224     the specified ranges, but I flubbed the condition.  At any rate,
3225     folks aren't complaining, so I'm not sure that it's worth
3226     adjusting this retroactively now.  --cmpilato  */
3227  if (len > 0)
3228    {
3229      apr_size_t i;
3230      apr_size_t binary_count = 0;
3231
3232      /* Run through the data we've read, counting the 'binary-ish'
3233         bytes.  HINT: If we see a 0x00 byte, we'll set our count to its
3234         max and stop reading the file. */
3235      for (i = 0; i < len; i++)
3236        {
3237          if (buf[i] == 0)
3238            {
3239              binary_count = len;
3240              break;
3241            }
3242          if ((buf[i] < 0x07)
3243              || ((buf[i] > 0x0D) && (buf[i] < 0x20))
3244              || (buf[i] > 0x7F))
3245            {
3246              binary_count++;
3247            }
3248        }
3249
3250      return (((binary_count * 1000) / len) > 850);
3251    }
3252
3253  return FALSE;
3254}
3255
3256
3257svn_error_t *
3258svn_io_detect_mimetype(const char **mimetype,
3259                       const char *file,
3260                       apr_pool_t *pool)
3261{
3262  return svn_io_detect_mimetype2(mimetype, file, NULL, pool);
3263}
3264
3265
3266svn_error_t *
3267svn_io_file_open(apr_file_t **new_file, const char *fname,
3268                 apr_int32_t flag, apr_fileperms_t perm,
3269                 apr_pool_t *pool)
3270{
3271  const char *fname_apr;
3272  apr_status_t status;
3273
3274  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
3275  status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE,
3276                     pool);
3277
3278  if (status)
3279    return svn_error_wrap_apr(status, _("Can't open file '%s'"),
3280                              svn_dirent_local_style(fname, pool));
3281  else
3282    return SVN_NO_ERROR;
3283}
3284
3285
3286static APR_INLINE svn_error_t *
3287do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
3288                           const char *msg, const char *msg_no_name,
3289                           apr_pool_t *pool)
3290{
3291  const char *name;
3292  svn_error_t *err;
3293
3294  if (! status)
3295    return SVN_NO_ERROR;
3296
3297  err = svn_io_file_name_get(&name, file, pool);
3298  if (err)
3299    name = NULL;
3300  svn_error_clear(err);
3301
3302  /* ### Issue #3014: Return a specific error for broken pipes,
3303   * ### with a single element in the error chain. */
3304  if (SVN__APR_STATUS_IS_EPIPE(status))
3305    return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
3306
3307  if (name)
3308    return svn_error_wrap_apr(status, _(msg),
3309                              try_utf8_from_internal_style(name, pool));
3310  else
3311    return svn_error_wrap_apr(status, "%s", _(msg_no_name));
3312}
3313
3314
3315svn_error_t *
3316svn_io_file_close(apr_file_t *file, apr_pool_t *pool)
3317{
3318  return do_io_file_wrapper_cleanup(file, apr_file_close(file),
3319                                    N_("Can't close file '%s'"),
3320                                    N_("Can't close stream"),
3321                                    pool);
3322}
3323
3324
3325svn_error_t *
3326svn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool)
3327{
3328  return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file),
3329                                    N_("Can't read file '%s'"),
3330                                    N_("Can't read stream"),
3331                                    pool);
3332}
3333
3334
3335svn_error_t *
3336svn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool)
3337{
3338  return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file),
3339                                    N_("Can't write file '%s'"),
3340                                    N_("Can't write stream"),
3341                                    pool);
3342}
3343
3344
3345svn_error_t *
3346svn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted,
3347                     apr_file_t *file, apr_pool_t *pool)
3348{
3349  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
3350  wanted &= ~SVN__APR_FINFO_MASK_OUT;
3351
3352  return do_io_file_wrapper_cleanup(
3353             file, apr_file_info_get(finfo, wanted, file),
3354             N_("Can't get attribute information from file '%s'"),
3355             N_("Can't get attribute information from stream"),
3356             pool);
3357}
3358
3359
3360svn_error_t *
3361svn_io_file_read(apr_file_t *file, void *buf,
3362                 apr_size_t *nbytes, apr_pool_t *pool)
3363{
3364  return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes),
3365                                    N_("Can't read file '%s'"),
3366                                    N_("Can't read stream"),
3367                                    pool);
3368}
3369
3370
3371svn_error_t *
3372svn_io_file_read_full2(apr_file_t *file, void *buf,
3373                       apr_size_t nbytes, apr_size_t *bytes_read,
3374                       svn_boolean_t *hit_eof,
3375                       apr_pool_t *pool)
3376{
3377  apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read);
3378  if (hit_eof)
3379    {
3380      if (APR_STATUS_IS_EOF(status))
3381        {
3382          *hit_eof = TRUE;
3383          return SVN_NO_ERROR;
3384        }
3385      else
3386        *hit_eof = FALSE;
3387    }
3388
3389  return do_io_file_wrapper_cleanup(file, status,
3390                                    N_("Can't read file '%s'"),
3391                                    N_("Can't read stream"),
3392                                    pool);
3393}
3394
3395
3396svn_error_t *
3397svn_io_file_seek(apr_file_t *file, apr_seek_where_t where,
3398                 apr_off_t *offset, apr_pool_t *pool)
3399{
3400  return do_io_file_wrapper_cleanup(
3401             file, apr_file_seek(file, where, offset),
3402             N_("Can't set position pointer in file '%s'"),
3403             N_("Can't set position pointer in stream"),
3404             pool);
3405}
3406
3407
3408svn_error_t *
3409svn_io_file_write(apr_file_t *file, const void *buf,
3410                  apr_size_t *nbytes, apr_pool_t *pool)
3411{
3412  return svn_error_trace(do_io_file_wrapper_cleanup(
3413     file, apr_file_write(file, buf, nbytes),
3414     N_("Can't write to file '%s'"),
3415     N_("Can't write to stream"),
3416     pool));
3417}
3418
3419
3420svn_error_t *
3421svn_io_file_write_full(apr_file_t *file, const void *buf,
3422                       apr_size_t nbytes, apr_size_t *bytes_written,
3423                       apr_pool_t *pool)
3424{
3425  /* We cannot simply call apr_file_write_full on Win32 as it may fail
3426     for larger values of NBYTES. In that case, we have to emulate the
3427     "_full" part here. Thus, always call apr_file_write directly on
3428     Win32 as this minimizes overhead for small data buffers. */
3429#ifdef WIN32
3430#define MAXBUFSIZE 30*1024
3431  apr_size_t bw = nbytes;
3432  apr_size_t to_write = nbytes;
3433
3434  /* try a simple "write everything at once" first */
3435  apr_status_t rv = apr_file_write(file, buf, &bw);
3436  buf = (char *)buf + bw;
3437  to_write -= bw;
3438
3439  /* if the OS cannot handle that, use smaller chunks */
3440  if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY)
3441      && nbytes > MAXBUFSIZE)
3442    {
3443      do {
3444        bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write;
3445        rv = apr_file_write(file, buf, &bw);
3446        buf = (char *)buf + bw;
3447        to_write -= bw;
3448      } while (rv == APR_SUCCESS && to_write > 0);
3449    }
3450
3451  /* bytes_written may actually be NULL */
3452  if (bytes_written)
3453    *bytes_written = nbytes - to_write;
3454#undef MAXBUFSIZE
3455#else
3456  apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written);
3457#endif
3458
3459  return svn_error_trace(do_io_file_wrapper_cleanup(
3460     file, rv,
3461     N_("Can't write to file '%s'"),
3462     N_("Can't write to stream"),
3463     pool));
3464}
3465
3466
3467svn_error_t *
3468svn_io_write_unique(const char **tmp_path,
3469                    const char *dirpath,
3470                    const void *buf,
3471                    apr_size_t nbytes,
3472                    svn_io_file_del_t delete_when,
3473                    apr_pool_t *pool)
3474{
3475  apr_file_t *new_file;
3476  svn_error_t *err;
3477
3478  SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath,
3479                                   delete_when, pool, pool));
3480
3481  err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool);
3482
3483  if (!err)
3484    err = svn_io_file_flush_to_disk(new_file, pool);
3485
3486  return svn_error_trace(
3487                  svn_error_compose_create(err,
3488                                           svn_io_file_close(new_file, pool)));
3489}
3490
3491
3492svn_error_t *
3493svn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool)
3494{
3495  /* This is a work-around. APR would flush the write buffer
3496     _after_ truncating the file causing now invalid buffered
3497     data to be written behind OFFSET. */
3498  SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
3499                                     N_("Can't flush file '%s'"),
3500                                     N_("Can't flush stream"),
3501                                     pool));
3502
3503  return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset),
3504                                    N_("Can't truncate file '%s'"),
3505                                    N_("Can't truncate stream"),
3506                                    pool);
3507}
3508
3509
3510svn_error_t *
3511svn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit,
3512                        apr_pool_t *pool)
3513{
3514  /* variables */
3515  apr_size_t total_read = 0;
3516  svn_boolean_t eof = FALSE;
3517  const char *name;
3518  svn_error_t *err;
3519  apr_size_t buf_size = *limit;
3520
3521  while (buf_size > 0)
3522    {
3523      /* read a fair chunk of data at once. But don't get too ambitious
3524       * as that would result in too much waste. Also make sure we can
3525       * put a NUL after the last byte read.
3526       */
3527      apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128;
3528      apr_size_t bytes_read = 0;
3529      char *eol;
3530
3531      if (to_read == 0)
3532        break;
3533
3534      /* read data block (or just a part of it) */
3535      SVN_ERR(svn_io_file_read_full2(file, buf, to_read,
3536                                     &bytes_read, &eof, pool));
3537
3538      /* look or a newline char */
3539      buf[bytes_read] = 0;
3540      eol = strchr(buf, '\n');
3541      if (eol)
3542        {
3543          apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read;
3544
3545          *eol = 0;
3546          *limit = total_read + (eol - buf);
3547
3548          /* correct the file pointer:
3549           * appear as though we just had read the newline char
3550           */
3551          SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
3552
3553          return SVN_NO_ERROR;
3554        }
3555      else if (eof)
3556        {
3557          /* no EOL found but we hit the end of the file.
3558           * Generate a nice EOF error object and return it.
3559           */
3560          char dummy;
3561          SVN_ERR(svn_io_file_getc(&dummy, file, pool));
3562        }
3563
3564      /* next data chunk */
3565      buf_size -= bytes_read;
3566      buf += bytes_read;
3567      total_read += bytes_read;
3568    }
3569
3570  /* buffer limit has been exceeded without finding the EOL */
3571  err = svn_io_file_name_get(&name, file, pool);
3572  if (err)
3573    name = NULL;
3574  svn_error_clear(err);
3575
3576  if (name)
3577    return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
3578                             _("Can't read length line in file '%s'"),
3579                             svn_dirent_local_style(name, pool));
3580  else
3581    return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
3582                            _("Can't read length line in stream"));
3583}
3584
3585
3586svn_error_t *
3587svn_io_stat(apr_finfo_t *finfo, const char *fname,
3588            apr_int32_t wanted, apr_pool_t *pool)
3589{
3590  apr_status_t status;
3591  const char *fname_apr;
3592
3593  /* APR doesn't like "" directories */
3594  if (fname[0] == '\0')
3595    fname = ".";
3596
3597  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
3598
3599  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
3600  wanted &= ~SVN__APR_FINFO_MASK_OUT;
3601
3602  status = apr_stat(finfo, fname_apr, wanted, pool);
3603  if (status)
3604    return svn_error_wrap_apr(status, _("Can't stat '%s'"),
3605                              svn_dirent_local_style(fname, pool));
3606
3607  return SVN_NO_ERROR;
3608}
3609
3610
3611svn_error_t *
3612svn_io_file_rename(const char *from_path, const char *to_path,
3613                   apr_pool_t *pool)
3614{
3615  apr_status_t status = APR_SUCCESS;
3616  const char *from_path_apr, *to_path_apr;
3617
3618  SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool));
3619  SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool));
3620
3621  status = apr_file_rename(from_path_apr, to_path_apr, pool);
3622
3623#if defined(WIN32) || defined(__OS2__)
3624  /* If the target file is read only NTFS reports EACCESS and
3625     FAT/FAT32 reports EEXIST */
3626  if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
3627    {
3628      /* Set the destination file writable because Windows will not
3629         allow us to rename when to_path is read-only, but will
3630         allow renaming when from_path is read only. */
3631      SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
3632
3633      status = apr_file_rename(from_path_apr, to_path_apr, pool);
3634    }
3635  WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool));
3636#endif /* WIN32 || __OS2__ */
3637
3638  if (status)
3639    return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"),
3640                              svn_dirent_local_style(from_path, pool),
3641                              svn_dirent_local_style(to_path, pool));
3642
3643  return SVN_NO_ERROR;
3644}
3645
3646
3647svn_error_t *
3648svn_io_file_move(const char *from_path, const char *to_path,
3649                 apr_pool_t *pool)
3650{
3651  svn_error_t *err = svn_io_file_rename(from_path, to_path, pool);
3652
3653  if (err && APR_STATUS_IS_EXDEV(err->apr_err))
3654    {
3655      const char *tmp_to_path;
3656
3657      svn_error_clear(err);
3658
3659      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path,
3660                                       svn_dirent_dirname(to_path, pool),
3661                                       svn_io_file_del_none,
3662                                       pool, pool));
3663
3664      err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool);
3665      if (err)
3666        goto failed_tmp;
3667
3668      err = svn_io_file_rename(tmp_to_path, to_path, pool);
3669      if (err)
3670        goto failed_tmp;
3671
3672      err = svn_io_remove_file2(from_path, FALSE, pool);
3673      if (! err)
3674        return SVN_NO_ERROR;
3675
3676      svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool));
3677
3678      return err;
3679
3680    failed_tmp:
3681      svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool));
3682    }
3683
3684  return err;
3685}
3686
3687/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden.
3688   HIDDEN determines if the hidden attribute
3689   should be set on the newly created directory. */
3690static svn_error_t *
3691dir_make(const char *path, apr_fileperms_t perm,
3692         svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool)
3693{
3694  apr_status_t status;
3695  const char *path_apr;
3696
3697  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
3698
3699  /* APR doesn't like "" directories */
3700  if (path_apr[0] == '\0')
3701    path_apr = ".";
3702
3703#if (APR_OS_DEFAULT & APR_WSTICKY)
3704  /* The APR shipped with httpd 2.0.50 contains a bug where
3705     APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits.
3706     There is a special case for file creation, but not directory
3707     creation, so directories wind up getting created with the sticky
3708     bit set.  (There is no such thing as a setuid directory, and the
3709     setgid bit is apparently ignored at mkdir() time.)  If we detect
3710     this problem, work around it by unsetting those bits if we are
3711     passed APR_OS_DEFAULT. */
3712  if (perm == APR_OS_DEFAULT)
3713    perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY);
3714#endif
3715
3716  status = apr_dir_make(path_apr, perm, pool);
3717  WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
3718
3719  if (status)
3720    return svn_error_wrap_apr(status, _("Can't create directory '%s'"),
3721                              svn_dirent_local_style(path, pool));
3722
3723#ifdef APR_FILE_ATTR_HIDDEN
3724  if (hidden)
3725    {
3726#ifndef WIN32
3727      status = apr_file_attrs_set(path_apr,
3728                                  APR_FILE_ATTR_HIDDEN,
3729                                  APR_FILE_ATTR_HIDDEN,
3730                                  pool);
3731#else
3732    /* on Windows, use our wrapper so we can also set the
3733       FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */
3734    status = io_win_file_attrs_set(path_apr,
3735                                   FILE_ATTRIBUTE_HIDDEN |
3736                                   FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
3737                                   FILE_ATTRIBUTE_HIDDEN |
3738                                   FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
3739                                   pool);
3740
3741#endif
3742      if (status)
3743        return svn_error_wrap_apr(status, _("Can't hide directory '%s'"),
3744                                  svn_dirent_local_style(path, pool));
3745    }
3746#endif
3747
3748/* Windows does not implement sgid. Skip here because retrieving
3749   the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented
3750   to be 'incredibly expensive'. */
3751#ifndef WIN32
3752  if (sgid)
3753    {
3754      apr_finfo_t finfo;
3755
3756      /* Per our contract, don't do error-checking.  Some filesystems
3757       * don't support the sgid bit, and that's okay. */
3758      status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool);
3759
3760      if (!status)
3761        apr_file_perms_set(path_apr, finfo.protection | APR_GSETID);
3762    }
3763#endif
3764
3765  return SVN_NO_ERROR;
3766}
3767
3768svn_error_t *
3769svn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool)
3770{
3771  return dir_make(path, perm, FALSE, FALSE, pool);
3772}
3773
3774svn_error_t *
3775svn_io_dir_make_hidden(const char *path, apr_fileperms_t perm,
3776                       apr_pool_t *pool)
3777{
3778  return dir_make(path, perm, TRUE, FALSE, pool);
3779}
3780
3781svn_error_t *
3782svn_io_dir_make_sgid(const char *path, apr_fileperms_t perm,
3783                     apr_pool_t *pool)
3784{
3785  return dir_make(path, perm, FALSE, TRUE, pool);
3786}
3787
3788
3789svn_error_t *
3790svn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool)
3791{
3792  apr_status_t status;
3793  const char *dirname_apr;
3794
3795  /* APR doesn't like "" directories */
3796  if (dirname[0] == '\0')
3797    dirname = ".";
3798
3799  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3800
3801  status = apr_dir_open(new_dir, dirname_apr, pool);
3802  if (status)
3803    return svn_error_wrap_apr(status, _("Can't open directory '%s'"),
3804                              svn_dirent_local_style(dirname, pool));
3805
3806  return SVN_NO_ERROR;
3807}
3808
3809svn_error_t *
3810svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool)
3811{
3812  apr_status_t status;
3813  const char *dirname_apr;
3814
3815  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3816
3817  status = apr_dir_remove(dirname_apr, pool);
3818
3819#ifdef WIN32
3820  {
3821    svn_boolean_t retry = TRUE;
3822
3823    if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY)
3824      {
3825        apr_status_t empty_status = dir_is_empty(dirname_apr, pool);
3826
3827        if (APR_STATUS_IS_ENOTEMPTY(empty_status))
3828          retry = FALSE;
3829      }
3830
3831    if (retry)
3832      {
3833        WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool));
3834      }
3835  }
3836#endif
3837  if (status)
3838    return svn_error_wrap_apr(status, _("Can't remove directory '%s'"),
3839                              svn_dirent_local_style(dirname, pool));
3840
3841  return SVN_NO_ERROR;
3842}
3843
3844
3845svn_error_t *
3846svn_io_dir_read(apr_finfo_t *finfo,
3847                apr_int32_t wanted,
3848                apr_dir_t *thedir,
3849                apr_pool_t *pool)
3850{
3851  apr_status_t status;
3852
3853  status = apr_dir_read(finfo, wanted, thedir);
3854
3855  if (status)
3856    return svn_error_wrap_apr(status, _("Can't read directory"));
3857
3858  /* It would be nice to use entry_name_to_utf8() below, but can we
3859     get the dir's path out of an apr_dir_t?  I don't see a reliable
3860     way to do it. */
3861
3862  if (finfo->fname)
3863    SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool));
3864
3865  if (finfo->name)
3866    SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool));
3867
3868  return SVN_NO_ERROR;
3869}
3870
3871svn_error_t *
3872svn_io_dir_close(apr_dir_t *thedir)
3873{
3874  apr_status_t apr_err = apr_dir_close(thedir);
3875  if (apr_err)
3876    return svn_error_wrap_apr(apr_err, _("Error closing directory"));
3877
3878  return SVN_NO_ERROR;
3879}
3880
3881svn_error_t *
3882svn_io_dir_walk2(const char *dirname,
3883                 apr_int32_t wanted,
3884                 svn_io_walk_func_t walk_func,
3885                 void *walk_baton,
3886                 apr_pool_t *pool)
3887{
3888  apr_status_t apr_err;
3889  apr_dir_t *handle;
3890  apr_pool_t *subpool;
3891  const char *dirname_apr;
3892  apr_finfo_t finfo;
3893
3894  wanted |= APR_FINFO_TYPE | APR_FINFO_NAME;
3895
3896  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
3897  wanted &= ~SVN__APR_FINFO_MASK_OUT;
3898
3899  /* The documentation for apr_dir_read used to state that "." and ".."
3900     will be returned as the first two files, but it doesn't
3901     work that way in practice, in particular ext3 on Linux-2.6 doesn't
3902     follow the rules.  For details see
3903     http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666
3904
3905     If APR ever does implement "dot-first" then it would be possible to
3906     remove the svn_io_stat and walk_func calls and use the walk_func
3907     inside the loop.
3908
3909     Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is
3910     documented to provide it, so we have to do a bit extra. */
3911  SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool));
3912  SVN_ERR(cstring_from_utf8(&finfo.name,
3913                            svn_dirent_basename(dirname, pool),
3914                            pool));
3915  finfo.valid |= APR_FINFO_NAME;
3916  SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool));
3917
3918  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3919
3920  /* APR doesn't like "" directories */
3921  if (dirname_apr[0] == '\0')
3922    dirname_apr = ".";
3923
3924  apr_err = apr_dir_open(&handle, dirname_apr, pool);
3925  if (apr_err)
3926    return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"),
3927                              svn_dirent_local_style(dirname, pool));
3928
3929  /* iteration subpool */
3930  subpool = svn_pool_create(pool);
3931
3932  while (1)
3933    {
3934      const char *name_utf8;
3935      const char *full_path;
3936
3937      svn_pool_clear(subpool);
3938
3939      apr_err = apr_dir_read(&finfo, wanted, handle);
3940      if (APR_STATUS_IS_ENOENT(apr_err))
3941        break;
3942      else if (apr_err)
3943        {
3944          return svn_error_wrap_apr(apr_err,
3945                                    _("Can't read directory entry in '%s'"),
3946                                    svn_dirent_local_style(dirname, pool));
3947        }
3948
3949      if (finfo.filetype == APR_DIR)
3950        {
3951          if (finfo.name[0] == '.'
3952              && (finfo.name[1] == '\0'
3953                  || (finfo.name[1] == '.' && finfo.name[2] == '\0')))
3954            /* skip "." and ".." */
3955            continue;
3956
3957          /* some other directory. recurse. it will be passed to the
3958             callback inside the recursion. */
3959          SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
3960                                     subpool));
3961          full_path = svn_dirent_join(dirname, name_utf8, subpool);
3962          SVN_ERR(svn_io_dir_walk2(full_path,
3963                                   wanted,
3964                                   walk_func,
3965                                   walk_baton,
3966                                   subpool));
3967        }
3968      else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
3969        {
3970          /* some other directory. pass it to the callback. */
3971          SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
3972                                     subpool));
3973          full_path = svn_dirent_join(dirname, name_utf8, subpool);
3974          SVN_ERR((*walk_func)(walk_baton,
3975                               full_path,
3976                               &finfo,
3977                               subpool));
3978        }
3979      /* else:
3980         Some other type of file; skip it for now.  We've reserved the
3981         right to expand our coverage here in the future, though,
3982         without revving this API.
3983      */
3984    }
3985
3986  svn_pool_destroy(subpool);
3987
3988  apr_err = apr_dir_close(handle);
3989  if (apr_err)
3990    return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"),
3991                              svn_dirent_local_style(dirname, pool));
3992
3993  return SVN_NO_ERROR;
3994}
3995
3996
3997
3998/**
3999 * Determine if a directory is empty or not.
4000 * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not.
4001 * @param path The directory.
4002 * @param pool Used for temporary allocation.
4003 * @remark If path is not a directory, or some other error occurs,
4004 * then return the appropriate apr status code.
4005 *
4006 * (This function is written in APR style, in anticipation of
4007 * perhaps someday being moved to APR as 'apr_dir_is_empty'.)
4008 */
4009static apr_status_t
4010dir_is_empty(const char *dir, apr_pool_t *pool)
4011{
4012  apr_status_t apr_err;
4013  apr_dir_t *dir_handle;
4014  apr_finfo_t finfo;
4015  apr_status_t retval = APR_SUCCESS;
4016
4017  /* APR doesn't like "" directories */
4018  if (dir[0] == '\0')
4019    dir = ".";
4020
4021  apr_err = apr_dir_open(&dir_handle, dir, pool);
4022  if (apr_err != APR_SUCCESS)
4023    return apr_err;
4024
4025  for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle);
4026       apr_err == APR_SUCCESS;
4027       apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle))
4028    {
4029      /* Ignore entries for this dir and its parent, robustly.
4030         (APR promises that they'll come first, so technically
4031         this guard could be moved outside the loop.  But Ryan Bloom
4032         says he doesn't believe it, and I believe him. */
4033      if (! (finfo.name[0] == '.'
4034             && (finfo.name[1] == '\0'
4035                 || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
4036        {
4037          retval = APR_ENOTEMPTY;
4038          break;
4039        }
4040    }
4041
4042  /* Make sure we broke out of the loop for the right reason. */
4043  if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err))
4044    return apr_err;
4045
4046  apr_err = apr_dir_close(dir_handle);
4047  if (apr_err != APR_SUCCESS)
4048    return apr_err;
4049
4050  return retval;
4051}
4052
4053
4054svn_error_t *
4055svn_io_dir_empty(svn_boolean_t *is_empty_p,
4056                 const char *path,
4057                 apr_pool_t *pool)
4058{
4059  apr_status_t status;
4060  const char *path_apr;
4061
4062  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
4063
4064  status = dir_is_empty(path_apr, pool);
4065
4066  if (!status)
4067    *is_empty_p = TRUE;
4068  else if (APR_STATUS_IS_ENOTEMPTY(status))
4069    *is_empty_p = FALSE;
4070  else
4071    return svn_error_wrap_apr(status, _("Can't check directory '%s'"),
4072                              svn_dirent_local_style(path, pool));
4073
4074  return SVN_NO_ERROR;
4075}
4076
4077
4078
4079/*** Version/format files ***/
4080
4081svn_error_t *
4082svn_io_write_version_file(const char *path,
4083                          int version,
4084                          apr_pool_t *pool)
4085{
4086  const char *path_tmp;
4087  const char *format_contents = apr_psprintf(pool, "%d\n", version);
4088
4089  SVN_ERR_ASSERT(version >= 0);
4090
4091  SVN_ERR(svn_io_write_unique(&path_tmp,
4092                              svn_dirent_dirname(path, pool),
4093                              format_contents, strlen(format_contents),
4094                              svn_io_file_del_none, pool));
4095
4096#if defined(WIN32) || defined(__OS2__)
4097  /* make the destination writable, but only on Windows, because
4098     Windows does not let us replace read-only files. */
4099  SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
4100#endif /* WIN32 || __OS2__ */
4101
4102  /* rename the temp file as the real destination */
4103  SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
4104
4105  /* And finally remove the perms to make it read only */
4106  return svn_io_set_file_read_only(path, FALSE, pool);
4107}
4108
4109
4110svn_error_t *
4111svn_io_read_version_file(int *version,
4112                         const char *path,
4113                         apr_pool_t *pool)
4114{
4115  apr_file_t *format_file;
4116  char buf[80];
4117  apr_size_t len;
4118  svn_error_t *err;
4119
4120  /* Read a chunk of data from PATH */
4121  SVN_ERR(svn_io_file_open(&format_file, path, APR_READ,
4122                           APR_OS_DEFAULT, pool));
4123  len = sizeof(buf);
4124  err = svn_io_file_read(format_file, buf, &len, pool);
4125
4126  /* Close the file. */
4127  SVN_ERR(svn_error_compose_create(err,
4128                                   svn_io_file_close(format_file, pool)));
4129
4130  /* If there was no data in PATH, return an error. */
4131  if (len == 0)
4132    return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
4133                             _("Reading '%s'"),
4134                             svn_dirent_local_style(path, pool));
4135
4136  /* Check that the first line contains only digits. */
4137  {
4138    apr_size_t i;
4139
4140    for (i = 0; i < len; ++i)
4141      {
4142        char c = buf[i];
4143
4144        if (i > 0 && (c == '\r' || c == '\n'))
4145          {
4146            buf[i] = '\0';
4147            break;
4148          }
4149        if (! svn_ctype_isdigit(c))
4150          return svn_error_createf
4151            (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
4152             _("First line of '%s' contains non-digit"),
4153             svn_dirent_local_style(path, pool));
4154      }
4155  }
4156
4157  /* Convert to integer. */
4158  SVN_ERR(svn_cstring_atoi(version, buf));
4159
4160  return SVN_NO_ERROR;
4161}
4162
4163
4164
4165/* Do a byte-for-byte comparison of FILE1 and FILE2. */
4166static svn_error_t *
4167contents_identical_p(svn_boolean_t *identical_p,
4168                     const char *file1,
4169                     const char *file2,
4170                     apr_pool_t *pool)
4171{
4172  svn_error_t *err;
4173  apr_size_t bytes_read1, bytes_read2;
4174  char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
4175  char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
4176  apr_file_t *file1_h;
4177  apr_file_t *file2_h;
4178  svn_boolean_t eof1 = FALSE;
4179  svn_boolean_t eof2 = FALSE;
4180
4181  SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
4182                           pool));
4183
4184  err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
4185                         pool);
4186
4187  if (err)
4188    return svn_error_trace(
4189               svn_error_compose_create(err,
4190                                        svn_io_file_close(file1_h, pool)));
4191
4192  *identical_p = TRUE;  /* assume TRUE, until disproved below */
4193  while (!err && !eof1 && !eof2)
4194    {
4195      err = svn_io_file_read_full2(file1_h, buf1,
4196                                   SVN__STREAM_CHUNK_SIZE, &bytes_read1,
4197                                   &eof1, pool);
4198      if (err)
4199          break;
4200
4201      err = svn_io_file_read_full2(file2_h, buf2,
4202                                   SVN__STREAM_CHUNK_SIZE, &bytes_read2,
4203                                   &eof2, pool);
4204      if (err)
4205          break;
4206
4207      if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1))
4208        {
4209          *identical_p = FALSE;
4210          break;
4211        }
4212    }
4213
4214  /* Special case: one file being a prefix of the other and the shorter
4215   * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */
4216  if (!err && (eof1 != eof2))
4217    *identical_p = FALSE;
4218
4219  return svn_error_trace(
4220           svn_error_compose_create(
4221                err,
4222                svn_error_compose_create(svn_io_file_close(file1_h, pool),
4223                                         svn_io_file_close(file2_h, pool))));
4224}
4225
4226
4227
4228/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */
4229static svn_error_t *
4230contents_three_identical_p(svn_boolean_t *identical_p12,
4231                           svn_boolean_t *identical_p23,
4232                           svn_boolean_t *identical_p13,
4233                           const char *file1,
4234                           const char *file2,
4235                           const char *file3,
4236                           apr_pool_t *scratch_pool)
4237{
4238  svn_error_t *err;
4239  apr_size_t bytes_read1, bytes_read2, bytes_read3;
4240  char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4241  char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4242  char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4243  apr_file_t *file1_h;
4244  apr_file_t *file2_h;
4245  apr_file_t *file3_h;
4246  svn_boolean_t eof1 = FALSE;
4247  svn_boolean_t eof2 = FALSE;
4248  svn_boolean_t eof3 = FALSE;
4249  svn_boolean_t read_1, read_2, read_3;
4250
4251  SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
4252                           scratch_pool));
4253
4254  err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
4255                         scratch_pool);
4256
4257  if (err)
4258    return svn_error_trace(
4259               svn_error_compose_create(err,
4260                                        svn_io_file_close(file1_h, scratch_pool)));
4261
4262  err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT,
4263                         scratch_pool);
4264
4265  if (err)
4266      return svn_error_trace(
4267               svn_error_compose_create(
4268                    err,
4269                    svn_error_compose_create(svn_io_file_close(file1_h,
4270                                                          scratch_pool),
4271                                             svn_io_file_close(file2_h,
4272                                                          scratch_pool))));
4273
4274  /* assume TRUE, until disproved below */
4275  *identical_p12 = *identical_p23 = *identical_p13 = TRUE;
4276  /* We need to read as long as no error occurs, and as long as one of the
4277   * flags could still change due to a read operation */
4278  while (!err
4279        && ((*identical_p12 && !eof1 && !eof2)
4280            || (*identical_p23 && !eof2 && !eof3)
4281            || (*identical_p13 && !eof1 && !eof3)))
4282    {
4283      read_1 = read_2 = read_3 = FALSE;
4284
4285      /* As long as a file is not at the end yet, and it is still
4286       * potentially identical to another file, we read the next chunk.*/
4287      if (!eof1 && (*identical_p12 || *identical_p13))
4288        {
4289          err = svn_io_file_read_full2(file1_h, buf1,
4290                                   SVN__STREAM_CHUNK_SIZE, &bytes_read1,
4291                                   &eof1, scratch_pool);
4292          if (err)
4293              break;
4294          read_1 = TRUE;
4295        }
4296
4297      if (!eof2 && (*identical_p12 || *identical_p23))
4298        {
4299          err = svn_io_file_read_full2(file2_h, buf2,
4300                                   SVN__STREAM_CHUNK_SIZE, &bytes_read2,
4301                                   &eof2, scratch_pool);
4302          if (err)
4303              break;
4304          read_2 = TRUE;
4305        }
4306
4307      if (!eof3 && (*identical_p13 || *identical_p23))
4308        {
4309          err = svn_io_file_read_full2(file3_h, buf3,
4310                                   SVN__STREAM_CHUNK_SIZE, &bytes_read3,
4311                                   &eof3, scratch_pool);
4312          if (err)
4313              break;
4314          read_3 = TRUE;
4315        }
4316
4317      /* If the files are still marked identical, and at least one of them
4318       * is not at the end of file, we check whether they differ, and set
4319       * their flag to false then. */
4320      if (*identical_p12
4321          && (read_1 || read_2)
4322          && ((eof1 != eof2)
4323              || (bytes_read1 != bytes_read2)
4324              || memcmp(buf1, buf2, bytes_read1)))
4325        {
4326          *identical_p12 = FALSE;
4327        }
4328
4329      if (*identical_p23
4330          && (read_2 || read_3)
4331          && ((eof2 != eof3)
4332              || (bytes_read2 != bytes_read3)
4333              || memcmp(buf2, buf3, bytes_read2)))
4334        {
4335          *identical_p23 = FALSE;
4336        }
4337
4338      if (*identical_p13
4339          && (read_1 || read_3)
4340          && ((eof1 != eof3)
4341              || (bytes_read1 != bytes_read3)
4342              || memcmp(buf1, buf3, bytes_read3)))
4343        {
4344          *identical_p13 = FALSE;
4345        }
4346    }
4347
4348  return svn_error_trace(
4349           svn_error_compose_create(
4350                err,
4351                svn_error_compose_create(
4352                    svn_io_file_close(file1_h, scratch_pool),
4353                    svn_error_compose_create(
4354                        svn_io_file_close(file2_h, scratch_pool),
4355                        svn_io_file_close(file3_h, scratch_pool)))));
4356}
4357
4358
4359
4360svn_error_t *
4361svn_io_files_contents_same_p(svn_boolean_t *same,
4362                             const char *file1,
4363                             const char *file2,
4364                             apr_pool_t *pool)
4365{
4366  svn_boolean_t q;
4367
4368  SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool));
4369
4370  if (q)
4371    {
4372      *same = FALSE;
4373      return SVN_NO_ERROR;
4374    }
4375
4376  SVN_ERR(contents_identical_p(&q, file1, file2, pool));
4377
4378  if (q)
4379    *same = TRUE;
4380  else
4381    *same = FALSE;
4382
4383  return SVN_NO_ERROR;
4384}
4385
4386svn_error_t *
4387svn_io_files_contents_three_same_p(svn_boolean_t *same12,
4388                                   svn_boolean_t *same23,
4389                                   svn_boolean_t *same13,
4390                                   const char *file1,
4391                                   const char *file2,
4392                                   const char *file3,
4393                                   apr_pool_t *scratch_pool)
4394{
4395  svn_boolean_t diff_size12, diff_size23, diff_size13;
4396
4397  SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12,
4398                                             &diff_size23,
4399                                             &diff_size13,
4400                                             file1,
4401                                             file2,
4402                                             file3,
4403                                             scratch_pool));
4404
4405  if (diff_size12 && diff_size23 && diff_size13)
4406    {
4407      *same12 = *same23 = *same13 = FALSE;
4408    }
4409  else if (diff_size12 && diff_size23)
4410    {
4411      *same12 = *same23 = FALSE;
4412      SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool));
4413    }
4414  else if (diff_size23 && diff_size13)
4415    {
4416      *same23 = *same13 = FALSE;
4417      SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool));
4418    }
4419  else if (diff_size12 && diff_size13)
4420    {
4421      *same12 = *same13 = FALSE;
4422      SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool));
4423    }
4424  else
4425    {
4426      SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13);
4427      SVN_ERR(contents_three_identical_p(same12, same23, same13,
4428                                         file1, file2, file3,
4429                                         scratch_pool));
4430    }
4431
4432  return SVN_NO_ERROR;
4433}
4434
4435#ifdef WIN32
4436/* Counter value of file_mktemp request (used in a threadsafe way), to make
4437   sure that a single process normally never generates the same tempname
4438   twice */
4439static volatile apr_uint32_t tempname_counter = 0;
4440#endif
4441
4442/* Creates a new temporary file in DIRECTORY with apr flags FLAGS.
4443   Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name.
4444   Perform temporary allocations in SCRATCH_POOL and the result in
4445   RESULT_POOL. */
4446static svn_error_t *
4447temp_file_create(apr_file_t **new_file,
4448                 const char **new_file_name,
4449                 const char *directory,
4450                 apr_int32_t flags,
4451                 apr_pool_t *result_pool,
4452                 apr_pool_t *scratch_pool)
4453{
4454#ifndef WIN32
4455  const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool);
4456  const char *templ_apr;
4457  apr_status_t status;
4458
4459  SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool));
4460
4461  /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the
4462         data available in POOL and we need a non-const pointer here,
4463         as apr changes the template to return the new filename. */
4464  status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool);
4465
4466  if (status)
4467    return svn_error_wrap_apr(status, _("Can't create temporary file from "
4468                              "template '%s'"), templ);
4469
4470  /* Translate the returned path back to utf-8 before returning it */
4471  return svn_error_trace(svn_path_cstring_to_utf8(new_file_name,
4472                                                  templ_apr,
4473                                                  result_pool));
4474#else
4475  /* The Windows implementation of apr_file_mktemp doesn't handle access
4476     denied errors correctly. Therefore we implement our own temp file
4477     creation function here. */
4478
4479  /* ### Most of this is borrowed from the svn_io_open_uniquely_named(),
4480     ### the function we used before. But we try to guess a more unique
4481     ### name before trying if it exists. */
4482
4483  /* Offset by some time value and a unique request nr to make the number
4484     +- unique for both this process and on the computer */
4485  int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter)
4486               + GetCurrentProcessId();
4487  int i;
4488
4489  /* ### Maybe use an iterpool? */
4490  for (i = 0; i <= 99999; i++)
4491    {
4492      apr_uint32_t unique_nr;
4493      const char *unique_name;
4494      const char *unique_name_apr;
4495      apr_file_t *try_file;
4496      apr_status_t apr_err;
4497
4498      /* Generate a number that should be unique for this application and
4499         usually for the entire computer to reduce the number of cycles
4500         through this loop. (A bit of calculation is much cheaper then
4501         disk io) */
4502      unique_nr = baseNr + 3 * i;
4503
4504      unique_name = svn_dirent_join(directory,
4505                                    apr_psprintf(scratch_pool, "svn-%X",
4506                                                 unique_nr),
4507                                    scratch_pool);
4508
4509      SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool));
4510
4511      apr_err = file_open(&try_file, unique_name_apr, flags,
4512                          APR_OS_DEFAULT, FALSE, scratch_pool);
4513
4514      if (APR_STATUS_IS_EEXIST(apr_err))
4515          continue;
4516      else if (apr_err)
4517        {
4518          /* On Win32, CreateFile fails with an "Access Denied" error
4519             code, rather than "File Already Exists", if the colliding
4520             name belongs to a directory. */
4521
4522          if (APR_STATUS_IS_EACCES(apr_err))
4523            {
4524              apr_finfo_t finfo;
4525              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
4526                                                APR_FINFO_TYPE, scratch_pool);
4527
4528              if (!apr_err_2 && finfo.filetype == APR_DIR)
4529                continue;
4530
4531              apr_err_2 = APR_TO_OS_ERROR(apr_err);
4532
4533              if (apr_err_2 == ERROR_ACCESS_DENIED ||
4534                  apr_err_2 == ERROR_SHARING_VIOLATION)
4535                {
4536                  /* The file is in use by another process or is hidden;
4537                     create a new name, but don't do this 99999 times in
4538                     case the folder is not writable */
4539                  i += 797;
4540                  continue;
4541                }
4542
4543              /* Else fall through and return the original error. */
4544            }
4545
4546          return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
4547                                    svn_dirent_local_style(unique_name,
4548                                                           scratch_pool));
4549        }
4550      else
4551        {
4552          /* Move file to the right pool */
4553          apr_err = apr_file_setaside(new_file, try_file, result_pool);
4554
4555          if (apr_err)
4556            return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"),
4557                                      svn_dirent_local_style(unique_name,
4558                                                             scratch_pool));
4559
4560          *new_file_name = apr_pstrdup(result_pool, unique_name);
4561
4562          return SVN_NO_ERROR;
4563        }
4564    }
4565
4566  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
4567                           NULL,
4568                           _("Unable to make name in '%s'"),
4569                           svn_dirent_local_style(directory, scratch_pool));
4570#endif
4571}
4572
4573/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */
4574svn_error_t *
4575svn_io_file_name_get(const char **filename,
4576                     apr_file_t *file,
4577                     apr_pool_t *pool)
4578{
4579  const char *fname_apr;
4580  apr_status_t status;
4581
4582  status = apr_file_name_get(&fname_apr, file);
4583  if (status)
4584    return svn_error_wrap_apr(status, _("Can't get file name"));
4585
4586  if (fname_apr)
4587    SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool));
4588  else
4589    *filename = NULL;
4590
4591  return SVN_NO_ERROR;
4592}
4593
4594
4595svn_error_t *
4596svn_io_open_unique_file3(apr_file_t **file,
4597                         const char **unique_path,
4598                         const char *dirpath,
4599                         svn_io_file_del_t delete_when,
4600                         apr_pool_t *result_pool,
4601                         apr_pool_t *scratch_pool)
4602{
4603  apr_file_t *tempfile;
4604  const char *tempname;
4605  struct temp_file_cleanup_s *baton = NULL;
4606  apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL |
4607                       APR_BUFFERED | APR_BINARY);
4608#if !defined(WIN32) && !defined(__OS2__)
4609  apr_fileperms_t perms;
4610  svn_boolean_t using_system_temp_dir = FALSE;
4611#endif
4612
4613  SVN_ERR_ASSERT(file || unique_path);
4614  if (file)
4615    *file = NULL;
4616  if (unique_path)
4617    *unique_path = NULL;
4618
4619  if (dirpath == NULL)
4620    {
4621#if !defined(WIN32) && !defined(__OS2__)
4622      using_system_temp_dir = TRUE;
4623#endif
4624      SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
4625    }
4626
4627  switch (delete_when)
4628    {
4629      case svn_io_file_del_on_pool_cleanup:
4630        baton = apr_palloc(result_pool, sizeof(*baton));
4631        baton->pool = result_pool;
4632        baton->fname_apr = NULL;
4633
4634        /* Because cleanups are run LIFO, we need to make sure to register
4635           our cleanup before the apr_file_close cleanup:
4636
4637           On Windows, you can't remove an open file.
4638        */
4639        apr_pool_cleanup_register(result_pool, baton,
4640                                  temp_file_plain_cleanup_handler,
4641                                  temp_file_child_cleanup_handler);
4642
4643        break;
4644      case svn_io_file_del_on_close:
4645        flags |= APR_DELONCLOSE;
4646        break;
4647      default:
4648        break;
4649    }
4650
4651  SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags,
4652                           result_pool, scratch_pool));
4653
4654#if !defined(WIN32) && !defined(__OS2__)
4655  /* apr_file_mktemp() creates files with mode 0600.
4656   * This is appropriate if we're using a system temp dir since we don't
4657   * want to leak sensitive data into temp files other users can read.
4658   * If we're not using a system temp dir we're probably using the
4659   * .svn/tmp area and it's likely that the tempfile will end up being
4660   * copied or renamed into the working copy.
4661   * This would cause working files having mode 0600 while users might
4662   * expect to see 0644 or 0664. So we tweak perms of the tempfile in this
4663   * case, but only if the umask allows it. */
4664  if (!using_system_temp_dir)
4665    {
4666      SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool));
4667      SVN_ERR(file_perms_set2(tempfile, perms, scratch_pool));
4668    }
4669#endif
4670
4671  if (file)
4672    *file = tempfile;
4673  else
4674    SVN_ERR(svn_io_file_close(tempfile, scratch_pool));
4675
4676  if (unique_path)
4677    *unique_path = tempname; /* Was allocated in result_pool */
4678
4679  if (baton)
4680    SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool));
4681
4682  return SVN_NO_ERROR;
4683}
4684
4685svn_error_t *
4686svn_io_file_readline(apr_file_t *file,
4687                     svn_stringbuf_t **stringbuf,
4688                     const char **eol,
4689                     svn_boolean_t *eof,
4690                     apr_size_t max_len,
4691                     apr_pool_t *result_pool,
4692                     apr_pool_t *scratch_pool)
4693{
4694  svn_stringbuf_t *str;
4695  const char *eol_str;
4696  apr_size_t numbytes;
4697  char c;
4698  apr_size_t len;
4699  svn_boolean_t found_eof;
4700
4701  str = svn_stringbuf_create_ensure(80, result_pool);
4702
4703  /* Read bytes into STR up to and including, but not storing,
4704   * the next EOL sequence. */
4705  eol_str = NULL;
4706  numbytes = 1;
4707  len = 0;
4708  found_eof = FALSE;
4709  while (!found_eof)
4710    {
4711      if (len < max_len)
4712        SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
4713                                       &found_eof, scratch_pool));
4714      len++;
4715      if (numbytes != 1 || len > max_len)
4716        {
4717          found_eof = TRUE;
4718          break;
4719        }
4720
4721      if (c == '\n')
4722        {
4723          eol_str = "\n";
4724        }
4725      else if (c == '\r')
4726        {
4727          eol_str = "\r";
4728
4729          if (!found_eof && len < max_len)
4730            {
4731              apr_off_t pos;
4732
4733              /* Check for "\r\n" by peeking at the next byte. */
4734              pos = 0;
4735              SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
4736              SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
4737                                             &found_eof, scratch_pool));
4738              if (numbytes == 1 && c == '\n')
4739                {
4740                  eol_str = "\r\n";
4741                  len++;
4742                }
4743              else
4744                {
4745                  /* Pretend we never peeked. */
4746                  SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
4747                  found_eof = FALSE;
4748                  numbytes = 1;
4749                }
4750            }
4751        }
4752      else
4753        svn_stringbuf_appendbyte(str, c);
4754
4755      if (eol_str)
4756        break;
4757    }
4758
4759  if (eol)
4760    *eol = eol_str;
4761  if (eof)
4762    *eof = found_eof;
4763  *stringbuf = str;
4764
4765  return SVN_NO_ERROR;
4766}
4767