1251881Speter/*
2251881Speter * io.c:   shared file reading, writing, and probing code.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include <stdio.h>
27251881Speter
28251881Speter#ifndef WIN32
29251881Speter#include <unistd.h>
30251881Speter#endif
31251881Speter
32251881Speter#ifndef APR_STATUS_IS_EPERM
33251881Speter#include <errno.h>
34251881Speter#ifdef EPERM
35251881Speter#define APR_STATUS_IS_EPERM(s)   ((s) == EPERM)
36251881Speter#else
37251881Speter#define APR_STATUS_IS_EPERM(s)   (0)
38251881Speter#endif
39251881Speter#endif
40251881Speter
41251881Speter#include <apr_lib.h>
42251881Speter#include <apr_pools.h>
43251881Speter#include <apr_file_io.h>
44251881Speter#include <apr_file_info.h>
45251881Speter#include <apr_general.h>
46251881Speter#include <apr_strings.h>
47251881Speter#include <apr_portable.h>
48251881Speter#include <apr_md5.h>
49251881Speter
50251881Speter#ifdef WIN32
51251881Speter#include <arch/win32/apr_arch_file_io.h>
52251881Speter#endif
53251881Speter
54251881Speter#include "svn_hash.h"
55251881Speter#include "svn_types.h"
56251881Speter#include "svn_dirent_uri.h"
57251881Speter#include "svn_path.h"
58251881Speter#include "svn_string.h"
59251881Speter#include "svn_error.h"
60251881Speter#include "svn_io.h"
61251881Speter#include "svn_pools.h"
62251881Speter#include "svn_utf.h"
63251881Speter#include "svn_config.h"
64251881Speter#include "svn_private_config.h"
65251881Speter#include "svn_ctype.h"
66251881Speter
67251881Speter#include "private/svn_atomic.h"
68251881Speter#include "private/svn_io_private.h"
69251881Speter
70251881Speter#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
71251881Speter
72251881Speter/*
73251881Speter  Windows is 'aided' by a number of types of applications that
74251881Speter  follow other applications around and open up files they have
75251881Speter  changed for various reasons (the most intrusive are virus
76251881Speter  scanners).  So, if one of these other apps has glommed onto
77251881Speter  our file we may get an 'access denied' error.
78251881Speter
79251881Speter  This retry loop does not completely solve the problem (who
80251881Speter  knows how long the other app is going to hold onto it for), but
81251881Speter  goes a long way towards minimizing it.  It is not an infinite
82251881Speter  loop because there might really be an error.
83251881Speter
84251881Speter  Another reason for retrying delete operations on Windows
85251881Speter  is that they are asynchronous -- the file or directory is not
86251881Speter  actually deleted until the last handle to it is closed.  The
87251881Speter  retry loop cannot completely solve this problem either, but can
88251881Speter  help mitigate it.
89251881Speter*/
90251881Speter#define RETRY_MAX_ATTEMPTS 100
91251881Speter#define RETRY_INITIAL_SLEEP 1000
92251881Speter#define RETRY_MAX_SLEEP 128000
93251881Speter
94251881Speter#define RETRY_LOOP(err, expr, retry_test, sleep_test)                      \
95251881Speter  do                                                                       \
96251881Speter    {                                                                      \
97251881Speter      apr_status_t os_err = APR_TO_OS_ERROR(err);                          \
98251881Speter      int sleep_count = RETRY_INITIAL_SLEEP;                               \
99251881Speter      int retries;                                                         \
100251881Speter      for (retries = 0;                                                    \
101251881Speter           retries < RETRY_MAX_ATTEMPTS && (retry_test);                   \
102251881Speter           os_err = APR_TO_OS_ERROR(err))                                  \
103251881Speter        {                                                                  \
104251881Speter          if (sleep_test)                                                  \
105251881Speter            {                                                              \
106251881Speter              ++retries;                                                   \
107251881Speter              apr_sleep(sleep_count);                                      \
108251881Speter              if (sleep_count < RETRY_MAX_SLEEP)                           \
109251881Speter                sleep_count *= 2;                                          \
110251881Speter            }                                                              \
111251881Speter          (err) = (expr);                                                  \
112251881Speter        }                                                                  \
113251881Speter    }                                                                      \
114251881Speter  while (0)
115251881Speter
116251881Speter#if defined(EDEADLK) && APR_HAS_THREADS
117251881Speter#define FILE_LOCK_RETRY_LOOP(err, expr)                                    \
118251881Speter  RETRY_LOOP(err,                                                          \
119251881Speter             expr,                                                         \
120251881Speter             (APR_STATUS_IS_EINTR(err) || os_err == EDEADLK),              \
121251881Speter             (!APR_STATUS_IS_EINTR(err)))
122251881Speter#else
123251881Speter#define FILE_LOCK_RETRY_LOOP(err, expr)                                    \
124251881Speter  RETRY_LOOP(err,                                                          \
125251881Speter             expr,                                                         \
126251881Speter             (APR_STATUS_IS_EINTR(err)),                                   \
127251881Speter             0)
128251881Speter#endif
129251881Speter
130251881Speter#ifndef WIN32_RETRY_LOOP
131251881Speter#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP)
132251881Speter#define WIN32_RETRY_LOOP(err, expr)                                        \
133251881Speter  RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED                     \
134251881Speter                         || os_err == ERROR_SHARING_VIOLATION              \
135251881Speter                         || os_err == ERROR_DIR_NOT_EMPTY),                \
136251881Speter             1)
137251881Speter#else
138251881Speter#define WIN32_RETRY_LOOP(err, expr) ((void)0)
139251881Speter#endif
140251881Speter#endif
141251881Speter
142251881Speter/* Forward declaration */
143251881Speterstatic apr_status_t
144251881Speterdir_is_empty(const char *dir, apr_pool_t *pool);
145251881Speterstatic APR_INLINE svn_error_t *
146251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
147251881Speter                           const char *msg, const char *msg_no_name,
148251881Speter                           apr_pool_t *pool);
149251881Speter
150251881Speter/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on
151251881Speter * operating systems where APR always uses utf-8 as native path format */
152251881Speterstatic svn_error_t *
153251881Spetercstring_to_utf8(const char **path_utf8,
154251881Speter                const char *path_apr,
155251881Speter                apr_pool_t *pool)
156251881Speter{
157251881Speter#if defined(WIN32) || defined(DARWIN)
158251881Speter  *path_utf8 = path_apr;
159251881Speter  return SVN_NO_ERROR;
160251881Speter#else
161251881Speter  return svn_path_cstring_to_utf8(path_utf8, path_apr, pool);
162251881Speter#endif
163251881Speter}
164251881Speter
165251881Speter/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on
166251881Speter * operating systems where APR always uses utf-8 as native path format */
167251881Speterstatic svn_error_t *
168251881Spetercstring_from_utf8(const char **path_apr,
169251881Speter                  const char *path_utf8,
170251881Speter                  apr_pool_t *pool)
171251881Speter{
172251881Speter#if defined(WIN32) || defined(DARWIN)
173251881Speter  *path_apr = path_utf8;
174251881Speter  return SVN_NO_ERROR;
175251881Speter#else
176251881Speter  return svn_path_cstring_from_utf8(path_apr, path_utf8, pool);
177251881Speter#endif
178251881Speter}
179251881Speter
180251881Speter/* Helper function that allows to convert an APR-level PATH to something
181251881Speter * that we can pass the svn_error_wrap_apr. Since we use it in context
182251881Speter * of error reporting, having *some* path info may be more useful than
183251881Speter * having none.  Therefore, we use a best effort approach here.
184251881Speter *
185251881Speter * This is different from svn_io_file_name_get in that it uses a different
186251881Speter * signature style and will never fail.
187251881Speter */
188251881Speterstatic const char *
189251881Spetertry_utf8_from_internal_style(const char *path, apr_pool_t *pool)
190251881Speter{
191251881Speter  svn_error_t *error;
192251881Speter  const char *path_utf8;
193251881Speter
194251881Speter  /* Special case. */
195251881Speter  if (path == NULL)
196251881Speter    return "(NULL)";
197251881Speter
198251881Speter  /* (try to) convert PATH to UTF-8. If that fails, continue with the plain
199251881Speter   * PATH because it is the best we have. It may actually be UTF-8 already.
200251881Speter   */
201251881Speter  error = cstring_to_utf8(&path_utf8, path, pool);
202251881Speter  if (error)
203251881Speter    {
204251881Speter      /* fallback to best representation we have */
205251881Speter
206251881Speter      svn_error_clear(error);
207251881Speter      path_utf8 = path;
208251881Speter    }
209251881Speter
210251881Speter  /* Toggle (back-)slashes etc. as necessary.
211251881Speter   */
212251881Speter  return svn_dirent_local_style(path_utf8, pool);
213251881Speter}
214251881Speter
215251881Speter
216251881Speter/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
217251881Speter * NAME is in the internal encoding used by APR; PARENT is in
218251881Speter * UTF-8 and in internal (not local) style.
219251881Speter *
220251881Speter * Use PARENT only for generating an error string if the conversion
221251881Speter * fails because NAME could not be represented in UTF-8.  In that
222251881Speter * case, return a two-level error in which the outer error's message
223251881Speter * mentions PARENT, but the inner error's message does not mention
224251881Speter * NAME (except possibly in hex) since NAME may not be printable.
225251881Speter * Such a compound error at least allows the user to go looking in the
226251881Speter * right directory for the problem.
227251881Speter *
228251881Speter * If there is any other error, just return that error directly.
229251881Speter *
230251881Speter * If there is any error, the effect on *NAME_P is undefined.
231251881Speter *
232251881Speter * *NAME_P and NAME may refer to the same storage.
233251881Speter */
234251881Speterstatic svn_error_t *
235251881Speterentry_name_to_utf8(const char **name_p,
236251881Speter                   const char *name,
237251881Speter                   const char *parent,
238251881Speter                   apr_pool_t *pool)
239251881Speter{
240251881Speter#if defined(WIN32) || defined(DARWIN)
241251881Speter  *name_p = apr_pstrdup(pool, name);
242251881Speter  return SVN_NO_ERROR;
243251881Speter#else
244251881Speter  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
245251881Speter  if (err && err->apr_err == APR_EINVAL)
246251881Speter    {
247251881Speter      return svn_error_createf(err->apr_err, err,
248251881Speter                               _("Error converting entry "
249251881Speter                                 "in directory '%s' to UTF-8"),
250251881Speter                               svn_dirent_local_style(parent, pool));
251251881Speter    }
252251881Speter  return err;
253251881Speter#endif
254251881Speter}
255251881Speter
256251881Speter
257251881Speter
258251881Speterstatic void
259251881Spetermap_apr_finfo_to_node_kind(svn_node_kind_t *kind,
260251881Speter                           svn_boolean_t *is_special,
261251881Speter                           apr_finfo_t *finfo)
262251881Speter{
263251881Speter  *is_special = FALSE;
264251881Speter
265251881Speter  if (finfo->filetype == APR_REG)
266251881Speter    *kind = svn_node_file;
267251881Speter  else if (finfo->filetype == APR_DIR)
268251881Speter    *kind = svn_node_dir;
269251881Speter  else if (finfo->filetype == APR_LNK)
270251881Speter    {
271251881Speter      *is_special = TRUE;
272251881Speter      *kind = svn_node_file;
273251881Speter    }
274251881Speter  else
275251881Speter    *kind = svn_node_unknown;
276251881Speter}
277251881Speter
278251881Speter/* Helper for svn_io_check_path() and svn_io_check_resolved_path();
279251881Speter   essentially the same semantics as those two, with the obvious
280251881Speter   interpretation for RESOLVE_SYMLINKS. */
281251881Speterstatic svn_error_t *
282251881Speterio_check_path(const char *path,
283251881Speter              svn_boolean_t resolve_symlinks,
284251881Speter              svn_boolean_t *is_special_p,
285251881Speter              svn_node_kind_t *kind,
286251881Speter              apr_pool_t *pool)
287251881Speter{
288251881Speter  apr_int32_t flags;
289251881Speter  apr_finfo_t finfo;
290251881Speter  apr_status_t apr_err;
291251881Speter  const char *path_apr;
292251881Speter  svn_boolean_t is_special = FALSE;
293251881Speter
294251881Speter  if (path[0] == '\0')
295251881Speter    path = ".";
296251881Speter
297251881Speter  /* Not using svn_io_stat() here because we want to check the
298251881Speter     apr_err return explicitly. */
299251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
300251881Speter
301251881Speter  flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
302251881Speter  apr_err = apr_stat(&finfo, path_apr, flags, pool);
303251881Speter
304251881Speter  if (APR_STATUS_IS_ENOENT(apr_err))
305251881Speter    *kind = svn_node_none;
306251881Speter  else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err))
307251881Speter    *kind = svn_node_none;
308251881Speter  else if (apr_err)
309251881Speter    return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"),
310251881Speter                              svn_dirent_local_style(path, pool));
311251881Speter  else
312251881Speter    map_apr_finfo_to_node_kind(kind, &is_special, &finfo);
313251881Speter
314251881Speter  *is_special_p = is_special;
315251881Speter
316251881Speter  return SVN_NO_ERROR;
317251881Speter}
318251881Speter
319251881Speter
320251881Speter/* Wrapper for apr_file_open(), taking an APR-encoded filename. */
321251881Speterstatic apr_status_t
322251881Speterfile_open(apr_file_t **f,
323251881Speter          const char *fname_apr,
324251881Speter          apr_int32_t flag,
325251881Speter          apr_fileperms_t perm,
326251881Speter          svn_boolean_t retry_on_failure,
327251881Speter          apr_pool_t *pool)
328251881Speter{
329251881Speter  apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool);
330251881Speter
331251881Speter  if (retry_on_failure)
332251881Speter    {
333251881Speter      WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool));
334251881Speter    }
335251881Speter  return status;
336251881Speter}
337251881Speter
338251881Speter
339251881Spetersvn_error_t *
340251881Spetersvn_io_check_resolved_path(const char *path,
341251881Speter                           svn_node_kind_t *kind,
342251881Speter                           apr_pool_t *pool)
343251881Speter{
344251881Speter  svn_boolean_t ignored;
345251881Speter  return io_check_path(path, TRUE, &ignored, kind, pool);
346251881Speter}
347251881Speter
348251881Spetersvn_error_t *
349251881Spetersvn_io_check_path(const char *path,
350251881Speter                  svn_node_kind_t *kind,
351251881Speter                  apr_pool_t *pool)
352251881Speter{
353251881Speter  svn_boolean_t ignored;
354251881Speter  return io_check_path(path, FALSE, &ignored, kind, pool);
355251881Speter}
356251881Speter
357251881Spetersvn_error_t *
358251881Spetersvn_io_check_special_path(const char *path,
359251881Speter                          svn_node_kind_t *kind,
360251881Speter                          svn_boolean_t *is_special,
361251881Speter                          apr_pool_t *pool)
362251881Speter{
363251881Speter  return io_check_path(path, FALSE, is_special, kind, pool);
364251881Speter}
365251881Speter
366251881Speterstruct temp_file_cleanup_s
367251881Speter{
368251881Speter  apr_pool_t *pool;
369251881Speter  /* The (APR-encoded) full path of the file to be removed, or NULL if
370251881Speter   * nothing to do. */
371251881Speter  const char *fname_apr;
372251881Speter};
373251881Speter
374251881Speter
375251881Speterstatic apr_status_t
376251881Spetertemp_file_plain_cleanup_handler(void *baton)
377251881Speter{
378251881Speter  struct  temp_file_cleanup_s *b = baton;
379251881Speter  apr_status_t apr_err = APR_SUCCESS;
380251881Speter
381251881Speter  if (b->fname_apr)
382251881Speter    {
383251881Speter      apr_err = apr_file_remove(b->fname_apr, b->pool);
384251881Speter      WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool));
385251881Speter    }
386251881Speter
387251881Speter  return apr_err;
388251881Speter}
389251881Speter
390251881Speter
391251881Speterstatic apr_status_t
392251881Spetertemp_file_child_cleanup_handler(void *baton)
393251881Speter{
394251881Speter  struct  temp_file_cleanup_s *b = baton;
395251881Speter
396251881Speter  apr_pool_cleanup_kill(b->pool, b,
397251881Speter                        temp_file_plain_cleanup_handler);
398251881Speter
399251881Speter  return APR_SUCCESS;
400251881Speter}
401251881Speter
402251881Speter
403251881Spetersvn_error_t *
404251881Spetersvn_io_open_uniquely_named(apr_file_t **file,
405251881Speter                           const char **unique_path,
406251881Speter                           const char *dirpath,
407251881Speter                           const char *filename,
408251881Speter                           const char *suffix,
409251881Speter                           svn_io_file_del_t delete_when,
410251881Speter                           apr_pool_t *result_pool,
411251881Speter                           apr_pool_t *scratch_pool)
412251881Speter{
413251881Speter  const char *path;
414251881Speter  unsigned int i;
415251881Speter  struct temp_file_cleanup_s *baton = NULL;
416251881Speter
417251881Speter  /* At the beginning, we don't know whether unique_path will need
418251881Speter     UTF8 conversion */
419251881Speter  svn_boolean_t needs_utf8_conversion = TRUE;
420251881Speter
421251881Speter  SVN_ERR_ASSERT(file || unique_path);
422251881Speter
423251881Speter  if (dirpath == NULL)
424251881Speter    SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
425251881Speter  if (filename == NULL)
426251881Speter    filename = "tempfile";
427251881Speter  if (suffix == NULL)
428251881Speter    suffix = ".tmp";
429251881Speter
430251881Speter  path = svn_dirent_join(dirpath, filename, scratch_pool);
431251881Speter
432251881Speter  if (delete_when == svn_io_file_del_on_pool_cleanup)
433251881Speter    {
434251881Speter      baton = apr_palloc(result_pool, sizeof(*baton));
435251881Speter
436251881Speter      baton->pool = result_pool;
437251881Speter      baton->fname_apr = NULL;
438251881Speter
439251881Speter      /* Because cleanups are run LIFO, we need to make sure to register
440251881Speter         our cleanup before the apr_file_close cleanup:
441251881Speter
442251881Speter         On Windows, you can't remove an open file.
443251881Speter      */
444251881Speter      apr_pool_cleanup_register(result_pool, baton,
445251881Speter                                temp_file_plain_cleanup_handler,
446251881Speter                                temp_file_child_cleanup_handler);
447251881Speter    }
448251881Speter
449251881Speter  for (i = 1; i <= 99999; i++)
450251881Speter    {
451251881Speter      const char *unique_name;
452251881Speter      const char *unique_name_apr;
453251881Speter      apr_file_t *try_file;
454251881Speter      apr_status_t apr_err;
455251881Speter      apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL
456251881Speter                          | APR_BUFFERED | APR_BINARY);
457251881Speter
458251881Speter      if (delete_when == svn_io_file_del_on_close)
459251881Speter        flag |= APR_DELONCLOSE;
460251881Speter
461251881Speter      /* Special case the first attempt -- if we can avoid having a
462251881Speter         generated numeric portion at all, that's best.  So first we
463251881Speter         try with just the suffix; then future tries add a number
464251881Speter         before the suffix.  (A do-while loop could avoid the repeated
465251881Speter         conditional, but it's not worth the clarity loss.)
466251881Speter
467251881Speter         If the first attempt fails, the first number will be "2".
468251881Speter         This is good, since "1" would misleadingly imply that
469251881Speter         the second attempt was actually the first... and if someone's
470251881Speter         got conflicts on their conflicts, we probably don't want to
471251881Speter         add to their confusion :-). */
472251881Speter      if (i == 1)
473251881Speter        unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix);
474251881Speter      else
475251881Speter        unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix);
476251881Speter
477251881Speter      /* Hmmm.  Ideally, we would append to a native-encoding buf
478251881Speter         before starting iteration, then convert back to UTF-8 for
479251881Speter         return. But I suppose that would make the appending code
480251881Speter         sensitive to i18n in a way it shouldn't be... Oh well. */
481251881Speter      if (needs_utf8_conversion)
482251881Speter        {
483251881Speter          SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name,
484251881Speter                                    scratch_pool));
485251881Speter          if (i == 1)
486251881Speter            {
487251881Speter              /* The variable parts of unique_name will not require UTF8
488251881Speter                 conversion. Therefore, if UTF8 conversion had no effect
489251881Speter                 on it in the first iteration, it won't require conversion
490251881Speter                 in any future iteration. */
491251881Speter              needs_utf8_conversion = strcmp(unique_name_apr, unique_name);
492251881Speter            }
493251881Speter        }
494251881Speter      else
495251881Speter        unique_name_apr = unique_name;
496251881Speter
497251881Speter      apr_err = file_open(&try_file, unique_name_apr, flag,
498251881Speter                          APR_OS_DEFAULT, FALSE, result_pool);
499251881Speter
500251881Speter      if (APR_STATUS_IS_EEXIST(apr_err))
501251881Speter        continue;
502251881Speter      else if (apr_err)
503251881Speter        {
504251881Speter          /* On Win32, CreateFile fails with an "Access Denied" error
505251881Speter             code, rather than "File Already Exists", if the colliding
506251881Speter             name belongs to a directory. */
507251881Speter          if (APR_STATUS_IS_EACCES(apr_err))
508251881Speter            {
509251881Speter              apr_finfo_t finfo;
510251881Speter              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
511251881Speter                                                APR_FINFO_TYPE, scratch_pool);
512251881Speter
513251881Speter              if (!apr_err_2 && finfo.filetype == APR_DIR)
514251881Speter                continue;
515251881Speter
516251881Speter#ifdef WIN32
517251881Speter              apr_err_2 = APR_TO_OS_ERROR(apr_err);
518251881Speter
519251881Speter              if (apr_err_2 == ERROR_ACCESS_DENIED ||
520251881Speter                  apr_err_2 == ERROR_SHARING_VIOLATION)
521251881Speter                {
522251881Speter                  /* The file is in use by another process or is hidden;
523251881Speter                     create a new name, but don't do this 99999 times in
524251881Speter                     case the folder is not writable */
525251881Speter                  i += 797;
526251881Speter                  continue;
527251881Speter                }
528251881Speter#endif
529251881Speter
530251881Speter              /* Else fall through and return the original error. */
531251881Speter            }
532251881Speter
533251881Speter          if (file)
534251881Speter            *file = NULL;
535251881Speter          if (unique_path)
536251881Speter            *unique_path = NULL;
537251881Speter          return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
538251881Speter                                    svn_dirent_local_style(unique_name,
539251881Speter                                                         scratch_pool));
540251881Speter        }
541251881Speter      else
542251881Speter        {
543251881Speter          if (delete_when == svn_io_file_del_on_pool_cleanup)
544251881Speter            baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr);
545251881Speter
546251881Speter          if (file)
547251881Speter            *file = try_file;
548251881Speter          else
549251881Speter            apr_file_close(try_file);
550251881Speter          if (unique_path)
551251881Speter            *unique_path = apr_pstrdup(result_pool, unique_name);
552251881Speter
553251881Speter          return SVN_NO_ERROR;
554251881Speter        }
555251881Speter    }
556251881Speter
557251881Speter  if (file)
558251881Speter    *file = NULL;
559251881Speter  if (unique_path)
560251881Speter    *unique_path = NULL;
561251881Speter  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
562251881Speter                           NULL,
563251881Speter                           _("Unable to make name for '%s'"),
564251881Speter                           svn_dirent_local_style(path, scratch_pool));
565251881Speter}
566251881Speter
567251881Spetersvn_error_t *
568251881Spetersvn_io_create_unique_link(const char **unique_name_p,
569251881Speter                          const char *path,
570251881Speter                          const char *dest,
571251881Speter                          const char *suffix,
572251881Speter                          apr_pool_t *pool)
573251881Speter{
574251881Speter#ifdef HAVE_SYMLINK
575251881Speter  unsigned int i;
576251881Speter  const char *unique_name;
577251881Speter  const char *unique_name_apr;
578251881Speter  const char *dest_apr;
579251881Speter  int rv;
580251881Speter
581251881Speter  SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool));
582251881Speter  for (i = 1; i <= 99999; i++)
583251881Speter    {
584251881Speter      apr_status_t apr_err;
585251881Speter
586251881Speter      /* Special case the first attempt -- if we can avoid having a
587251881Speter         generated numeric portion at all, that's best.  So first we
588251881Speter         try with just the suffix; then future tries add a number
589251881Speter         before the suffix.  (A do-while loop could avoid the repeated
590251881Speter         conditional, but it's not worth the clarity loss.)
591251881Speter
592251881Speter         If the first attempt fails, the first number will be "2".
593251881Speter         This is good, since "1" would misleadingly imply that
594251881Speter         the second attempt was actually the first... and if someone's
595251881Speter         got conflicts on their conflicts, we probably don't want to
596251881Speter         add to their confusion :-). */
597251881Speter      if (i == 1)
598251881Speter        unique_name = apr_psprintf(pool, "%s%s", path, suffix);
599251881Speter      else
600251881Speter        unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
601251881Speter
602251881Speter      /* Hmmm.  Ideally, we would append to a native-encoding buf
603251881Speter         before starting iteration, then convert back to UTF-8 for
604251881Speter         return. But I suppose that would make the appending code
605251881Speter         sensitive to i18n in a way it shouldn't be... Oh well. */
606251881Speter      SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool));
607251881Speter      do {
608251881Speter        rv = symlink(dest_apr, unique_name_apr);
609251881Speter      } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
610251881Speter
611251881Speter      apr_err = apr_get_os_error();
612251881Speter
613251881Speter      if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err))
614251881Speter        continue;
615251881Speter      else if (rv == -1 && apr_err)
616251881Speter        {
617251881Speter          /* On Win32, CreateFile fails with an "Access Denied" error
618251881Speter             code, rather than "File Already Exists", if the colliding
619251881Speter             name belongs to a directory. */
620251881Speter          if (APR_STATUS_IS_EACCES(apr_err))
621251881Speter            {
622251881Speter              apr_finfo_t finfo;
623251881Speter              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
624251881Speter                                                APR_FINFO_TYPE, pool);
625251881Speter
626251881Speter              if (!apr_err_2
627251881Speter                  && (finfo.filetype == APR_DIR))
628251881Speter                continue;
629251881Speter
630251881Speter              /* Else ignore apr_err_2; better to fall through and
631251881Speter                 return the original error. */
632251881Speter            }
633251881Speter
634251881Speter          *unique_name_p = NULL;
635251881Speter          return svn_error_wrap_apr(apr_err,
636251881Speter                                    _("Can't create symbolic link '%s'"),
637251881Speter                                    svn_dirent_local_style(unique_name, pool));
638251881Speter        }
639251881Speter      else
640251881Speter        {
641251881Speter          *unique_name_p = unique_name;
642251881Speter          return SVN_NO_ERROR;
643251881Speter        }
644251881Speter    }
645251881Speter
646251881Speter  *unique_name_p = NULL;
647251881Speter  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
648251881Speter                           NULL,
649251881Speter                           _("Unable to make name for '%s'"),
650251881Speter                           svn_dirent_local_style(path, pool));
651251881Speter#else
652251881Speter  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
653251881Speter                          _("Symbolic links are not supported on this "
654251881Speter                            "platform"));
655251881Speter#endif
656251881Speter}
657251881Speter
658251881Spetersvn_error_t *
659251881Spetersvn_io_read_link(svn_string_t **dest,
660251881Speter                 const char *path,
661251881Speter                 apr_pool_t *pool)
662251881Speter{
663251881Speter#ifdef HAVE_READLINK
664251881Speter  svn_string_t dest_apr;
665251881Speter  const char *path_apr;
666251881Speter  char buf[1025];
667251881Speter  ssize_t rv;
668251881Speter
669251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
670251881Speter  do {
671251881Speter    rv = readlink(path_apr, buf, sizeof(buf) - 1);
672251881Speter  } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
673251881Speter
674251881Speter  if (rv == -1)
675251881Speter    return svn_error_wrap_apr(apr_get_os_error(),
676251881Speter                              _("Can't read contents of link"));
677251881Speter
678251881Speter  buf[rv] = '\0';
679251881Speter  dest_apr.data = buf;
680251881Speter  dest_apr.len = rv;
681251881Speter
682251881Speter  /* ### Cast needed, one of these interfaces is wrong */
683251881Speter  return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool);
684251881Speter#else
685251881Speter  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
686251881Speter                          _("Symbolic links are not supported on this "
687251881Speter                            "platform"));
688251881Speter#endif
689251881Speter}
690251881Speter
691251881Speter
692251881Spetersvn_error_t *
693251881Spetersvn_io_copy_link(const char *src,
694251881Speter                 const char *dst,
695251881Speter                 apr_pool_t *pool)
696251881Speter
697251881Speter{
698251881Speter#ifdef HAVE_READLINK
699251881Speter  svn_string_t *link_dest;
700251881Speter  const char *dst_tmp;
701251881Speter
702251881Speter  /* Notice what the link is pointing at... */
703251881Speter  SVN_ERR(svn_io_read_link(&link_dest, src, pool));
704251881Speter
705251881Speter  /* Make a tmp-link pointing at the same thing. */
706251881Speter  SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data,
707251881Speter                                    ".tmp", pool));
708251881Speter
709251881Speter  /* Move the tmp-link to link. */
710251881Speter  return svn_io_file_rename(dst_tmp, dst, pool);
711251881Speter
712251881Speter#else
713251881Speter  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
714251881Speter                          _("Symbolic links are not supported on this "
715251881Speter                            "platform"));
716251881Speter#endif
717251881Speter}
718251881Speter
719251881Speter/* Temporary directory name cache for svn_io_temp_dir() */
720251881Speterstatic volatile svn_atomic_t temp_dir_init_state = 0;
721251881Speterstatic const char *temp_dir;
722251881Speter
723251881Speter/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */
724251881Speterstatic svn_error_t *
725251881Speterinit_temp_dir(void *baton, apr_pool_t *scratch_pool)
726251881Speter{
727251881Speter  /* Global pool for the temp path */
728251881Speter  apr_pool_t *global_pool = svn_pool_create(NULL);
729251881Speter  const char *dir;
730251881Speter
731251881Speter  apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool);
732251881Speter
733251881Speter  if (apr_err)
734251881Speter    return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory"));
735251881Speter
736251881Speter  SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool));
737251881Speter
738251881Speter  dir = svn_dirent_internal_style(dir, scratch_pool);
739251881Speter
740251881Speter  SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool));
741251881Speter
742251881Speter  return SVN_NO_ERROR;
743251881Speter}
744251881Speter
745251881Speter
746251881Spetersvn_error_t *
747251881Spetersvn_io_temp_dir(const char **dir,
748251881Speter                apr_pool_t *pool)
749251881Speter{
750251881Speter  SVN_ERR(svn_atomic__init_once(&temp_dir_init_state,
751251881Speter                                init_temp_dir, NULL, pool));
752251881Speter
753251881Speter  *dir = apr_pstrdup(pool, temp_dir);
754251881Speter
755251881Speter  return SVN_NO_ERROR;
756251881Speter}
757251881Speter
758251881Speter
759251881Speter
760251881Speter
761251881Speter/*** Creating, copying and appending files. ***/
762251881Speter
763251881Speter/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary
764251881Speter * allocations.
765251881Speter *
766251881Speter * NOTE: We don't use apr_copy_file() for this, since it takes filenames
767251881Speter * as parameters.  Since we want to copy to a temporary file
768251881Speter * and rename for atomicity (see below), this would require an extra
769251881Speter * close/open pair, which can be expensive, especially on
770251881Speter * remote file systems.
771251881Speter */
772251881Speterstatic apr_status_t
773251881Spetercopy_contents(apr_file_t *from_file,
774251881Speter              apr_file_t *to_file,
775251881Speter              apr_pool_t *pool)
776251881Speter{
777251881Speter  /* Copy bytes till the cows come home. */
778251881Speter  while (1)
779251881Speter    {
780251881Speter      char buf[SVN__STREAM_CHUNK_SIZE];
781251881Speter      apr_size_t bytes_this_time = sizeof(buf);
782251881Speter      apr_status_t read_err;
783251881Speter      apr_status_t write_err;
784251881Speter
785251881Speter      /* Read 'em. */
786251881Speter      read_err = apr_file_read(from_file, buf, &bytes_this_time);
787251881Speter      if (read_err && !APR_STATUS_IS_EOF(read_err))
788251881Speter        {
789251881Speter          return read_err;
790251881Speter        }
791251881Speter
792251881Speter      /* Write 'em. */
793251881Speter      write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL);
794251881Speter      if (write_err)
795251881Speter        {
796251881Speter          return write_err;
797251881Speter        }
798251881Speter
799251881Speter      if (read_err && APR_STATUS_IS_EOF(read_err))
800251881Speter        {
801251881Speter          /* Return the results of this close: an error, or success. */
802251881Speter          return APR_SUCCESS;
803251881Speter        }
804251881Speter    }
805251881Speter  /* NOTREACHED */
806251881Speter}
807251881Speter
808251881Speter
809251881Spetersvn_error_t *
810251881Spetersvn_io_copy_file(const char *src,
811251881Speter                 const char *dst,
812251881Speter                 svn_boolean_t copy_perms,
813251881Speter                 apr_pool_t *pool)
814251881Speter{
815251881Speter  apr_file_t *from_file, *to_file;
816251881Speter  apr_status_t apr_err;
817251881Speter  const char *dst_tmp;
818251881Speter  svn_error_t *err;
819251881Speter
820251881Speter  /* ### NOTE: sometimes src == dst. In this case, because we copy to a
821251881Speter     ###   temporary file, and then rename over the top of the destination,
822251881Speter     ###   the net result is resetting the permissions on src/dst.
823251881Speter     ###
824251881Speter     ### Note: specifically, this can happen during a switch when the desired
825251881Speter     ###   permissions for a file change from one branch to another. See
826251881Speter     ###   switch_tests 17.
827251881Speter     ###
828251881Speter     ### ... yes, we should avoid copying to the same file, and we should
829251881Speter     ###     make the "reset perms" explicit. The switch *happens* to work
830251881Speter     ###     because of this copy-to-temp-then-rename implementation. If it
831251881Speter     ###     weren't for that, the switch would break.
832251881Speter  */
833251881Speter#ifdef CHECK_FOR_SAME_FILE
834251881Speter  if (strcmp(src, dst) == 0)
835251881Speter    return SVN_NO_ERROR;
836251881Speter#endif
837251881Speter
838251881Speter  SVN_ERR(svn_io_file_open(&from_file, src, APR_READ,
839251881Speter                           APR_OS_DEFAULT, pool));
840251881Speter
841251881Speter  /* For atomicity, we copy to a tmp file and then rename the tmp
842251881Speter     file over the real destination. */
843251881Speter
844251881Speter  SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp,
845251881Speter                                   svn_dirent_dirname(dst, pool),
846251881Speter                                   svn_io_file_del_none, pool, pool));
847251881Speter
848251881Speter  apr_err = copy_contents(from_file, to_file, pool);
849251881Speter
850251881Speter  if (apr_err)
851251881Speter    {
852251881Speter      err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"),
853251881Speter                               svn_dirent_local_style(src, pool),
854251881Speter                               svn_dirent_local_style(dst_tmp, pool));
855251881Speter    }
856251881Speter   else
857251881Speter     err = NULL;
858251881Speter
859251881Speter  err = svn_error_compose_create(err,
860251881Speter                                 svn_io_file_close(from_file, pool));
861251881Speter
862251881Speter  err = svn_error_compose_create(err,
863251881Speter                                 svn_io_file_close(to_file, pool));
864251881Speter
865251881Speter  if (err)
866251881Speter    {
867251881Speter      return svn_error_compose_create(
868251881Speter                                 err,
869251881Speter                                 svn_io_remove_file2(dst_tmp, TRUE, pool));
870251881Speter    }
871251881Speter
872251881Speter  /* If copying perms, set the perms on dst_tmp now, so they will be
873251881Speter     atomically inherited in the upcoming rename.  But note that we
874251881Speter     had to wait until now to set perms, because if they say
875251881Speter     read-only, then we'd have failed filling dst_tmp's contents. */
876251881Speter  if (copy_perms)
877251881Speter    SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool));
878251881Speter
879251881Speter  return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool));
880251881Speter}
881251881Speter
882251881Speter#if !defined(WIN32) && !defined(__OS2__)
883251881Speter/* Wrapper for apr_file_perms_set(), taking a UTF8-encoded filename. */
884251881Speterstatic svn_error_t *
885251881Speterfile_perms_set(const char *fname, apr_fileperms_t perms,
886251881Speter               apr_pool_t *pool)
887251881Speter{
888251881Speter  const char *fname_apr;
889251881Speter  apr_status_t status;
890251881Speter
891251881Speter  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
892251881Speter
893251881Speter  status = apr_file_perms_set(fname_apr, perms);
894251881Speter  if (status)
895251881Speter    return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
896251881Speter                              fname);
897251881Speter  else
898251881Speter    return SVN_NO_ERROR;
899251881Speter}
900251881Speter
901251881Speter/* Set permissions PERMS on the FILE. This is a cheaper variant of the
902251881Speter * file_perms_set wrapper() function because no locale-dependent string
903251881Speter * conversion is required. POOL will be used for allocations.
904251881Speter */
905251881Speterstatic svn_error_t *
906251881Speterfile_perms_set2(apr_file_t* file, apr_fileperms_t perms, apr_pool_t *pool)
907251881Speter{
908251881Speter  const char *fname_apr;
909251881Speter  apr_status_t status;
910251881Speter
911251881Speter  status = apr_file_name_get(&fname_apr, file);
912251881Speter  if (status)
913251881Speter    return svn_error_wrap_apr(status, _("Can't get file name"));
914251881Speter
915251881Speter  status = apr_file_perms_set(fname_apr, perms);
916251881Speter  if (status)
917251881Speter    return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
918251881Speter                              try_utf8_from_internal_style(fname_apr, pool));
919251881Speter  else
920251881Speter    return SVN_NO_ERROR;
921251881Speter}
922251881Speter
923251881Speter#endif /* !WIN32 && !__OS2__ */
924251881Speter
925251881Spetersvn_error_t *
926251881Spetersvn_io_copy_perms(const char *src,
927251881Speter                  const char *dst,
928251881Speter                  apr_pool_t *pool)
929251881Speter{
930251881Speter  /* ### On Windows or OS/2, apr_file_perms_set always returns APR_ENOTIMPL,
931251881Speter         and the path passed to apr_file_perms_set must be encoded
932251881Speter         in the platform-specific path encoding; not necessary UTF-8.
933251881Speter         We need a platform-specific implementation to get the
934251881Speter         permissions right. */
935251881Speter
936251881Speter#if !defined(WIN32) && !defined(__OS2__)
937251881Speter  {
938251881Speter    apr_finfo_t finfo;
939251881Speter    svn_node_kind_t kind;
940251881Speter    svn_boolean_t is_special;
941251881Speter    svn_error_t *err;
942251881Speter
943251881Speter    /* If DST is a symlink, don't bother copying permissions. */
944251881Speter    SVN_ERR(svn_io_check_special_path(dst, &kind, &is_special, pool));
945251881Speter    if (is_special)
946251881Speter      return SVN_NO_ERROR;
947251881Speter
948251881Speter    SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_PROT, pool));
949251881Speter    err = file_perms_set(dst, finfo.protection, pool);
950251881Speter    if (err)
951251881Speter      {
952251881Speter        /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL
953251881Speter           here under normal circumstances, because the perms themselves
954251881Speter           came from a call to apr_file_info_get(), and we already know
955251881Speter           this is the non-Win32 case.  But if it does happen, it's not
956251881Speter           an error. */
957251881Speter        if (APR_STATUS_IS_INCOMPLETE(err->apr_err) ||
958251881Speter            APR_STATUS_IS_ENOTIMPL(err->apr_err))
959251881Speter          svn_error_clear(err);
960251881Speter        else
961251881Speter          {
962251881Speter            const char *message;
963251881Speter            message = apr_psprintf(pool, _("Can't set permissions on '%s'"),
964251881Speter                                   svn_dirent_local_style(dst, pool));
965251881Speter            return svn_error_quick_wrap(err, message);
966251881Speter          }
967251881Speter      }
968251881Speter  }
969251881Speter#endif /* !WIN32 && !__OS2__ */
970251881Speter
971251881Speter  return SVN_NO_ERROR;
972251881Speter}
973251881Speter
974251881Speter
975251881Spetersvn_error_t *
976251881Spetersvn_io_append_file(const char *src, const char *dst, apr_pool_t *pool)
977251881Speter{
978251881Speter  apr_status_t apr_err;
979251881Speter  const char *src_apr, *dst_apr;
980251881Speter
981251881Speter  SVN_ERR(cstring_from_utf8(&src_apr, src, pool));
982251881Speter  SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool));
983251881Speter
984251881Speter  apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool);
985251881Speter
986251881Speter  if (apr_err)
987251881Speter    return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"),
988251881Speter                              svn_dirent_local_style(src, pool),
989251881Speter                              svn_dirent_local_style(dst, pool));
990251881Speter
991251881Speter  return SVN_NO_ERROR;
992251881Speter}
993251881Speter
994251881Speter
995251881Spetersvn_error_t *svn_io_copy_dir_recursively(const char *src,
996251881Speter                                         const char *dst_parent,
997251881Speter                                         const char *dst_basename,
998251881Speter                                         svn_boolean_t copy_perms,
999251881Speter                                         svn_cancel_func_t cancel_func,
1000251881Speter                                         void *cancel_baton,
1001251881Speter                                         apr_pool_t *pool)
1002251881Speter{
1003251881Speter  svn_node_kind_t kind;
1004251881Speter  apr_status_t status;
1005251881Speter  const char *dst_path;
1006251881Speter  apr_dir_t *this_dir;
1007251881Speter  apr_finfo_t this_entry;
1008251881Speter  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
1009251881Speter
1010251881Speter  /* Make a subpool for recursion */
1011251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1012251881Speter
1013251881Speter  /* The 'dst_path' is simply dst_parent/dst_basename */
1014251881Speter  dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
1015251881Speter
1016251881Speter  /* Sanity checks:  SRC and DST_PARENT are directories, and
1017251881Speter     DST_BASENAME doesn't already exist in DST_PARENT. */
1018251881Speter  SVN_ERR(svn_io_check_path(src, &kind, subpool));
1019251881Speter  if (kind != svn_node_dir)
1020251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1021251881Speter                             _("Source '%s' is not a directory"),
1022251881Speter                             svn_dirent_local_style(src, pool));
1023251881Speter
1024251881Speter  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
1025251881Speter  if (kind != svn_node_dir)
1026251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1027251881Speter                             _("Destination '%s' is not a directory"),
1028251881Speter                             svn_dirent_local_style(dst_parent, pool));
1029251881Speter
1030251881Speter  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
1031251881Speter  if (kind != svn_node_none)
1032251881Speter    return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
1033251881Speter                             _("Destination '%s' already exists"),
1034251881Speter                             svn_dirent_local_style(dst_path, pool));
1035251881Speter
1036251881Speter  /* Create the new directory. */
1037251881Speter  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
1038251881Speter  SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool));
1039251881Speter
1040251881Speter  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
1041251881Speter  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
1042251881Speter
1043251881Speter  for (status = apr_dir_read(&this_entry, flags, this_dir);
1044251881Speter       status == APR_SUCCESS;
1045251881Speter       status = apr_dir_read(&this_entry, flags, this_dir))
1046251881Speter    {
1047251881Speter      if ((this_entry.name[0] == '.')
1048251881Speter          && ((this_entry.name[1] == '\0')
1049251881Speter              || ((this_entry.name[1] == '.')
1050251881Speter                  && (this_entry.name[2] == '\0'))))
1051251881Speter        {
1052251881Speter          continue;
1053251881Speter        }
1054251881Speter      else
1055251881Speter        {
1056251881Speter          const char *src_target, *entryname_utf8;
1057251881Speter
1058251881Speter          if (cancel_func)
1059251881Speter            SVN_ERR(cancel_func(cancel_baton));
1060251881Speter
1061251881Speter          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
1062251881Speter                                     src, subpool));
1063251881Speter          src_target = svn_dirent_join(src, entryname_utf8, subpool);
1064251881Speter
1065251881Speter          if (this_entry.filetype == APR_REG) /* regular file */
1066251881Speter            {
1067251881Speter              const char *dst_target = svn_dirent_join(dst_path,
1068251881Speter                                                       entryname_utf8,
1069251881Speter                                                       subpool);
1070251881Speter              SVN_ERR(svn_io_copy_file(src_target, dst_target,
1071251881Speter                                       copy_perms, subpool));
1072251881Speter            }
1073251881Speter          else if (this_entry.filetype == APR_LNK) /* symlink */
1074251881Speter            {
1075251881Speter              const char *dst_target = svn_dirent_join(dst_path,
1076251881Speter                                                       entryname_utf8,
1077251881Speter                                                       subpool);
1078251881Speter              SVN_ERR(svn_io_copy_link(src_target, dst_target,
1079251881Speter                                       subpool));
1080251881Speter            }
1081251881Speter          else if (this_entry.filetype == APR_DIR) /* recurse */
1082251881Speter            {
1083251881Speter              /* Prevent infinite recursion by filtering off our
1084251881Speter                 newly created destination path. */
1085251881Speter              if (strcmp(src, dst_parent) == 0
1086251881Speter                  && strcmp(entryname_utf8, dst_basename) == 0)
1087251881Speter                continue;
1088251881Speter
1089251881Speter              SVN_ERR(svn_io_copy_dir_recursively
1090251881Speter                      (src_target,
1091251881Speter                       dst_path,
1092251881Speter                       entryname_utf8,
1093251881Speter                       copy_perms,
1094251881Speter                       cancel_func,
1095251881Speter                       cancel_baton,
1096251881Speter                       subpool));
1097251881Speter            }
1098251881Speter          /* ### support other APR node types someday?? */
1099251881Speter
1100251881Speter        }
1101251881Speter    }
1102251881Speter
1103251881Speter  if (! (APR_STATUS_IS_ENOENT(status)))
1104251881Speter    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
1105251881Speter                              svn_dirent_local_style(src, pool));
1106251881Speter
1107251881Speter  status = apr_dir_close(this_dir);
1108251881Speter  if (status)
1109251881Speter    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
1110251881Speter                              svn_dirent_local_style(src, pool));
1111251881Speter
1112251881Speter  /* Free any memory used by recursion */
1113251881Speter  svn_pool_destroy(subpool);
1114251881Speter
1115251881Speter  return SVN_NO_ERROR;
1116251881Speter}
1117251881Speter
1118251881Speter
1119251881Spetersvn_error_t *
1120251881Spetersvn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
1121251881Speter{
1122251881Speter  const char *path_apr;
1123251881Speter  apr_status_t apr_err;
1124251881Speter
1125251881Speter  if (svn_path_is_empty(path))
1126251881Speter    /* Empty path (current dir) is assumed to always exist,
1127251881Speter       so we do nothing, per docs. */
1128251881Speter    return SVN_NO_ERROR;
1129251881Speter
1130251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1131251881Speter
1132251881Speter  apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool);
1133251881Speter  WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr,
1134251881Speter                                                   APR_OS_DEFAULT, pool));
1135251881Speter
1136251881Speter  if (apr_err)
1137251881Speter    return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"),
1138251881Speter                              svn_dirent_local_style(path, pool));
1139251881Speter
1140251881Speter  return SVN_NO_ERROR;
1141251881Speter}
1142251881Speter
1143251881Spetersvn_error_t *svn_io_file_create(const char *file,
1144251881Speter                                const char *contents,
1145251881Speter                                apr_pool_t *pool)
1146251881Speter{
1147251881Speter  apr_file_t *f;
1148251881Speter  apr_size_t written;
1149251881Speter  svn_error_t *err = SVN_NO_ERROR;
1150251881Speter
1151251881Speter  SVN_ERR(svn_io_file_open(&f, file,
1152251881Speter                           (APR_WRITE | APR_CREATE | APR_EXCL),
1153251881Speter                           APR_OS_DEFAULT,
1154251881Speter                           pool));
1155251881Speter  if (contents && *contents)
1156251881Speter    err = svn_io_file_write_full(f, contents, strlen(contents),
1157251881Speter                                 &written, pool);
1158251881Speter
1159251881Speter
1160251881Speter  return svn_error_trace(
1161251881Speter                        svn_error_compose_create(err,
1162251881Speter                                                 svn_io_file_close(f, pool)));
1163251881Speter}
1164251881Speter
1165251881Spetersvn_error_t *svn_io_dir_file_copy(const char *src_path,
1166251881Speter                                  const char *dest_path,
1167251881Speter                                  const char *file,
1168251881Speter                                  apr_pool_t *pool)
1169251881Speter{
1170251881Speter  const char *file_dest_path = svn_dirent_join(dest_path, file, pool);
1171251881Speter  const char *file_src_path = svn_dirent_join(src_path, file, pool);
1172251881Speter
1173251881Speter  return svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool);
1174251881Speter}
1175251881Speter
1176251881Speter
1177251881Speter/*** Modtime checking. ***/
1178251881Speter
1179251881Spetersvn_error_t *
1180251881Spetersvn_io_file_affected_time(apr_time_t *apr_time,
1181251881Speter                          const char *path,
1182251881Speter                          apr_pool_t *pool)
1183251881Speter{
1184251881Speter  apr_finfo_t finfo;
1185251881Speter
1186251881Speter  SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1187251881Speter
1188251881Speter  *apr_time = finfo.mtime;
1189251881Speter
1190251881Speter  return SVN_NO_ERROR;
1191251881Speter}
1192251881Speter
1193251881Speter
1194251881Spetersvn_error_t *
1195251881Spetersvn_io_set_file_affected_time(apr_time_t apr_time,
1196251881Speter                              const char *path,
1197251881Speter                              apr_pool_t *pool)
1198251881Speter{
1199251881Speter  apr_status_t status;
1200251881Speter  const char *native_path;
1201251881Speter
1202251881Speter  SVN_ERR(cstring_from_utf8(&native_path, path, pool));
1203251881Speter  status = apr_file_mtime_set(native_path, apr_time, pool);
1204251881Speter
1205251881Speter  if (status)
1206251881Speter    return svn_error_wrap_apr(status, _("Can't set access time of '%s'"),
1207251881Speter                              svn_dirent_local_style(path, pool));
1208251881Speter
1209251881Speter  return SVN_NO_ERROR;
1210251881Speter}
1211251881Speter
1212251881Speter
1213251881Spetervoid
1214251881Spetersvn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool)
1215251881Speter{
1216251881Speter  apr_time_t now, then;
1217251881Speter  svn_error_t *err;
1218251881Speter  char *sleep_env_var;
1219251881Speter
1220251881Speter  sleep_env_var = getenv(SVN_SLEEP_ENV_VAR);
1221251881Speter
1222251881Speter  if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0)
1223251881Speter    return; /* Allow skipping for testing */
1224251881Speter
1225251881Speter  now = apr_time_now();
1226251881Speter
1227251881Speter  /* Calculate 0.02 seconds after the next second wallclock tick. */
1228251881Speter  then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50);
1229251881Speter
1230251881Speter  /* Worst case is waiting one second, so we can use that time to determine
1231251881Speter     if we can sleep shorter than that */
1232251881Speter  if (path)
1233251881Speter    {
1234251881Speter      apr_finfo_t finfo;
1235251881Speter
1236251881Speter      err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool);
1237251881Speter
1238251881Speter      if (err)
1239251881Speter        {
1240251881Speter          svn_error_clear(err); /* Fall back on original behavior */
1241251881Speter        }
1242251881Speter      else if (finfo.mtime % APR_USEC_PER_SEC)
1243251881Speter        {
1244251881Speter          /* Very simplistic but safe approach:
1245251881Speter              If the filesystem has < sec mtime we can be reasonably sure
1246269847Speter              that the filesystem has some sub-second resolution.  On Windows
1247269847Speter              it is likely to be sub-millisecond; on Linux systems it depends
1248269847Speter              on the filesystem, ext4 is typically 1ms, 4ms or 10ms resolution.
1249251881Speter
1250251881Speter             ## Perhaps find a better algorithm here. This will fail once
1251269847Speter                in every 1000 cases on a millisecond precision filesystem
1252269847Speter                if the mtime happens to be an exact second.
1253251881Speter
1254251881Speter                But better to fail once in every thousand cases than every
1255251881Speter                time, like we did before.
1256251881Speter
1257251881Speter             Note for further research on algorithm:
1258269847Speter               FAT32 has < 1 sec precision on ctime, but 2 sec on mtime.
1259251881Speter
1260269847Speter               Linux/ext4 with CONFIG_HZ=250 has high resolution
1261269847Speter               apr_time_now and although the filesystem timestamps
1262269847Speter               have similar high precision they are only updated with
1263269847Speter               a coarser 4ms resolution. */
1264251881Speter
1265269847Speter          /* 10 milliseconds after now. */
1266269847Speter#ifndef SVN_HI_RES_SLEEP_MS
1267269847Speter#define SVN_HI_RES_SLEEP_MS 10
1268269847Speter#endif
1269269847Speter          then = now + apr_time_from_msec(SVN_HI_RES_SLEEP_MS);
1270251881Speter        }
1271251881Speter
1272269847Speter      /* Remove time taken to do stat() from sleep. */
1273269847Speter      now = apr_time_now();
1274251881Speter    }
1275251881Speter
1276269847Speter  if (now >= then)
1277269847Speter    return; /* Passing negative values may suspend indefinitely (Windows) */
1278269847Speter
1279269847Speter  /* (t < 1000 will be round to 0 in apr) */
1280269847Speter  if (then - now < 1000)
1281269847Speter    apr_sleep(1000);
1282269847Speter  else
1283269847Speter    apr_sleep(then - now);
1284251881Speter}
1285251881Speter
1286251881Speter
1287251881Spetersvn_error_t *
1288251881Spetersvn_io_filesizes_different_p(svn_boolean_t *different_p,
1289251881Speter                             const char *file1,
1290251881Speter                             const char *file2,
1291251881Speter                             apr_pool_t *pool)
1292251881Speter{
1293251881Speter  apr_finfo_t finfo1;
1294251881Speter  apr_finfo_t finfo2;
1295251881Speter  apr_status_t status;
1296251881Speter  const char *file1_apr, *file2_apr;
1297251881Speter
1298251881Speter  /* Not using svn_io_stat() because don't want to generate
1299251881Speter     svn_error_t objects for non-error conditions. */
1300251881Speter
1301251881Speter  SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool));
1302251881Speter  SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool));
1303251881Speter
1304251881Speter  /* Stat both files */
1305251881Speter  status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool);
1306251881Speter  if (status)
1307251881Speter    {
1308251881Speter      /* If we got an error stat'ing a file, it could be because the
1309251881Speter         file was removed... or who knows.  Whatever the case, we
1310251881Speter         don't know if the filesizes are definitely different, so
1311251881Speter         assume that they're not. */
1312251881Speter      *different_p = FALSE;
1313251881Speter      return SVN_NO_ERROR;
1314251881Speter    }
1315251881Speter
1316251881Speter  status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool);
1317251881Speter  if (status)
1318251881Speter    {
1319251881Speter      /* See previous comment. */
1320251881Speter      *different_p = FALSE;
1321251881Speter      return SVN_NO_ERROR;
1322251881Speter    }
1323251881Speter
1324251881Speter  /* Examine file sizes */
1325251881Speter  if (finfo1.size == finfo2.size)
1326251881Speter    *different_p = FALSE;
1327251881Speter  else
1328251881Speter    *different_p = TRUE;
1329251881Speter
1330251881Speter  return SVN_NO_ERROR;
1331251881Speter}
1332251881Speter
1333251881Speter
1334251881Spetersvn_error_t *
1335251881Spetersvn_io_filesizes_three_different_p(svn_boolean_t *different_p12,
1336251881Speter                                   svn_boolean_t *different_p23,
1337251881Speter                                   svn_boolean_t *different_p13,
1338251881Speter                                   const char *file1,
1339251881Speter                                   const char *file2,
1340251881Speter                                   const char *file3,
1341251881Speter                                   apr_pool_t *scratch_pool)
1342251881Speter{
1343251881Speter  apr_finfo_t finfo1, finfo2, finfo3;
1344251881Speter  apr_status_t status1, status2, status3;
1345251881Speter  const char *file1_apr, *file2_apr, *file3_apr;
1346251881Speter
1347251881Speter  /* Not using svn_io_stat() because don't want to generate
1348251881Speter     svn_error_t objects for non-error conditions. */
1349251881Speter
1350251881Speter  SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool));
1351251881Speter  SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool));
1352251881Speter  SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool));
1353251881Speter
1354251881Speter  /* Stat all three files */
1355251881Speter  status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool);
1356251881Speter  status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool);
1357251881Speter  status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool);
1358251881Speter
1359251881Speter  /* If we got an error stat'ing a file, it could be because the
1360251881Speter     file was removed... or who knows.  Whatever the case, we
1361251881Speter     don't know if the filesizes are definitely different, so
1362251881Speter     assume that they're not. */
1363251881Speter  *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size;
1364251881Speter  *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size;
1365251881Speter  *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size;
1366251881Speter
1367251881Speter  return SVN_NO_ERROR;
1368251881Speter}
1369251881Speter
1370251881Speter
1371251881Spetersvn_error_t *
1372251881Spetersvn_io_file_checksum2(svn_checksum_t **checksum,
1373251881Speter                      const char *file,
1374251881Speter                      svn_checksum_kind_t kind,
1375251881Speter                      apr_pool_t *pool)
1376251881Speter{
1377251881Speter  svn_stream_t *file_stream;
1378251881Speter  svn_stream_t *checksum_stream;
1379251881Speter  apr_file_t* f;
1380251881Speter
1381251881Speter  SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool));
1382251881Speter  file_stream = svn_stream_from_aprfile2(f, FALSE, pool);
1383251881Speter  checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind,
1384251881Speter                                            TRUE, pool);
1385251881Speter
1386251881Speter  /* Because the checksummed stream will force the reading (and
1387251881Speter     checksumming) of all the file's bytes, we can just close the stream
1388251881Speter     and let its magic work. */
1389251881Speter  return svn_stream_close(checksum_stream);
1390251881Speter}
1391251881Speter
1392251881Speter
1393251881Spetersvn_error_t *
1394251881Spetersvn_io_file_checksum(unsigned char digest[],
1395251881Speter                     const char *file,
1396251881Speter                     apr_pool_t *pool)
1397251881Speter{
1398251881Speter  svn_checksum_t *checksum;
1399251881Speter
1400251881Speter  SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool));
1401251881Speter  memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE);
1402251881Speter
1403251881Speter  return SVN_NO_ERROR;
1404251881Speter}
1405251881Speter
1406251881Speter
1407251881Speter
1408251881Speter/*** Permissions and modes. ***/
1409251881Speter
1410251881Speter#if !defined(WIN32) && !defined(__OS2__)
1411251881Speter/* Given the file specified by PATH, attempt to create an
1412251881Speter   identical version of it owned by the current user.  This is done by
1413251881Speter   moving it to a temporary location, copying the file back to its old
1414251881Speter   path, then deleting the temporarily moved version.  All temporary
1415251881Speter   allocations are done in POOL. */
1416251881Speterstatic svn_error_t *
1417251881Speterreown_file(const char *path,
1418251881Speter           apr_pool_t *pool)
1419251881Speter{
1420251881Speter  const char *unique_name;
1421251881Speter
1422251881Speter  SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name,
1423251881Speter                                   svn_dirent_dirname(path, pool),
1424251881Speter                                   svn_io_file_del_none, pool, pool));
1425251881Speter  SVN_ERR(svn_io_file_rename(path, unique_name, pool));
1426251881Speter  SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool));
1427251881Speter  return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool));
1428251881Speter}
1429251881Speter
1430251881Speter/* Determine what the PERMS for a new file should be by looking at the
1431251881Speter   permissions of a temporary file that we create.
1432251881Speter   Unfortunately, umask() as defined in POSIX provides no thread-safe way
1433251881Speter   to get at the current value of the umask, so what we're doing here is
1434251881Speter   the only way we have to determine which combination of write bits
1435251881Speter   (User/Group/World) should be set by default.
1436251881Speter   Make temporary allocations in SCRATCH_POOL.  */
1437251881Speterstatic svn_error_t *
1438251881Speterget_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool)
1439251881Speter{
1440251881Speter  /* the default permissions as read from the temp folder */
1441251881Speter  static apr_fileperms_t default_perms = 0;
1442251881Speter
1443251881Speter  /* Technically, this "racy": Multiple threads may use enter here and
1444251881Speter     try to figure out the default permission concurrently. That's fine
1445251881Speter     since they will end up with the same results. Even more technical,
1446251881Speter     apr_fileperms_t is an atomic type on 32+ bit machines.
1447251881Speter   */
1448251881Speter  if (default_perms == 0)
1449251881Speter    {
1450251881Speter      apr_finfo_t finfo;
1451251881Speter      apr_file_t *fd;
1452251881Speter      const char *fname_base, *fname;
1453251881Speter      apr_uint32_t randomish;
1454251881Speter      svn_error_t *err;
1455251881Speter
1456251881Speter      /* Get the perms for a newly created file to find out what bits
1457251881Speter        should be set.
1458251881Speter
1459251881Speter        Explictly delete the file because we want this file to be as
1460251881Speter        short-lived as possible since its presence means other
1461251881Speter        processes may have to try multiple names.
1462251881Speter
1463251881Speter        Using svn_io_open_uniquely_named() here because other tempfile
1464251881Speter        creation functions tweak the permission bits of files they create.
1465251881Speter      */
1466251881Speter      randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool
1467251881Speter                   + (apr_uint32_t)apr_time_now());
1468251881Speter      fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish);
1469251881Speter
1470251881Speter      SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base,
1471251881Speter                                         NULL, svn_io_file_del_none,
1472251881Speter                                         scratch_pool, scratch_pool));
1473251881Speter      err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool);
1474251881Speter      err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool));
1475251881Speter      err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE,
1476251881Speter                                                              scratch_pool));
1477251881Speter      SVN_ERR(err);
1478251881Speter      *perms = finfo.protection;
1479251881Speter      default_perms = finfo.protection;
1480251881Speter    }
1481251881Speter  else
1482251881Speter    *perms = default_perms;
1483251881Speter
1484251881Speter  return SVN_NO_ERROR;
1485251881Speter}
1486251881Speter
1487251881Speter/* OR together permission bits of the file FD and the default permissions
1488251881Speter   of a file as determined by get_default_file_perms(). Do temporary
1489251881Speter   allocations in SCRATCH_POOL. */
1490251881Speterstatic svn_error_t *
1491251881Spetermerge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms,
1492251881Speter                         apr_pool_t *scratch_pool)
1493251881Speter{
1494251881Speter  apr_finfo_t finfo;
1495251881Speter  apr_fileperms_t default_perms;
1496251881Speter
1497251881Speter  SVN_ERR(get_default_file_perms(&default_perms, scratch_pool));
1498251881Speter  SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool));
1499251881Speter
1500251881Speter  /* Glom the perms together. */
1501251881Speter  *perms = default_perms | finfo.protection;
1502251881Speter  return SVN_NO_ERROR;
1503251881Speter}
1504251881Speter
1505251881Speter/* This is a helper function for the svn_io_set_file_read* functions
1506251881Speter   that attempts to honor the users umask when dealing with
1507251881Speter   permission changes.  It is a no-op when invoked on a symlink. */
1508251881Speterstatic svn_error_t *
1509251881Speterio_set_file_perms(const char *path,
1510251881Speter                  svn_boolean_t change_readwrite,
1511251881Speter                  svn_boolean_t enable_write,
1512251881Speter                  svn_boolean_t change_executable,
1513251881Speter                  svn_boolean_t executable,
1514251881Speter                  svn_boolean_t ignore_enoent,
1515251881Speter                  apr_pool_t *pool)
1516251881Speter{
1517251881Speter  apr_status_t status;
1518251881Speter  const char *path_apr;
1519251881Speter  apr_finfo_t finfo;
1520251881Speter  apr_fileperms_t perms_to_set;
1521251881Speter
1522251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1523251881Speter
1524251881Speter  /* Try to change only a minimal amount of the perms first
1525251881Speter     by getting the current perms and adding bits
1526251881Speter     only on where read perms are granted.  If this fails
1527251881Speter     fall through to just setting file attributes. */
1528251881Speter  status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool);
1529251881Speter  if (status)
1530251881Speter    {
1531251881Speter      if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1532251881Speter        return SVN_NO_ERROR;
1533251881Speter      else if (status != APR_ENOTIMPL)
1534251881Speter        return svn_error_wrap_apr(status,
1535251881Speter                                  _("Can't change perms of file '%s'"),
1536251881Speter                                  svn_dirent_local_style(path, pool));
1537251881Speter      return SVN_NO_ERROR;
1538251881Speter    }
1539251881Speter
1540251881Speter  if (finfo.filetype == APR_LNK)
1541251881Speter    return SVN_NO_ERROR;
1542251881Speter
1543251881Speter  perms_to_set = finfo.protection;
1544251881Speter  if (change_readwrite)
1545251881Speter    {
1546251881Speter      if (enable_write) /* Make read-write. */
1547251881Speter        {
1548262253Speter          /* Tweak the owner bits only. The group/other bits aren't safe to
1549262253Speter           * touch because we may end up setting them in undesired ways. */
1550262253Speter          perms_to_set |= (APR_UREAD|APR_UWRITE);
1551251881Speter        }
1552251881Speter      else
1553251881Speter        {
1554251881Speter          if (finfo.protection & APR_UREAD)
1555251881Speter            perms_to_set &= ~APR_UWRITE;
1556251881Speter          if (finfo.protection & APR_GREAD)
1557251881Speter            perms_to_set &= ~APR_GWRITE;
1558251881Speter          if (finfo.protection & APR_WREAD)
1559251881Speter            perms_to_set &= ~APR_WWRITE;
1560251881Speter        }
1561251881Speter    }
1562251881Speter
1563251881Speter  if (change_executable)
1564251881Speter    {
1565251881Speter      if (executable)
1566251881Speter        {
1567251881Speter          if (finfo.protection & APR_UREAD)
1568251881Speter            perms_to_set |= APR_UEXECUTE;
1569251881Speter          if (finfo.protection & APR_GREAD)
1570251881Speter            perms_to_set |= APR_GEXECUTE;
1571251881Speter          if (finfo.protection & APR_WREAD)
1572251881Speter            perms_to_set |= APR_WEXECUTE;
1573251881Speter        }
1574251881Speter      else
1575251881Speter        {
1576251881Speter          if (finfo.protection & APR_UREAD)
1577251881Speter            perms_to_set &= ~APR_UEXECUTE;
1578251881Speter          if (finfo.protection & APR_GREAD)
1579251881Speter            perms_to_set &= ~APR_GEXECUTE;
1580251881Speter          if (finfo.protection & APR_WREAD)
1581251881Speter            perms_to_set &= ~APR_WEXECUTE;
1582251881Speter        }
1583251881Speter    }
1584251881Speter
1585251881Speter  /* If we aren't changing anything then just return, this saves
1586251881Speter     some system calls and helps with shared working copies */
1587251881Speter  if (perms_to_set == finfo.protection)
1588251881Speter    return SVN_NO_ERROR;
1589251881Speter
1590251881Speter  status = apr_file_perms_set(path_apr, perms_to_set);
1591251881Speter  if (!status)
1592251881Speter    return SVN_NO_ERROR;
1593251881Speter
1594251881Speter  if (APR_STATUS_IS_EPERM(status))
1595251881Speter    {
1596251881Speter      /* We don't have permissions to change the
1597251881Speter         permissions!  Try a move, copy, and delete
1598251881Speter         workaround to see if we can get the file owned by
1599251881Speter         us.  If these succeed, try the permissions set
1600251881Speter         again.
1601251881Speter
1602251881Speter         Note that we only attempt this in the
1603251881Speter         stat-available path.  This assumes that the
1604251881Speter         move-copy workaround will only be helpful on
1605251881Speter         platforms that implement apr_stat. */
1606251881Speter      SVN_ERR(reown_file(path, pool));
1607251881Speter      status = apr_file_perms_set(path_apr, perms_to_set);
1608251881Speter    }
1609251881Speter
1610251881Speter  if (!status)
1611251881Speter    return SVN_NO_ERROR;
1612251881Speter
1613251881Speter  if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1614251881Speter    return SVN_NO_ERROR;
1615251881Speter  else if (status == APR_ENOTIMPL)
1616251881Speter    {
1617251881Speter      /* At least try to set the attributes. */
1618251881Speter      apr_fileattrs_t attrs = 0;
1619251881Speter      apr_fileattrs_t attrs_values = 0;
1620251881Speter
1621251881Speter      if (change_readwrite)
1622251881Speter        {
1623251881Speter          attrs = APR_FILE_ATTR_READONLY;
1624251881Speter          if (!enable_write)
1625251881Speter            attrs_values = APR_FILE_ATTR_READONLY;
1626251881Speter        }
1627251881Speter      if (change_executable)
1628251881Speter        {
1629251881Speter          attrs = APR_FILE_ATTR_EXECUTABLE;
1630251881Speter          if (executable)
1631251881Speter            attrs_values = APR_FILE_ATTR_EXECUTABLE;
1632251881Speter        }
1633251881Speter      status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
1634251881Speter    }
1635251881Speter
1636251881Speter  return svn_error_wrap_apr(status,
1637251881Speter                            _("Can't change perms of file '%s'"),
1638251881Speter                            svn_dirent_local_style(path, pool));
1639251881Speter}
1640251881Speter#endif /* !WIN32 && !__OS2__ */
1641251881Speter
1642251881Speter#ifdef WIN32
1643251881Speter#if APR_HAS_UNICODE_FS
1644251881Speter/* copy of the apr function utf8_to_unicode_path since apr doesn't export this one */
1645251881Speterstatic apr_status_t io_utf8_to_unicode_path(apr_wchar_t* retstr, apr_size_t retlen,
1646251881Speter                                            const char* srcstr)
1647251881Speter{
1648251881Speter    /* TODO: The computations could preconvert the string to determine
1649251881Speter     * the true size of the retstr, but that's a memory over speed
1650251881Speter     * tradeoff that isn't appropriate this early in development.
1651251881Speter     *
1652251881Speter     * Allocate the maximum string length based on leading 4
1653251881Speter     * characters of \\?\ (allowing nearly unlimited path lengths)
1654251881Speter     * plus the trailing null, then transform /'s into \\'s since
1655251881Speter     * the \\?\ form doesn't allow '/' path separators.
1656251881Speter     *
1657251881Speter     * Note that the \\?\ form only works for local drive paths, and
1658251881Speter     * \\?\UNC\ is needed UNC paths.
1659251881Speter     */
1660251881Speter    apr_size_t srcremains = strlen(srcstr) + 1;
1661251881Speter    apr_wchar_t *t = retstr;
1662251881Speter    apr_status_t rv;
1663251881Speter
1664251881Speter    /* This is correct, we don't twist the filename if it will
1665251881Speter     * definitely be shorter than 248 characters.  It merits some
1666251881Speter     * performance testing to see if this has any effect, but there
1667251881Speter     * seem to be applications that get confused by the resulting
1668251881Speter     * Unicode \\?\ style file names, especially if they use argv[0]
1669251881Speter     * or call the Win32 API functions such as GetModuleName, etc.
1670251881Speter     * Not every application is prepared to handle such names.
1671251881Speter     *
1672251881Speter     * Note also this is shorter than MAX_PATH, as directory paths
1673251881Speter     * are actually limited to 248 characters.
1674251881Speter     *
1675251881Speter     * Note that a utf-8 name can never result in more wide chars
1676251881Speter     * than the original number of utf-8 narrow chars.
1677251881Speter     */
1678251881Speter    if (srcremains > 248) {
1679251881Speter        if (srcstr[1] == ':' && (srcstr[2] == '/' || srcstr[2] == '\\')) {
1680251881Speter            wcscpy (retstr, L"\\\\?\\");
1681251881Speter            retlen -= 4;
1682251881Speter            t += 4;
1683251881Speter        }
1684251881Speter        else if ((srcstr[0] == '/' || srcstr[0] == '\\')
1685251881Speter              && (srcstr[1] == '/' || srcstr[1] == '\\')
1686251881Speter              && (srcstr[2] != '?')) {
1687251881Speter            /* Skip the slashes */
1688251881Speter            srcstr += 2;
1689251881Speter            srcremains -= 2;
1690251881Speter            wcscpy (retstr, L"\\\\?\\UNC\\");
1691251881Speter            retlen -= 8;
1692251881Speter            t += 8;
1693251881Speter        }
1694251881Speter    }
1695251881Speter
1696251881Speter    if (rv = apr_conv_utf8_to_ucs2(srcstr, &srcremains, t, &retlen)) {
1697251881Speter        return (rv == APR_INCOMPLETE) ? APR_EINVAL : rv;
1698251881Speter    }
1699251881Speter    if (srcremains) {
1700251881Speter        return APR_ENAMETOOLONG;
1701251881Speter    }
1702251881Speter    for (; *t; ++t)
1703251881Speter        if (*t == L'/')
1704251881Speter            *t = L'\\';
1705251881Speter    return APR_SUCCESS;
1706251881Speter}
1707251881Speter#endif
1708251881Speter
1709251881Speterstatic apr_status_t io_win_file_attrs_set(const char *fname,
1710251881Speter                                          DWORD attributes,
1711251881Speter                                          DWORD attr_mask,
1712251881Speter                                          apr_pool_t *pool)
1713251881Speter{
1714251881Speter    /* this is an implementation of apr_file_attrs_set() but one
1715251881Speter       that uses the proper Windows attributes instead of the apr
1716251881Speter       attributes. This way, we can apply any Windows file and
1717251881Speter       folder attributes even if apr doesn't implement them */
1718251881Speter    DWORD flags;
1719251881Speter    apr_status_t rv;
1720251881Speter#if APR_HAS_UNICODE_FS
1721251881Speter    apr_wchar_t wfname[APR_PATH_MAX];
1722251881Speter#endif
1723251881Speter
1724251881Speter#if APR_HAS_UNICODE_FS
1725251881Speter    IF_WIN_OS_IS_UNICODE
1726251881Speter    {
1727251881Speter        if (rv = io_utf8_to_unicode_path(wfname,
1728251881Speter                                         sizeof(wfname) / sizeof(wfname[0]),
1729251881Speter                                         fname))
1730251881Speter            return rv;
1731251881Speter        flags = GetFileAttributesW(wfname);
1732251881Speter    }
1733251881Speter#endif
1734251881Speter#if APR_HAS_ANSI_FS
1735251881Speter    ELSE_WIN_OS_IS_ANSI
1736251881Speter    {
1737251881Speter        flags = GetFileAttributesA(fname);
1738251881Speter    }
1739251881Speter#endif
1740251881Speter
1741251881Speter    if (flags == 0xFFFFFFFF)
1742251881Speter        return apr_get_os_error();
1743251881Speter
1744251881Speter    flags &= ~attr_mask;
1745251881Speter    flags |= (attributes & attr_mask);
1746251881Speter
1747251881Speter#if APR_HAS_UNICODE_FS
1748251881Speter    IF_WIN_OS_IS_UNICODE
1749251881Speter    {
1750251881Speter        rv = SetFileAttributesW(wfname, flags);
1751251881Speter    }
1752251881Speter#endif
1753251881Speter#if APR_HAS_ANSI_FS
1754251881Speter    ELSE_WIN_OS_IS_ANSI
1755251881Speter    {
1756251881Speter        rv = SetFileAttributesA(fname, flags);
1757251881Speter    }
1758251881Speter#endif
1759251881Speter
1760251881Speter    if (rv == 0)
1761251881Speter        return apr_get_os_error();
1762251881Speter
1763251881Speter    return APR_SUCCESS;
1764251881Speter}
1765251881Speter
1766251881Speter#endif
1767251881Speter
1768251881Spetersvn_error_t *
1769251881Spetersvn_io_set_file_read_write_carefully(const char *path,
1770251881Speter                                     svn_boolean_t enable_write,
1771251881Speter                                     svn_boolean_t ignore_enoent,
1772251881Speter                                     apr_pool_t *pool)
1773251881Speter{
1774251881Speter  if (enable_write)
1775251881Speter    return svn_io_set_file_read_write(path, ignore_enoent, pool);
1776251881Speter  return svn_io_set_file_read_only(path, ignore_enoent, pool);
1777251881Speter}
1778251881Speter
1779251881Spetersvn_error_t *
1780251881Spetersvn_io_set_file_read_only(const char *path,
1781251881Speter                          svn_boolean_t ignore_enoent,
1782251881Speter                          apr_pool_t *pool)
1783251881Speter{
1784251881Speter  /* On Windows and OS/2, just set the file attributes -- on unix call
1785251881Speter     our internal function which attempts to honor the umask. */
1786251881Speter#if !defined(WIN32) && !defined(__OS2__)
1787251881Speter  return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
1788251881Speter                           ignore_enoent, pool);
1789251881Speter#else
1790251881Speter  apr_status_t status;
1791251881Speter  const char *path_apr;
1792251881Speter
1793251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1794251881Speter
1795251881Speter  status = apr_file_attrs_set(path_apr,
1796251881Speter                              APR_FILE_ATTR_READONLY,
1797251881Speter                              APR_FILE_ATTR_READONLY,
1798251881Speter                              pool);
1799251881Speter
1800251881Speter  if (status && status != APR_ENOTIMPL)
1801251881Speter    if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
1802251881Speter      return svn_error_wrap_apr(status,
1803251881Speter                                _("Can't set file '%s' read-only"),
1804251881Speter                                svn_dirent_local_style(path, pool));
1805251881Speter
1806251881Speter  return SVN_NO_ERROR;
1807251881Speter#endif
1808251881Speter}
1809251881Speter
1810251881Speter
1811251881Spetersvn_error_t *
1812251881Spetersvn_io_set_file_read_write(const char *path,
1813251881Speter                           svn_boolean_t ignore_enoent,
1814251881Speter                           apr_pool_t *pool)
1815251881Speter{
1816251881Speter  /* On Windows and OS/2, just set the file attributes -- on unix call
1817251881Speter     our internal function which attempts to honor the umask. */
1818251881Speter#if !defined(WIN32) && !defined(__OS2__)
1819251881Speter  return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
1820251881Speter                           ignore_enoent, pool);
1821251881Speter#else
1822251881Speter  apr_status_t status;
1823251881Speter  const char *path_apr;
1824251881Speter
1825251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1826251881Speter
1827251881Speter  status = apr_file_attrs_set(path_apr,
1828251881Speter                              0,
1829251881Speter                              APR_FILE_ATTR_READONLY,
1830251881Speter                              pool);
1831251881Speter
1832251881Speter  if (status && status != APR_ENOTIMPL)
1833251881Speter    if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
1834251881Speter      return svn_error_wrap_apr(status,
1835251881Speter                                _("Can't set file '%s' read-write"),
1836251881Speter                                svn_dirent_local_style(path, pool));
1837251881Speter
1838251881Speter  return SVN_NO_ERROR;
1839251881Speter#endif
1840251881Speter}
1841251881Speter
1842251881Spetersvn_error_t *
1843251881Spetersvn_io_set_file_executable(const char *path,
1844251881Speter                           svn_boolean_t executable,
1845251881Speter                           svn_boolean_t ignore_enoent,
1846251881Speter                           apr_pool_t *pool)
1847251881Speter{
1848251881Speter  /* On Windows and OS/2, just exit -- on unix call our internal function
1849251881Speter  which attempts to honor the umask. */
1850251881Speter#if (!defined(WIN32) && !defined(__OS2__))
1851251881Speter  return io_set_file_perms(path, FALSE, FALSE, TRUE, executable,
1852251881Speter                           ignore_enoent, pool);
1853251881Speter#else
1854251881Speter  return SVN_NO_ERROR;
1855251881Speter#endif
1856251881Speter}
1857251881Speter
1858251881Speter
1859251881Spetersvn_error_t *
1860251881Spetersvn_io__is_finfo_read_only(svn_boolean_t *read_only,
1861251881Speter                           apr_finfo_t *file_info,
1862251881Speter                           apr_pool_t *pool)
1863251881Speter{
1864251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
1865251881Speter  apr_status_t apr_err;
1866251881Speter  apr_uid_t uid;
1867251881Speter  apr_gid_t gid;
1868251881Speter
1869251881Speter  *read_only = FALSE;
1870251881Speter
1871251881Speter  apr_err = apr_uid_current(&uid, &gid, pool);
1872251881Speter
1873251881Speter  if (apr_err)
1874251881Speter    return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
1875251881Speter
1876251881Speter  /* Check write bit for current user. */
1877251881Speter  if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
1878251881Speter    *read_only = !(file_info->protection & APR_UWRITE);
1879251881Speter
1880251881Speter  else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
1881251881Speter    *read_only = !(file_info->protection & APR_GWRITE);
1882251881Speter
1883251881Speter  else
1884251881Speter    *read_only = !(file_info->protection & APR_WWRITE);
1885251881Speter
1886251881Speter#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
1887251881Speter  *read_only = (file_info->protection & APR_FREADONLY);
1888251881Speter#endif
1889251881Speter
1890251881Speter  return SVN_NO_ERROR;
1891251881Speter}
1892251881Speter
1893251881Spetersvn_error_t *
1894251881Spetersvn_io__is_finfo_executable(svn_boolean_t *executable,
1895251881Speter                            apr_finfo_t *file_info,
1896251881Speter                            apr_pool_t *pool)
1897251881Speter{
1898251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
1899251881Speter  apr_status_t apr_err;
1900251881Speter  apr_uid_t uid;
1901251881Speter  apr_gid_t gid;
1902251881Speter
1903251881Speter  *executable = FALSE;
1904251881Speter
1905251881Speter  apr_err = apr_uid_current(&uid, &gid, pool);
1906251881Speter
1907251881Speter  if (apr_err)
1908251881Speter    return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
1909251881Speter
1910251881Speter  /* Check executable bit for current user. */
1911251881Speter  if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
1912251881Speter    *executable = (file_info->protection & APR_UEXECUTE);
1913251881Speter
1914251881Speter  else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
1915251881Speter    *executable = (file_info->protection & APR_GEXECUTE);
1916251881Speter
1917251881Speter  else
1918251881Speter    *executable = (file_info->protection & APR_WEXECUTE);
1919251881Speter
1920251881Speter#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
1921251881Speter  *executable = FALSE;
1922251881Speter#endif
1923251881Speter
1924251881Speter  return SVN_NO_ERROR;
1925251881Speter}
1926251881Speter
1927251881Spetersvn_error_t *
1928251881Spetersvn_io_is_file_executable(svn_boolean_t *executable,
1929251881Speter                          const char *path,
1930251881Speter                          apr_pool_t *pool)
1931251881Speter{
1932251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
1933251881Speter  apr_finfo_t file_info;
1934251881Speter
1935251881Speter  SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER,
1936251881Speter                      pool));
1937251881Speter  SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool));
1938251881Speter
1939251881Speter#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
1940251881Speter  *executable = FALSE;
1941251881Speter#endif
1942251881Speter
1943251881Speter  return SVN_NO_ERROR;
1944251881Speter}
1945251881Speter
1946251881Speter
1947251881Speter/*** File locking. ***/
1948251881Speter#if !defined(WIN32) && !defined(__OS2__)
1949251881Speter/* Clear all outstanding locks on ARG, an open apr_file_t *. */
1950251881Speterstatic apr_status_t
1951251881Speterfile_clear_locks(void *arg)
1952251881Speter{
1953251881Speter  apr_status_t apr_err;
1954251881Speter  apr_file_t *f = arg;
1955251881Speter
1956251881Speter  /* Remove locks. */
1957251881Speter  apr_err = apr_file_unlock(f);
1958251881Speter  if (apr_err)
1959251881Speter    return apr_err;
1960251881Speter
1961251881Speter  return 0;
1962251881Speter}
1963251881Speter#endif
1964251881Speter
1965251881Spetersvn_error_t *
1966251881Spetersvn_io_lock_open_file(apr_file_t *lockfile_handle,
1967251881Speter                      svn_boolean_t exclusive,
1968251881Speter                      svn_boolean_t nonblocking,
1969251881Speter                      apr_pool_t *pool)
1970251881Speter{
1971251881Speter  int locktype = APR_FLOCK_SHARED;
1972251881Speter  apr_status_t apr_err;
1973251881Speter  const char *fname;
1974251881Speter
1975251881Speter  if (exclusive)
1976251881Speter    locktype = APR_FLOCK_EXCLUSIVE;
1977251881Speter  if (nonblocking)
1978251881Speter    locktype |= APR_FLOCK_NONBLOCK;
1979251881Speter
1980251881Speter  /* We need this only in case of an error but this is cheap to get -
1981251881Speter   * so we do it here for clarity. */
1982251881Speter  apr_err = apr_file_name_get(&fname, lockfile_handle);
1983251881Speter  if (apr_err)
1984251881Speter    return svn_error_wrap_apr(apr_err, _("Can't get file name"));
1985251881Speter
1986251881Speter  /* Get lock on the filehandle. */
1987251881Speter  apr_err = apr_file_lock(lockfile_handle, locktype);
1988251881Speter
1989251881Speter  /* In deployments with two or more multithreaded servers running on
1990251881Speter     the same system serving two or more fsfs repositories it is
1991251881Speter     possible for a deadlock to occur when getting a write lock on
1992251881Speter     db/txn-current-lock:
1993251881Speter
1994251881Speter     Process 1                         Process 2
1995251881Speter     ---------                         ---------
1996251881Speter     thread 1: get lock in repos A
1997251881Speter                                       thread 1: get lock in repos B
1998251881Speter                                       thread 2: block getting lock in repos A
1999251881Speter     thread 2: try to get lock in B *** deadlock ***
2000251881Speter
2001251881Speter     Retry for a while for the deadlock to clear. */
2002251881Speter  FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype));
2003251881Speter
2004251881Speter  if (apr_err)
2005251881Speter    {
2006251881Speter      switch (locktype & APR_FLOCK_TYPEMASK)
2007251881Speter        {
2008251881Speter        case APR_FLOCK_SHARED:
2009251881Speter          return svn_error_wrap_apr(apr_err,
2010251881Speter                                    _("Can't get shared lock on file '%s'"),
2011251881Speter                                    try_utf8_from_internal_style(fname, pool));
2012251881Speter        case APR_FLOCK_EXCLUSIVE:
2013251881Speter          return svn_error_wrap_apr(apr_err,
2014251881Speter                                    _("Can't get exclusive lock on file '%s'"),
2015251881Speter                                    try_utf8_from_internal_style(fname, pool));
2016251881Speter        default:
2017251881Speter          SVN_ERR_MALFUNCTION();
2018251881Speter        }
2019251881Speter    }
2020251881Speter
2021251881Speter/* On Windows and OS/2 file locks are automatically released when
2022251881Speter   the file handle closes */
2023251881Speter#if !defined(WIN32) && !defined(__OS2__)
2024251881Speter  apr_pool_cleanup_register(pool, lockfile_handle,
2025251881Speter                            file_clear_locks,
2026251881Speter                            apr_pool_cleanup_null);
2027251881Speter#endif
2028251881Speter
2029251881Speter  return SVN_NO_ERROR;
2030251881Speter}
2031251881Speter
2032251881Spetersvn_error_t *
2033251881Spetersvn_io_unlock_open_file(apr_file_t *lockfile_handle,
2034251881Speter                        apr_pool_t *pool)
2035251881Speter{
2036251881Speter  const char *fname;
2037251881Speter  apr_status_t apr_err;
2038251881Speter
2039251881Speter  /* We need this only in case of an error but this is cheap to get -
2040251881Speter   * so we do it here for clarity. */
2041251881Speter  apr_err = apr_file_name_get(&fname, lockfile_handle);
2042251881Speter  if (apr_err)
2043251881Speter    return svn_error_wrap_apr(apr_err, _("Can't get file name"));
2044251881Speter
2045251881Speter  /* The actual unlock attempt. */
2046251881Speter  apr_err = apr_file_unlock(lockfile_handle);
2047251881Speter  if (apr_err)
2048251881Speter    return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"),
2049251881Speter                              try_utf8_from_internal_style(fname, pool));
2050251881Speter
2051251881Speter/* On Windows and OS/2 file locks are automatically released when
2052251881Speter   the file handle closes */
2053251881Speter#if !defined(WIN32) && !defined(__OS2__)
2054251881Speter  apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks);
2055251881Speter#endif
2056251881Speter
2057251881Speter  return SVN_NO_ERROR;
2058251881Speter}
2059251881Speter
2060251881Spetersvn_error_t *
2061251881Spetersvn_io_file_lock2(const char *lock_file,
2062251881Speter                  svn_boolean_t exclusive,
2063251881Speter                  svn_boolean_t nonblocking,
2064251881Speter                  apr_pool_t *pool)
2065251881Speter{
2066251881Speter  int locktype = APR_FLOCK_SHARED;
2067251881Speter  apr_file_t *lockfile_handle;
2068251881Speter  apr_int32_t flags;
2069251881Speter
2070251881Speter  if (exclusive)
2071251881Speter    locktype = APR_FLOCK_EXCLUSIVE;
2072251881Speter
2073251881Speter  flags = APR_READ;
2074251881Speter  if (locktype == APR_FLOCK_EXCLUSIVE)
2075251881Speter    flags |= APR_WRITE;
2076251881Speter
2077251881Speter  /* locktype is never read after this block, so we don't need to bother
2078251881Speter     setting it.  If that were to ever change, uncomment the following
2079251881Speter     block.
2080251881Speter  if (nonblocking)
2081251881Speter    locktype |= APR_FLOCK_NONBLOCK;
2082251881Speter  */
2083251881Speter
2084251881Speter  SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags,
2085251881Speter                           APR_OS_DEFAULT,
2086251881Speter                           pool));
2087251881Speter
2088251881Speter  /* Get lock on the filehandle. */
2089251881Speter  return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool);
2090251881Speter}
2091251881Speter
2092251881Speter
2093251881Speter
2094251881Speter/* Data consistency/coherency operations. */
2095251881Speter
2096251881Spetersvn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
2097251881Speter                                       apr_pool_t *pool)
2098251881Speter{
2099251881Speter  apr_os_file_t filehand;
2100251881Speter
2101251881Speter  /* First make sure that any user-space buffered data is flushed. */
2102251881Speter  SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
2103251881Speter                                     N_("Can't flush file '%s'"),
2104251881Speter                                     N_("Can't flush stream"),
2105251881Speter                                     pool));
2106251881Speter
2107251881Speter  apr_os_file_get(&filehand, file);
2108251881Speter
2109251881Speter  /* Call the operating system specific function to actually force the
2110251881Speter     data to disk. */
2111251881Speter  {
2112251881Speter#ifdef WIN32
2113251881Speter
2114251881Speter    if (! FlushFileBuffers(filehand))
2115251881Speter        return svn_error_wrap_apr(apr_get_os_error(),
2116251881Speter                                  _("Can't flush file to disk"));
2117251881Speter
2118251881Speter#else
2119251881Speter      int rv;
2120251881Speter
2121251881Speter      do {
2122251881Speter        rv = fsync(filehand);
2123251881Speter      } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
2124251881Speter
2125251881Speter      /* If the file is in a memory filesystem, fsync() may return
2126251881Speter         EINVAL.  Presumably the user knows the risks, and we can just
2127251881Speter         ignore the error. */
2128251881Speter      if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error()))
2129251881Speter        return SVN_NO_ERROR;
2130251881Speter
2131251881Speter      if (rv == -1)
2132251881Speter        return svn_error_wrap_apr(apr_get_os_error(),
2133251881Speter                                  _("Can't flush file to disk"));
2134251881Speter
2135251881Speter#endif
2136251881Speter  }
2137251881Speter  return SVN_NO_ERROR;
2138251881Speter}
2139251881Speter
2140251881Speter
2141251881Speter
2142251881Speter/* TODO write test for these two functions, then refactor. */
2143251881Speter
2144251881Speter/* Set RESULT to an svn_stringbuf_t containing the contents of FILE.
2145251881Speter   FILENAME is the FILE's on-disk APR-safe name, or NULL if that name
2146251881Speter   isn't known.  If CHECK_SIZE is TRUE, the function will attempt to
2147251881Speter   first stat() the file to determine it's size before sucking its
2148251881Speter   contents into the stringbuf.  (Doing so can prevent unnecessary
2149251881Speter   memory usage, an unwanted side effect of the stringbuf growth and
2150251881Speter   reallocation mechanism.)  */
2151251881Speterstatic svn_error_t *
2152251881Speterstringbuf_from_aprfile(svn_stringbuf_t **result,
2153251881Speter                       const char *filename,
2154251881Speter                       apr_file_t *file,
2155251881Speter                       svn_boolean_t check_size,
2156251881Speter                       apr_pool_t *pool)
2157251881Speter{
2158251881Speter  apr_size_t len;
2159251881Speter  svn_error_t *err;
2160251881Speter  svn_stringbuf_t *res = NULL;
2161251881Speter  apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE;
2162251881Speter  char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
2163251881Speter
2164251881Speter  /* If our caller wants us to check the size of the file for
2165251881Speter     efficient memory handling, we'll try to do so. */
2166251881Speter  if (check_size)
2167251881Speter    {
2168251881Speter      apr_status_t status;
2169251881Speter
2170251881Speter      /* If our caller didn't tell us the file's name, we'll ask APR
2171251881Speter         if it knows the name.  No problem if we can't figure it out.  */
2172251881Speter      if (! filename)
2173251881Speter        {
2174251881Speter          const char *filename_apr;
2175251881Speter          if (! (status = apr_file_name_get(&filename_apr, file)))
2176251881Speter            filename = filename_apr;
2177251881Speter        }
2178251881Speter
2179251881Speter      /* If we now know the filename, try to stat().  If we succeed,
2180251881Speter         we know how to allocate our stringbuf.  */
2181251881Speter      if (filename)
2182251881Speter        {
2183251881Speter          apr_finfo_t finfo;
2184251881Speter          if (! (status = apr_stat(&finfo, filename, APR_FINFO_MIN, pool)))
2185251881Speter            res_initial_len = (apr_size_t)finfo.size;
2186251881Speter        }
2187251881Speter    }
2188251881Speter
2189251881Speter
2190251881Speter  /* XXX: We should check the incoming data for being of type binary. */
2191251881Speter
2192251881Speter  res = svn_stringbuf_create_ensure(res_initial_len, pool);
2193251881Speter
2194251881Speter  /* apr_file_read will not return data and eof in the same call. So this loop
2195251881Speter   * is safe from missing read data.  */
2196251881Speter  len = SVN__STREAM_CHUNK_SIZE;
2197251881Speter  err = svn_io_file_read(file, buf, &len, pool);
2198251881Speter  while (! err)
2199251881Speter    {
2200251881Speter      svn_stringbuf_appendbytes(res, buf, len);
2201251881Speter      len = SVN__STREAM_CHUNK_SIZE;
2202251881Speter      err = svn_io_file_read(file, buf, &len, pool);
2203251881Speter    }
2204251881Speter
2205251881Speter  /* Having read all the data we *expect* EOF */
2206251881Speter  if (err && !APR_STATUS_IS_EOF(err->apr_err))
2207251881Speter    return err;
2208251881Speter  svn_error_clear(err);
2209251881Speter
2210251881Speter  *result = res;
2211251881Speter  return SVN_NO_ERROR;
2212251881Speter}
2213251881Speter
2214251881Spetersvn_error_t *
2215251881Spetersvn_stringbuf_from_file2(svn_stringbuf_t **result,
2216251881Speter                         const char *filename,
2217251881Speter                         apr_pool_t *pool)
2218251881Speter{
2219251881Speter  apr_file_t *f;
2220251881Speter
2221251881Speter  if (filename[0] == '-' && filename[1] == '\0')
2222251881Speter    {
2223251881Speter      apr_status_t apr_err;
2224251881Speter      if ((apr_err = apr_file_open_stdin(&f, pool)))
2225251881Speter        return svn_error_wrap_apr(apr_err, _("Can't open stdin"));
2226251881Speter      SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool));
2227251881Speter    }
2228251881Speter  else
2229251881Speter    {
2230251881Speter      SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool));
2231251881Speter      SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool));
2232251881Speter    }
2233251881Speter  return svn_io_file_close(f, pool);
2234251881Speter}
2235251881Speter
2236251881Speter
2237251881Spetersvn_error_t *
2238251881Spetersvn_stringbuf_from_file(svn_stringbuf_t **result,
2239251881Speter                        const char *filename,
2240251881Speter                        apr_pool_t *pool)
2241251881Speter{
2242251881Speter  if (filename[0] == '-' && filename[1] == '\0')
2243251881Speter    return svn_error_create
2244251881Speter        (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2245251881Speter         _("Reading from stdin is disallowed"));
2246251881Speter  return svn_stringbuf_from_file2(result, filename, pool);
2247251881Speter}
2248251881Speter
2249251881Spetersvn_error_t *
2250251881Spetersvn_stringbuf_from_aprfile(svn_stringbuf_t **result,
2251251881Speter                           apr_file_t *file,
2252251881Speter                           apr_pool_t *pool)
2253251881Speter{
2254251881Speter  return stringbuf_from_aprfile(result, NULL, file, TRUE, pool);
2255251881Speter}
2256251881Speter
2257251881Speter
2258251881Speter
2259251881Speter/* Deletion. */
2260251881Speter
2261251881Spetersvn_error_t *
2262251881Spetersvn_io_remove_file2(const char *path,
2263251881Speter                    svn_boolean_t ignore_enoent,
2264251881Speter                    apr_pool_t *scratch_pool)
2265251881Speter{
2266251881Speter  apr_status_t apr_err;
2267251881Speter  const char *path_apr;
2268251881Speter
2269251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool));
2270251881Speter
2271251881Speter  apr_err = apr_file_remove(path_apr, scratch_pool);
2272251881Speter  if (!apr_err
2273251881Speter      || (ignore_enoent
2274251881Speter          && (APR_STATUS_IS_ENOENT(apr_err)
2275251881Speter              || SVN__APR_STATUS_IS_ENOTDIR(apr_err))))
2276251881Speter    return SVN_NO_ERROR;
2277251881Speter
2278251881Speter#ifdef WIN32
2279251881Speter  /* If the target is read only NTFS reports EACCESS and FAT/FAT32
2280251881Speter     reports EEXIST */
2281251881Speter  if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err))
2282251881Speter    {
2283251881Speter      /* Set the destination file writable because Windows will not
2284251881Speter         allow us to delete when path is read-only */
2285251881Speter      SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool));
2286251881Speter      apr_err = apr_file_remove(path_apr, scratch_pool);
2287251881Speter
2288251881Speter      if (!apr_err)
2289251881Speter        return SVN_NO_ERROR;
2290251881Speter    }
2291251881Speter
2292251881Speter    {
2293251881Speter      apr_status_t os_err = APR_TO_OS_ERROR(apr_err);
2294251881Speter      /* Check to make sure we aren't trying to delete a directory */
2295251881Speter      if (os_err == ERROR_ACCESS_DENIED || os_err == ERROR_SHARING_VIOLATION)
2296251881Speter        {
2297251881Speter          apr_finfo_t finfo;
2298251881Speter
2299251881Speter          if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool)
2300251881Speter              && finfo.filetype == APR_REG)
2301251881Speter            {
2302251881Speter              WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr,
2303251881Speter                                                        scratch_pool));
2304251881Speter            }
2305251881Speter        }
2306251881Speter
2307251881Speter      /* Just return the delete error */
2308251881Speter    }
2309251881Speter#endif
2310251881Speter
2311251881Speter  if (apr_err)
2312251881Speter    return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"),
2313251881Speter                              svn_dirent_local_style(path, scratch_pool));
2314251881Speter
2315251881Speter  return SVN_NO_ERROR;
2316251881Speter}
2317251881Speter
2318251881Speter
2319251881Spetersvn_error_t *
2320251881Spetersvn_io_remove_dir(const char *path, apr_pool_t *pool)
2321251881Speter{
2322251881Speter  return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
2323251881Speter}
2324251881Speter
2325251881Speter/*
2326251881Speter Mac OS X has a bug where if you're reading the contents of a
2327251881Speter directory via readdir in a loop, and you remove one of the entries in
2328251881Speter the directory and the directory has 338 or more files in it you will
2329251881Speter skip over some of the entries in the directory.  Needless to say,
2330251881Speter this causes problems if you are using this kind of loop inside a
2331251881Speter function that is recursively deleting a directory, because when you
2332251881Speter get around to removing the directory it will still have something in
2333251881Speter it. A similar problem has been observed in other BSDs. This bug has
2334251881Speter since been fixed. See http://www.vnode.ch/fixing_seekdir for details.
2335251881Speter
2336251881Speter The workaround is to delete the files only _after_ the initial
2337251881Speter directory scan.  A previous workaround involving rewinddir is
2338251881Speter problematic on Win32 and some NFS clients, notably NetBSD.
2339251881Speter
2340251881Speter See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and
2341251881Speter http://subversion.tigris.org/issues/show_bug.cgi?id=3501.
2342251881Speter*/
2343251881Speter
2344251881Speter/* Neither windows nor unix allows us to delete a non-empty
2345251881Speter   directory.
2346251881Speter
2347251881Speter   This is a function to perform the equivalent of 'rm -rf'. */
2348251881Spetersvn_error_t *
2349251881Spetersvn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
2350251881Speter                   svn_cancel_func_t cancel_func, void *cancel_baton,
2351251881Speter                   apr_pool_t *pool)
2352251881Speter{
2353251881Speter  svn_error_t *err;
2354251881Speter  apr_pool_t *subpool;
2355251881Speter  apr_hash_t *dirents;
2356251881Speter  apr_hash_index_t *hi;
2357251881Speter
2358251881Speter  /* Check for pending cancellation request.
2359251881Speter     If we need to bail out, do so early. */
2360251881Speter
2361251881Speter  if (cancel_func)
2362251881Speter    SVN_ERR((*cancel_func)(cancel_baton));
2363251881Speter
2364251881Speter  subpool = svn_pool_create(pool);
2365251881Speter
2366251881Speter  err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool);
2367251881Speter  if (err)
2368251881Speter    {
2369251881Speter      /* if the directory doesn't exist, our mission is accomplished */
2370251881Speter      if (ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err))
2371251881Speter        {
2372251881Speter          svn_error_clear(err);
2373251881Speter          return SVN_NO_ERROR;
2374251881Speter        }
2375251881Speter      return svn_error_trace(err);
2376251881Speter    }
2377251881Speter
2378251881Speter  for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
2379251881Speter    {
2380251881Speter      const char *name = svn__apr_hash_index_key(hi);
2381251881Speter      const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
2382251881Speter      const char *fullpath;
2383251881Speter
2384251881Speter      fullpath = svn_dirent_join(path, name, subpool);
2385251881Speter      if (dirent->kind == svn_node_dir)
2386251881Speter        {
2387251881Speter          /* Don't check for cancellation, the callee will immediately do so */
2388251881Speter          SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func,
2389251881Speter                                     cancel_baton, subpool));
2390251881Speter        }
2391251881Speter      else
2392251881Speter        {
2393251881Speter          if (cancel_func)
2394251881Speter            SVN_ERR((*cancel_func)(cancel_baton));
2395251881Speter
2396251881Speter          err = svn_io_remove_file2(fullpath, FALSE, subpool);
2397251881Speter          if (err)
2398251881Speter            return svn_error_createf
2399251881Speter              (err->apr_err, err, _("Can't remove '%s'"),
2400251881Speter               svn_dirent_local_style(fullpath, subpool));
2401251881Speter        }
2402251881Speter    }
2403251881Speter
2404251881Speter  svn_pool_destroy(subpool);
2405251881Speter
2406251881Speter  return svn_io_dir_remove_nonrecursive(path, pool);
2407251881Speter}
2408251881Speter
2409251881Spetersvn_error_t *
2410251881Spetersvn_io_get_dir_filenames(apr_hash_t **dirents,
2411251881Speter                         const char *path,
2412251881Speter                         apr_pool_t *pool)
2413251881Speter{
2414251881Speter  return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE,
2415251881Speter                                             pool, pool));
2416251881Speter}
2417251881Speter
2418251881Spetersvn_io_dirent2_t *
2419251881Spetersvn_io_dirent2_create(apr_pool_t *result_pool)
2420251881Speter{
2421251881Speter  svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent));
2422251881Speter
2423251881Speter  /*dirent->kind = svn_node_none;
2424251881Speter  dirent->special = FALSE;*/
2425251881Speter  dirent->filesize = SVN_INVALID_FILESIZE;
2426251881Speter  /*dirent->mtime = 0;*/
2427251881Speter
2428251881Speter  return dirent;
2429251881Speter}
2430251881Speter
2431251881Spetersvn_io_dirent2_t *
2432251881Spetersvn_io_dirent2_dup(const svn_io_dirent2_t *item,
2433251881Speter                   apr_pool_t *result_pool)
2434251881Speter{
2435251881Speter  return apr_pmemdup(result_pool,
2436251881Speter                     item,
2437251881Speter                     sizeof(*item));
2438251881Speter}
2439251881Speter
2440251881Spetersvn_error_t *
2441251881Spetersvn_io_get_dirents3(apr_hash_t **dirents,
2442251881Speter                    const char *path,
2443251881Speter                    svn_boolean_t only_check_type,
2444251881Speter                    apr_pool_t *result_pool,
2445251881Speter                    apr_pool_t *scratch_pool)
2446251881Speter{
2447251881Speter  apr_status_t status;
2448251881Speter  apr_dir_t *this_dir;
2449251881Speter  apr_finfo_t this_entry;
2450251881Speter  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
2451251881Speter
2452251881Speter  if (!only_check_type)
2453251881Speter    flags |= APR_FINFO_SIZE | APR_FINFO_MTIME;
2454251881Speter
2455251881Speter  *dirents = apr_hash_make(result_pool);
2456251881Speter
2457251881Speter  SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool));
2458251881Speter
2459251881Speter  for (status = apr_dir_read(&this_entry, flags, this_dir);
2460251881Speter       status == APR_SUCCESS;
2461251881Speter       status = apr_dir_read(&this_entry, flags, this_dir))
2462251881Speter    {
2463251881Speter      if ((this_entry.name[0] == '.')
2464251881Speter          && ((this_entry.name[1] == '\0')
2465251881Speter              || ((this_entry.name[1] == '.')
2466251881Speter                  && (this_entry.name[2] == '\0'))))
2467251881Speter        {
2468251881Speter          continue;
2469251881Speter        }
2470251881Speter      else
2471251881Speter        {
2472251881Speter          const char *name;
2473251881Speter          svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool);
2474251881Speter
2475251881Speter          SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool));
2476251881Speter
2477251881Speter          map_apr_finfo_to_node_kind(&(dirent->kind),
2478251881Speter                                     &(dirent->special),
2479251881Speter                                     &this_entry);
2480251881Speter
2481251881Speter          if (!only_check_type)
2482251881Speter            {
2483251881Speter              dirent->filesize = this_entry.size;
2484251881Speter              dirent->mtime = this_entry.mtime;
2485251881Speter            }
2486251881Speter
2487251881Speter          svn_hash_sets(*dirents, name, dirent);
2488251881Speter        }
2489251881Speter    }
2490251881Speter
2491251881Speter  if (! (APR_STATUS_IS_ENOENT(status)))
2492251881Speter    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
2493251881Speter                              svn_dirent_local_style(path, scratch_pool));
2494251881Speter
2495251881Speter  status = apr_dir_close(this_dir);
2496251881Speter  if (status)
2497251881Speter    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
2498251881Speter                              svn_dirent_local_style(path, scratch_pool));
2499251881Speter
2500251881Speter  return SVN_NO_ERROR;
2501251881Speter}
2502251881Speter
2503251881Spetersvn_error_t *
2504251881Spetersvn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p,
2505251881Speter                    const char *path,
2506251881Speter                    svn_boolean_t verify_truename,
2507251881Speter                    svn_boolean_t ignore_enoent,
2508251881Speter                    apr_pool_t *result_pool,
2509251881Speter                    apr_pool_t *scratch_pool)
2510251881Speter{
2511251881Speter  apr_finfo_t finfo;
2512251881Speter  svn_io_dirent2_t *dirent;
2513251881Speter  svn_error_t *err;
2514251881Speter  apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK
2515251881Speter                       | APR_FINFO_SIZE | APR_FINFO_MTIME;
2516251881Speter
2517251881Speter#if defined(WIN32) || defined(__OS2__)
2518251881Speter  if (verify_truename)
2519251881Speter    wanted |= APR_FINFO_NAME;
2520251881Speter#endif
2521251881Speter
2522251881Speter  err = svn_io_stat(&finfo, path, wanted, scratch_pool);
2523251881Speter
2524251881Speter  if (err && ignore_enoent &&
2525251881Speter      (APR_STATUS_IS_ENOENT(err->apr_err)
2526251881Speter       || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
2527251881Speter    {
2528251881Speter      svn_error_clear(err);
2529251881Speter      dirent = svn_io_dirent2_create(result_pool);
2530251881Speter      SVN_ERR_ASSERT(dirent->kind == svn_node_none);
2531251881Speter
2532251881Speter      *dirent_p = dirent;
2533251881Speter      return SVN_NO_ERROR;
2534251881Speter    }
2535251881Speter  SVN_ERR(err);
2536251881Speter
2537251881Speter#if defined(WIN32) || defined(__OS2__) || defined(DARWIN)
2538251881Speter  if (verify_truename)
2539251881Speter    {
2540251881Speter      const char *requested_name = svn_dirent_basename(path, NULL);
2541251881Speter
2542251881Speter      if (requested_name[0] == '\0')
2543251881Speter        {
2544251881Speter          /* No parent directory. No need to stat/verify */
2545251881Speter        }
2546251881Speter#if defined(WIN32) || defined(__OS2__)
2547251881Speter      else if (finfo.name)
2548251881Speter        {
2549251881Speter          const char *name_on_disk;
2550251881Speter          SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path,
2551251881Speter                                     scratch_pool));
2552251881Speter
2553251881Speter          if (strcmp(name_on_disk, requested_name) /* != 0 */)
2554251881Speter            {
2555251881Speter              if (ignore_enoent)
2556251881Speter                {
2557251881Speter                  *dirent_p = svn_io_dirent2_create(result_pool);
2558251881Speter                  return SVN_NO_ERROR;
2559251881Speter                }
2560251881Speter              else
2561251881Speter                return svn_error_createf(APR_ENOENT, NULL,
2562251881Speter                          _("Path '%s' not found, case obstructed by '%s'"),
2563251881Speter                          svn_dirent_local_style(path, scratch_pool),
2564251881Speter                          name_on_disk);
2565251881Speter            }
2566251881Speter        }
2567251881Speter#elif defined(DARWIN)
2568251881Speter      /* Currently apr doesn't set finfo.name on DARWIN, returning
2569251881Speter                   APR_INCOMPLETE.
2570251881Speter         ### Can we optimize this in another way? */
2571251881Speter      else
2572251881Speter        {
2573251881Speter          apr_hash_t *dirents;
2574251881Speter
2575251881Speter          err = svn_io_get_dirents3(&dirents,
2576251881Speter                                    svn_dirent_dirname(path, scratch_pool),
2577251881Speter                                    TRUE /* only_check_type */,
2578251881Speter                                    scratch_pool, scratch_pool);
2579251881Speter
2580251881Speter          if (err && ignore_enoent
2581251881Speter              && (APR_STATUS_IS_ENOENT(err->apr_err)
2582251881Speter                  || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
2583251881Speter            {
2584251881Speter              svn_error_clear(err);
2585251881Speter
2586251881Speter              *dirent_p = svn_io_dirent2_create(result_pool);
2587251881Speter              return SVN_NO_ERROR;
2588251881Speter            }
2589251881Speter          else
2590251881Speter            SVN_ERR(err);
2591251881Speter
2592251881Speter          if (! svn_hash_gets(dirents, requested_name))
2593251881Speter            {
2594251881Speter              if (ignore_enoent)
2595251881Speter                {
2596251881Speter                  *dirent_p = svn_io_dirent2_create(result_pool);
2597251881Speter                  return SVN_NO_ERROR;
2598251881Speter                }
2599251881Speter              else
2600251881Speter                return svn_error_createf(APR_ENOENT, NULL,
2601251881Speter                          _("Path '%s' not found"),
2602251881Speter                          svn_dirent_local_style(path, scratch_pool));
2603251881Speter            }
2604251881Speter        }
2605251881Speter#endif
2606251881Speter    }
2607251881Speter#endif
2608251881Speter
2609251881Speter  dirent = svn_io_dirent2_create(result_pool);
2610251881Speter  map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo);
2611251881Speter
2612251881Speter  dirent->filesize = finfo.size;
2613251881Speter  dirent->mtime = finfo.mtime;
2614251881Speter
2615251881Speter  *dirent_p = dirent;
2616251881Speter
2617251881Speter  return SVN_NO_ERROR;
2618251881Speter}
2619251881Speter
2620251881Speter/* Pool userdata key for the error file passed to svn_io_start_cmd(). */
2621251881Speter#define ERRFILE_KEY "svn-io-start-cmd-errfile"
2622251881Speter
2623251881Speter/* Handle an error from the child process (before command execution) by
2624251881Speter   printing DESC and the error string corresponding to STATUS to stderr. */
2625251881Speterstatic void
2626251881Speterhandle_child_process_error(apr_pool_t *pool, apr_status_t status,
2627251881Speter                           const char *desc)
2628251881Speter{
2629251881Speter  char errbuf[256];
2630251881Speter  apr_file_t *errfile;
2631251881Speter  void *p;
2632251881Speter
2633251881Speter  /* We can't do anything if we get an error here, so just return. */
2634251881Speter  if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool))
2635251881Speter    return;
2636251881Speter  errfile = p;
2637251881Speter
2638251881Speter  if (errfile)
2639251881Speter    /* What we get from APR is in native encoding. */
2640251881Speter    apr_file_printf(errfile, "%s: %s",
2641251881Speter                    desc, apr_strerror(status, errbuf,
2642251881Speter                                       sizeof(errbuf)));
2643251881Speter}
2644251881Speter
2645251881Speter
2646251881Spetersvn_error_t *
2647251881Spetersvn_io_start_cmd3(apr_proc_t *cmd_proc,
2648251881Speter                  const char *path,
2649251881Speter                  const char *cmd,
2650251881Speter                  const char *const *args,
2651251881Speter                  const char *const *env,
2652251881Speter                  svn_boolean_t inherit,
2653251881Speter                  svn_boolean_t infile_pipe,
2654251881Speter                  apr_file_t *infile,
2655251881Speter                  svn_boolean_t outfile_pipe,
2656251881Speter                  apr_file_t *outfile,
2657251881Speter                  svn_boolean_t errfile_pipe,
2658251881Speter                  apr_file_t *errfile,
2659251881Speter                  apr_pool_t *pool)
2660251881Speter{
2661251881Speter  apr_status_t apr_err;
2662251881Speter  apr_procattr_t *cmdproc_attr;
2663251881Speter  int num_args;
2664251881Speter  const char **args_native;
2665251881Speter  const char *cmd_apr;
2666251881Speter
2667251881Speter  SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe));
2668251881Speter  SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe));
2669251881Speter  SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe));
2670251881Speter
2671251881Speter  /* Create the process attributes. */
2672251881Speter  apr_err = apr_procattr_create(&cmdproc_attr, pool);
2673251881Speter  if (apr_err)
2674251881Speter    return svn_error_wrap_apr(apr_err,
2675251881Speter                              _("Can't create process '%s' attributes"),
2676251881Speter                              cmd);
2677251881Speter
2678251881Speter  /* Make sure we invoke cmd directly, not through a shell. */
2679251881Speter  apr_err = apr_procattr_cmdtype_set(cmdproc_attr,
2680251881Speter                                     inherit ? APR_PROGRAM_PATH : APR_PROGRAM);
2681251881Speter  if (apr_err)
2682251881Speter    return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"),
2683251881Speter                              cmd);
2684251881Speter
2685251881Speter  /* Set the process's working directory. */
2686251881Speter  if (path)
2687251881Speter    {
2688251881Speter      const char *path_apr;
2689251881Speter
2690251881Speter      SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
2691251881Speter      apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr);
2692251881Speter      if (apr_err)
2693251881Speter        return svn_error_wrap_apr(apr_err,
2694251881Speter                                  _("Can't set process '%s' directory"),
2695251881Speter                                  cmd);
2696251881Speter    }
2697251881Speter
2698251881Speter  /* Use requested inputs and outputs.
2699251881Speter
2700251881Speter     ### Unfortunately each of these apr functions creates a pipe and then
2701251881Speter     overwrites the pipe file descriptor with the descriptor we pass
2702251881Speter     in. The pipes can then never be closed. This is an APR bug. */
2703251881Speter  if (infile)
2704251881Speter    {
2705251881Speter      apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL);
2706251881Speter      if (apr_err)
2707251881Speter        return svn_error_wrap_apr(apr_err,
2708251881Speter                                  _("Can't set process '%s' child input"),
2709251881Speter                                  cmd);
2710251881Speter    }
2711251881Speter  if (outfile)
2712251881Speter    {
2713251881Speter      apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL);
2714251881Speter      if (apr_err)
2715251881Speter        return svn_error_wrap_apr(apr_err,
2716251881Speter                                  _("Can't set process '%s' child outfile"),
2717251881Speter                                  cmd);
2718251881Speter    }
2719251881Speter  if (errfile)
2720251881Speter    {
2721251881Speter      apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL);
2722251881Speter      if (apr_err)
2723251881Speter        return svn_error_wrap_apr(apr_err,
2724251881Speter                                  _("Can't set process '%s' child errfile"),
2725251881Speter                                  cmd);
2726251881Speter    }
2727251881Speter
2728251881Speter  /* Forward request for pipes to APR. */
2729251881Speter  if (infile_pipe || outfile_pipe || errfile_pipe)
2730251881Speter    {
2731251881Speter      apr_err = apr_procattr_io_set(cmdproc_attr,
2732251881Speter                                    infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
2733251881Speter                                    outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
2734251881Speter                                    errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE);
2735251881Speter
2736251881Speter      if (apr_err)
2737251881Speter        return svn_error_wrap_apr(apr_err,
2738251881Speter                                  _("Can't set process '%s' stdio pipes"),
2739251881Speter                                  cmd);
2740251881Speter    }
2741251881Speter
2742251881Speter  /* Have the child print any problems executing its program to errfile. */
2743251881Speter  apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool);
2744251881Speter  if (apr_err)
2745251881Speter    return svn_error_wrap_apr(apr_err,
2746251881Speter                              _("Can't set process '%s' child errfile for "
2747251881Speter                                "error handler"),
2748251881Speter                              cmd);
2749251881Speter  apr_err = apr_procattr_child_errfn_set(cmdproc_attr,
2750251881Speter                                         handle_child_process_error);
2751251881Speter  if (apr_err)
2752251881Speter    return svn_error_wrap_apr(apr_err,
2753251881Speter                              _("Can't set process '%s' error handler"),
2754251881Speter                              cmd);
2755251881Speter
2756251881Speter  /* Convert cmd and args from UTF-8 */
2757251881Speter  SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool));
2758251881Speter  for (num_args = 0; args[num_args]; num_args++)
2759251881Speter    ;
2760251881Speter  args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *));
2761251881Speter  args_native[num_args] = NULL;
2762251881Speter  while (num_args--)
2763251881Speter    {
2764251881Speter      /* ### Well, it turns out that on APR on Windows expects all
2765251881Speter             program args to be in UTF-8. Callers of svn_io_run_cmd
2766251881Speter             should be aware of that. */
2767251881Speter      SVN_ERR(cstring_from_utf8(&args_native[num_args],
2768251881Speter                                args[num_args], pool));
2769251881Speter    }
2770251881Speter
2771251881Speter
2772251881Speter  /* Start the cmd command. */
2773251881Speter  apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native,
2774251881Speter                            inherit ? NULL : env, cmdproc_attr, pool);
2775251881Speter  if (apr_err)
2776251881Speter    return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd);
2777251881Speter
2778251881Speter  return SVN_NO_ERROR;
2779251881Speter}
2780251881Speter
2781251881Speter#undef ERRFILE_KEY
2782251881Speter
2783251881Spetersvn_error_t *
2784251881Spetersvn_io_wait_for_cmd(apr_proc_t *cmd_proc,
2785251881Speter                    const char *cmd,
2786251881Speter                    int *exitcode,
2787251881Speter                    apr_exit_why_e *exitwhy,
2788251881Speter                    apr_pool_t *pool)
2789251881Speter{
2790251881Speter  apr_status_t apr_err;
2791251881Speter  apr_exit_why_e exitwhy_val;
2792251881Speter  int exitcode_val;
2793251881Speter
2794251881Speter  /* The Win32 apr_proc_wait doesn't set this... */
2795251881Speter  exitwhy_val = APR_PROC_EXIT;
2796251881Speter
2797251881Speter  /* Wait for the cmd command to finish. */
2798251881Speter  apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT);
2799251881Speter  if (!APR_STATUS_IS_CHILD_DONE(apr_err))
2800251881Speter    return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"),
2801251881Speter                              cmd);
2802251881Speter
2803251881Speter  if (exitwhy)
2804251881Speter    *exitwhy = exitwhy_val;
2805251881Speter  else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)
2806251881Speter           && APR_PROC_CHECK_CORE_DUMP(exitwhy_val))
2807251881Speter    return svn_error_createf
2808251881Speter      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2809251881Speter       _("Process '%s' failed (signal %d, core dumped)"),
2810251881Speter       cmd, exitcode_val);
2811251881Speter  else if (APR_PROC_CHECK_SIGNALED(exitwhy_val))
2812251881Speter    return svn_error_createf
2813251881Speter      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2814251881Speter       _("Process '%s' failed (signal %d)"),
2815251881Speter       cmd, exitcode_val);
2816251881Speter  else if (! APR_PROC_CHECK_EXIT(exitwhy_val))
2817251881Speter    /* Don't really know what happened here. */
2818251881Speter    return svn_error_createf
2819251881Speter      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2820251881Speter       _("Process '%s' failed (exitwhy %d, exitcode %d)"),
2821251881Speter       cmd, exitwhy_val, exitcode_val);
2822251881Speter
2823251881Speter  if (exitcode)
2824251881Speter    *exitcode = exitcode_val;
2825251881Speter  else if (exitcode_val != 0)
2826251881Speter    return svn_error_createf
2827251881Speter      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2828251881Speter       _("Process '%s' returned error exitcode %d"), cmd, exitcode_val);
2829251881Speter
2830251881Speter  return SVN_NO_ERROR;
2831251881Speter}
2832251881Speter
2833251881Speter
2834251881Spetersvn_error_t *
2835251881Spetersvn_io_run_cmd(const char *path,
2836251881Speter               const char *cmd,
2837251881Speter               const char *const *args,
2838251881Speter               int *exitcode,
2839251881Speter               apr_exit_why_e *exitwhy,
2840251881Speter               svn_boolean_t inherit,
2841251881Speter               apr_file_t *infile,
2842251881Speter               apr_file_t *outfile,
2843251881Speter               apr_file_t *errfile,
2844251881Speter               apr_pool_t *pool)
2845251881Speter{
2846251881Speter  apr_proc_t cmd_proc;
2847251881Speter
2848251881Speter  SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit,
2849251881Speter                            FALSE, infile, FALSE, outfile, FALSE, errfile,
2850251881Speter                            pool));
2851251881Speter
2852251881Speter  return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool);
2853251881Speter}
2854251881Speter
2855251881Speter
2856251881Spetersvn_error_t *
2857251881Spetersvn_io_run_diff2(const char *dir,
2858251881Speter                 const char *const *user_args,
2859251881Speter                 int num_user_args,
2860251881Speter                 const char *label1,
2861251881Speter                 const char *label2,
2862251881Speter                 const char *from,
2863251881Speter                 const char *to,
2864251881Speter                 int *pexitcode,
2865251881Speter                 apr_file_t *outfile,
2866251881Speter                 apr_file_t *errfile,
2867251881Speter                 const char *diff_cmd,
2868251881Speter                 apr_pool_t *pool)
2869251881Speter{
2870251881Speter  const char **args;
2871251881Speter  int i;
2872251881Speter  int exitcode;
2873251881Speter  int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */
2874251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
2875251881Speter
2876251881Speter  if (pexitcode == NULL)
2877251881Speter    pexitcode = &exitcode;
2878251881Speter
2879251881Speter  if (user_args != NULL)
2880251881Speter    nargs += num_user_args;
2881251881Speter  else
2882251881Speter    nargs += 1; /* -u */
2883251881Speter
2884251881Speter  if (label1 != NULL)
2885251881Speter    nargs += 2; /* the -L and the label itself */
2886251881Speter  if (label2 != NULL)
2887251881Speter    nargs += 2; /* the -L and the label itself */
2888251881Speter
2889251881Speter  args = apr_palloc(subpool, nargs * sizeof(char *));
2890251881Speter
2891251881Speter  i = 0;
2892251881Speter  args[i++] = diff_cmd;
2893251881Speter
2894251881Speter  if (user_args != NULL)
2895251881Speter    {
2896251881Speter      int j;
2897251881Speter      for (j = 0; j < num_user_args; ++j)
2898251881Speter        args[i++] = user_args[j];
2899251881Speter    }
2900251881Speter  else
2901251881Speter    args[i++] = "-u"; /* assume -u if the user didn't give us any args */
2902251881Speter
2903251881Speter  if (label1 != NULL)
2904251881Speter    {
2905251881Speter      args[i++] = "-L";
2906251881Speter      args[i++] = label1;
2907251881Speter    }
2908251881Speter  if (label2 != NULL)
2909251881Speter    {
2910251881Speter      args[i++] = "-L";
2911251881Speter      args[i++] = label2;
2912251881Speter    }
2913251881Speter
2914251881Speter  args[i++] = svn_dirent_local_style(from, subpool);
2915251881Speter  args[i++] = svn_dirent_local_style(to, subpool);
2916251881Speter  args[i++] = NULL;
2917251881Speter
2918251881Speter  SVN_ERR_ASSERT(i == nargs);
2919251881Speter
2920251881Speter  SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE,
2921251881Speter                         NULL, outfile, errfile, subpool));
2922251881Speter
2923251881Speter  /* The man page for (GNU) diff describes the return value as:
2924251881Speter
2925251881Speter       "An exit status of 0 means no differences were found, 1 means
2926251881Speter        some differences were found, and 2 means trouble."
2927251881Speter
2928251881Speter     A return value of 2 typically occurs when diff cannot read its input
2929251881Speter     or write to its output, but in any case we probably ought to return an
2930251881Speter     error for anything other than 0 or 1 as the output is likely to be
2931251881Speter     corrupt.
2932251881Speter   */
2933251881Speter  if (*pexitcode != 0 && *pexitcode != 1)
2934251881Speter    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
2935251881Speter                             _("'%s' returned %d"),
2936251881Speter                             svn_dirent_local_style(diff_cmd, pool),
2937251881Speter                             *pexitcode);
2938251881Speter
2939251881Speter  svn_pool_destroy(subpool);
2940251881Speter
2941251881Speter  return SVN_NO_ERROR;
2942251881Speter}
2943251881Speter
2944251881Speter
2945251881Spetersvn_error_t *
2946251881Spetersvn_io_run_diff3_3(int *exitcode,
2947251881Speter                   const char *dir,
2948251881Speter                   const char *mine,
2949251881Speter                   const char *older,
2950251881Speter                   const char *yours,
2951251881Speter                   const char *mine_label,
2952251881Speter                   const char *older_label,
2953251881Speter                   const char *yours_label,
2954251881Speter                   apr_file_t *merged,
2955251881Speter                   const char *diff3_cmd,
2956251881Speter                   const apr_array_header_t *user_args,
2957251881Speter                   apr_pool_t *pool)
2958251881Speter{
2959251881Speter  const char **args = apr_palloc(pool,
2960251881Speter                                 sizeof(char*) * (13
2961251881Speter                                                  + (user_args
2962251881Speter                                                     ? user_args->nelts
2963251881Speter                                                     : 1)));
2964251881Speter#ifndef NDEBUG
2965251881Speter  int nargs = 12;
2966251881Speter#endif
2967251881Speter  int i = 0;
2968251881Speter
2969251881Speter  /* Labels fall back to sensible defaults if not specified. */
2970251881Speter  if (mine_label == NULL)
2971251881Speter    mine_label = ".working";
2972251881Speter  if (older_label == NULL)
2973251881Speter    older_label = ".old";
2974251881Speter  if (yours_label == NULL)
2975251881Speter    yours_label = ".new";
2976251881Speter
2977251881Speter  /* Set up diff3 command line. */
2978251881Speter  args[i++] = diff3_cmd;
2979251881Speter  if (user_args)
2980251881Speter    {
2981251881Speter      int j;
2982251881Speter      for (j = 0; j < user_args->nelts; ++j)
2983251881Speter        args[i++] = APR_ARRAY_IDX(user_args, j, const char *);
2984251881Speter#ifndef NDEBUG
2985251881Speter      nargs += user_args->nelts;
2986251881Speter#endif
2987251881Speter    }
2988251881Speter  else
2989251881Speter    {
2990251881Speter      args[i++] = "-E";             /* We tried "-A" here, but that caused
2991251881Speter                                       overlapping identical changes to
2992251881Speter                                       conflict.  See issue #682. */
2993251881Speter#ifndef NDEBUG
2994251881Speter      ++nargs;
2995251881Speter#endif
2996251881Speter    }
2997251881Speter  args[i++] = "-m";
2998251881Speter  args[i++] = "-L";
2999251881Speter  args[i++] = mine_label;
3000251881Speter  args[i++] = "-L";
3001251881Speter  args[i++] = older_label;      /* note:  this label is ignored if
3002251881Speter                                   using 2-part markers, which is the
3003251881Speter                                   case with "-E". */
3004251881Speter  args[i++] = "-L";
3005251881Speter  args[i++] = yours_label;
3006251881Speter#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG
3007251881Speter  {
3008251881Speter    svn_boolean_t has_arg;
3009251881Speter
3010251881Speter    /* ### FIXME: we really shouldn't be reading the config here;
3011251881Speter       instead, the necessary bits should be passed in by the caller.
3012251881Speter       But should we add another parameter to this function, when the
3013251881Speter       whole external diff3 thing might eventually go away?  */
3014251881Speter    apr_hash_t *config;
3015251881Speter    svn_config_t *cfg;
3016251881Speter
3017251881Speter    SVN_ERR(svn_config_get_config(&config, pool));
3018251881Speter    cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
3019251881Speter    SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS,
3020251881Speter                                SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG,
3021251881Speter                                TRUE));
3022251881Speter    if (has_arg)
3023251881Speter      {
3024251881Speter        const char *diff_cmd, *diff_utf8;
3025251881Speter        svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
3026251881Speter                       SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF);
3027251881Speter        SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool));
3028251881Speter        args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL);
3029251881Speter#ifndef NDEBUG
3030251881Speter        ++nargs;
3031251881Speter#endif
3032251881Speter      }
3033251881Speter  }
3034251881Speter#endif
3035251881Speter  args[i++] = svn_dirent_local_style(mine, pool);
3036251881Speter  args[i++] = svn_dirent_local_style(older, pool);
3037251881Speter  args[i++] = svn_dirent_local_style(yours, pool);
3038251881Speter  args[i++] = NULL;
3039251881Speter#ifndef NDEBUG
3040251881Speter  SVN_ERR_ASSERT(i == nargs);
3041251881Speter#endif
3042251881Speter
3043251881Speter  /* Run diff3, output the merged text into the scratch file. */
3044251881Speter  SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args,
3045251881Speter                         exitcode, NULL,
3046251881Speter                         TRUE, /* keep environment */
3047251881Speter                         NULL, merged, NULL,
3048251881Speter                         pool));
3049251881Speter
3050251881Speter  /* According to the diff3 docs, a '0' means the merge was clean, and
3051251881Speter     '1' means conflict markers were found.  Anything else is real
3052251881Speter     error. */
3053251881Speter  if ((*exitcode != 0) && (*exitcode != 1))
3054251881Speter    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
3055251881Speter                             _("Error running '%s':  exitcode was %d, "
3056251881Speter                               "args were:"
3057251881Speter                               "\nin directory '%s', basenames:\n%s\n%s\n%s"),
3058251881Speter                             svn_dirent_local_style(diff3_cmd, pool),
3059251881Speter                             *exitcode,
3060251881Speter                             svn_dirent_local_style(dir, pool),
3061251881Speter                             /* Don't call svn_path_local_style() on
3062251881Speter                                the basenames.  We don't want them to
3063251881Speter                                be absolute, and we don't need the
3064251881Speter                                separator conversion. */
3065251881Speter                             mine, older, yours);
3066251881Speter
3067251881Speter  return SVN_NO_ERROR;
3068251881Speter}
3069251881Speter
3070251881Speter
3071251881Speter/* Canonicalize a string for hashing.  Modifies KEY in place. */
3072251881Speterstatic APR_INLINE char *
3073251881Speterfileext_tolower(char *key)
3074251881Speter{
3075251881Speter  register char *p;
3076251881Speter  for (p = key; *p != 0; ++p)
3077251881Speter    *p = (char)apr_tolower(*p);
3078251881Speter  return key;
3079251881Speter}
3080251881Speter
3081251881Speter
3082251881Spetersvn_error_t *
3083251881Spetersvn_io_parse_mimetypes_file(apr_hash_t **type_map,
3084251881Speter                            const char *mimetypes_file,
3085251881Speter                            apr_pool_t *pool)
3086251881Speter{
3087251881Speter  svn_error_t *err = SVN_NO_ERROR;
3088251881Speter  apr_hash_t *types = apr_hash_make(pool);
3089251881Speter  svn_boolean_t eof = FALSE;
3090251881Speter  svn_stringbuf_t *buf;
3091251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
3092251881Speter  apr_file_t *types_file;
3093251881Speter  svn_stream_t *mimetypes_stream;
3094251881Speter
3095251881Speter  SVN_ERR(svn_io_file_open(&types_file, mimetypes_file,
3096251881Speter                           APR_READ, APR_OS_DEFAULT, pool));
3097251881Speter  mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool);
3098251881Speter
3099251881Speter  while (1)
3100251881Speter    {
3101251881Speter      apr_array_header_t *tokens;
3102251881Speter      const char *type;
3103251881Speter
3104251881Speter      svn_pool_clear(subpool);
3105251881Speter
3106251881Speter      /* Read a line. */
3107251881Speter      if ((err = svn_stream_readline(mimetypes_stream, &buf,
3108251881Speter                                     APR_EOL_STR, &eof, subpool)))
3109251881Speter        break;
3110251881Speter
3111251881Speter      /* Only pay attention to non-empty, non-comment lines. */
3112251881Speter      if (buf->len)
3113251881Speter        {
3114251881Speter          int i;
3115251881Speter
3116251881Speter          if (buf->data[0] == '#')
3117251881Speter            continue;
3118251881Speter
3119251881Speter          /* Tokenize (into our return pool). */
3120251881Speter          tokens = svn_cstring_split(buf->data, " \t", TRUE, pool);
3121251881Speter          if (tokens->nelts < 2)
3122251881Speter            continue;
3123251881Speter
3124251881Speter          /* The first token in a multi-token line is the media type.
3125251881Speter             Subsequent tokens are filename extensions associated with
3126251881Speter             that media type. */
3127251881Speter          type = APR_ARRAY_IDX(tokens, 0, const char *);
3128251881Speter          for (i = 1; i < tokens->nelts; i++)
3129251881Speter            {
3130251881Speter              /* We can safely address 'ext' as a non-const string because
3131251881Speter               * we know svn_cstring_split() allocated it in 'pool' for us. */
3132251881Speter              char *ext = APR_ARRAY_IDX(tokens, i, char *);
3133251881Speter              fileext_tolower(ext);
3134251881Speter              svn_hash_sets(types, ext, type);
3135251881Speter            }
3136251881Speter        }
3137251881Speter      if (eof)
3138251881Speter        break;
3139251881Speter    }
3140251881Speter  svn_pool_destroy(subpool);
3141251881Speter
3142251881Speter  /* If there was an error above, close the file (ignoring any error
3143251881Speter     from *that*) and return the originally error. */
3144251881Speter  if (err)
3145251881Speter    {
3146251881Speter      svn_error_clear(svn_stream_close(mimetypes_stream));
3147251881Speter      return err;
3148251881Speter    }
3149251881Speter
3150251881Speter  /* Close the stream (which closes the underlying file, too). */
3151251881Speter  SVN_ERR(svn_stream_close(mimetypes_stream));
3152251881Speter
3153251881Speter  *type_map = types;
3154251881Speter  return SVN_NO_ERROR;
3155251881Speter}
3156251881Speter
3157251881Speter
3158251881Spetersvn_error_t *
3159251881Spetersvn_io_detect_mimetype2(const char **mimetype,
3160251881Speter                        const char *file,
3161251881Speter                        apr_hash_t *mimetype_map,
3162251881Speter                        apr_pool_t *pool)
3163251881Speter{
3164251881Speter  static const char * const generic_binary = "application/octet-stream";
3165251881Speter
3166251881Speter  svn_node_kind_t kind;
3167251881Speter  apr_file_t *fh;
3168251881Speter  svn_error_t *err;
3169251881Speter  unsigned char block[1024];
3170251881Speter  apr_size_t amt_read = sizeof(block);
3171251881Speter
3172251881Speter  /* Default return value is NULL. */
3173251881Speter  *mimetype = NULL;
3174251881Speter
3175251881Speter  /* If there is a mimetype_map provided, we'll first try to look up
3176251881Speter     our file's extension in the map.  Failing that, we'll run the
3177251881Speter     heuristic. */
3178251881Speter  if (mimetype_map)
3179251881Speter    {
3180251881Speter      const char *type_from_map;
3181251881Speter      char *path_ext; /* Can point to physical const memory but only when
3182251881Speter                         svn_path_splitext sets it to "". */
3183251881Speter      svn_path_splitext(NULL, (const char **)&path_ext, file, pool);
3184251881Speter      fileext_tolower(path_ext);
3185251881Speter      if ((type_from_map = svn_hash_gets(mimetype_map, path_ext)))
3186251881Speter        {
3187251881Speter          *mimetype = type_from_map;
3188251881Speter          return SVN_NO_ERROR;
3189251881Speter        }
3190251881Speter    }
3191251881Speter
3192251881Speter  /* See if this file even exists, and make sure it really is a file. */
3193251881Speter  SVN_ERR(svn_io_check_path(file, &kind, pool));
3194251881Speter  if (kind != svn_node_file)
3195251881Speter    return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
3196251881Speter                             _("Can't detect MIME type of non-file '%s'"),
3197251881Speter                             svn_dirent_local_style(file, pool));
3198251881Speter
3199251881Speter  SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool));
3200251881Speter
3201251881Speter  /* Read a block of data from FILE. */
3202251881Speter  err = svn_io_file_read(fh, block, &amt_read, pool);
3203251881Speter  if (err && ! APR_STATUS_IS_EOF(err->apr_err))
3204251881Speter    return err;
3205251881Speter  svn_error_clear(err);
3206251881Speter
3207251881Speter  /* Now close the file.  No use keeping it open any more.  */
3208251881Speter  SVN_ERR(svn_io_file_close(fh, pool));
3209251881Speter
3210251881Speter  if (svn_io_is_binary_data(block, amt_read))
3211251881Speter    *mimetype = generic_binary;
3212251881Speter
3213251881Speter  return SVN_NO_ERROR;
3214251881Speter}
3215251881Speter
3216251881Speter
3217251881Spetersvn_boolean_t
3218251881Spetersvn_io_is_binary_data(const void *data, apr_size_t len)
3219251881Speter{
3220251881Speter  const unsigned char *buf = data;
3221251881Speter
3222251881Speter  if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF)
3223251881Speter    {
3224251881Speter      /* This is an empty UTF-8 file which only contains the UTF-8 BOM.
3225251881Speter       * Treat it as plain text. */
3226251881Speter      return FALSE;
3227251881Speter    }
3228251881Speter
3229251881Speter  /* Right now, this function is going to be really stupid.  It's
3230251881Speter     going to examine the block of data, and make sure that 15%
3231251881Speter     of the bytes are such that their value is in the ranges 0x07-0x0D
3232251881Speter     or 0x20-0x7F, and that none of those bytes is 0x00.  If those
3233251881Speter     criteria are not met, we're calling it binary.
3234251881Speter
3235251881Speter     NOTE:  Originally, I intended to target 85% of the bytes being in
3236251881Speter     the specified ranges, but I flubbed the condition.  At any rate,
3237251881Speter     folks aren't complaining, so I'm not sure that it's worth
3238251881Speter     adjusting this retroactively now.  --cmpilato  */
3239251881Speter  if (len > 0)
3240251881Speter    {
3241251881Speter      apr_size_t i;
3242251881Speter      apr_size_t binary_count = 0;
3243251881Speter
3244251881Speter      /* Run through the data we've read, counting the 'binary-ish'
3245251881Speter         bytes.  HINT: If we see a 0x00 byte, we'll set our count to its
3246251881Speter         max and stop reading the file. */
3247251881Speter      for (i = 0; i < len; i++)
3248251881Speter        {
3249251881Speter          if (buf[i] == 0)
3250251881Speter            {
3251251881Speter              binary_count = len;
3252251881Speter              break;
3253251881Speter            }
3254251881Speter          if ((buf[i] < 0x07)
3255251881Speter              || ((buf[i] > 0x0D) && (buf[i] < 0x20))
3256251881Speter              || (buf[i] > 0x7F))
3257251881Speter            {
3258251881Speter              binary_count++;
3259251881Speter            }
3260251881Speter        }
3261251881Speter
3262251881Speter      return (((binary_count * 1000) / len) > 850);
3263251881Speter    }
3264251881Speter
3265251881Speter  return FALSE;
3266251881Speter}
3267251881Speter
3268251881Speter
3269251881Spetersvn_error_t *
3270251881Spetersvn_io_detect_mimetype(const char **mimetype,
3271251881Speter                       const char *file,
3272251881Speter                       apr_pool_t *pool)
3273251881Speter{
3274251881Speter  return svn_io_detect_mimetype2(mimetype, file, NULL, pool);
3275251881Speter}
3276251881Speter
3277251881Speter
3278251881Spetersvn_error_t *
3279251881Spetersvn_io_file_open(apr_file_t **new_file, const char *fname,
3280251881Speter                 apr_int32_t flag, apr_fileperms_t perm,
3281251881Speter                 apr_pool_t *pool)
3282251881Speter{
3283251881Speter  const char *fname_apr;
3284251881Speter  apr_status_t status;
3285251881Speter
3286251881Speter  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
3287251881Speter  status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE,
3288251881Speter                     pool);
3289251881Speter
3290251881Speter  if (status)
3291251881Speter    return svn_error_wrap_apr(status, _("Can't open file '%s'"),
3292251881Speter                              svn_dirent_local_style(fname, pool));
3293251881Speter  else
3294251881Speter    return SVN_NO_ERROR;
3295251881Speter}
3296251881Speter
3297251881Speter
3298251881Speterstatic APR_INLINE svn_error_t *
3299251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
3300251881Speter                           const char *msg, const char *msg_no_name,
3301251881Speter                           apr_pool_t *pool)
3302251881Speter{
3303251881Speter  const char *name;
3304251881Speter  svn_error_t *err;
3305251881Speter
3306251881Speter  if (! status)
3307251881Speter    return SVN_NO_ERROR;
3308251881Speter
3309251881Speter  err = svn_io_file_name_get(&name, file, pool);
3310251881Speter  if (err)
3311251881Speter    name = NULL;
3312251881Speter  svn_error_clear(err);
3313251881Speter
3314251881Speter  /* ### Issue #3014: Return a specific error for broken pipes,
3315251881Speter   * ### with a single element in the error chain. */
3316262253Speter  if (SVN__APR_STATUS_IS_EPIPE(status))
3317251881Speter    return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
3318251881Speter
3319251881Speter  if (name)
3320251881Speter    return svn_error_wrap_apr(status, _(msg),
3321251881Speter                              try_utf8_from_internal_style(name, pool));
3322251881Speter  else
3323251881Speter    return svn_error_wrap_apr(status, "%s", _(msg_no_name));
3324251881Speter}
3325251881Speter
3326251881Speter
3327251881Spetersvn_error_t *
3328251881Spetersvn_io_file_close(apr_file_t *file, apr_pool_t *pool)
3329251881Speter{
3330251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_close(file),
3331251881Speter                                    N_("Can't close file '%s'"),
3332251881Speter                                    N_("Can't close stream"),
3333251881Speter                                    pool);
3334251881Speter}
3335251881Speter
3336251881Speter
3337251881Spetersvn_error_t *
3338251881Spetersvn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool)
3339251881Speter{
3340251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file),
3341251881Speter                                    N_("Can't read file '%s'"),
3342251881Speter                                    N_("Can't read stream"),
3343251881Speter                                    pool);
3344251881Speter}
3345251881Speter
3346251881Speter
3347251881Spetersvn_error_t *
3348251881Spetersvn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool)
3349251881Speter{
3350251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file),
3351251881Speter                                    N_("Can't write file '%s'"),
3352251881Speter                                    N_("Can't write stream"),
3353251881Speter                                    pool);
3354251881Speter}
3355251881Speter
3356251881Speter
3357251881Spetersvn_error_t *
3358251881Spetersvn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted,
3359251881Speter                     apr_file_t *file, apr_pool_t *pool)
3360251881Speter{
3361251881Speter  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
3362251881Speter  wanted &= ~SVN__APR_FINFO_MASK_OUT;
3363251881Speter
3364251881Speter  return do_io_file_wrapper_cleanup(
3365251881Speter             file, apr_file_info_get(finfo, wanted, file),
3366251881Speter             N_("Can't get attribute information from file '%s'"),
3367251881Speter             N_("Can't get attribute information from stream"),
3368251881Speter             pool);
3369251881Speter}
3370251881Speter
3371251881Speter
3372251881Spetersvn_error_t *
3373251881Spetersvn_io_file_read(apr_file_t *file, void *buf,
3374251881Speter                 apr_size_t *nbytes, apr_pool_t *pool)
3375251881Speter{
3376251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes),
3377251881Speter                                    N_("Can't read file '%s'"),
3378251881Speter                                    N_("Can't read stream"),
3379251881Speter                                    pool);
3380251881Speter}
3381251881Speter
3382251881Speter
3383251881Spetersvn_error_t *
3384251881Spetersvn_io_file_read_full2(apr_file_t *file, void *buf,
3385251881Speter                       apr_size_t nbytes, apr_size_t *bytes_read,
3386251881Speter                       svn_boolean_t *hit_eof,
3387251881Speter                       apr_pool_t *pool)
3388251881Speter{
3389251881Speter  apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read);
3390251881Speter  if (hit_eof)
3391251881Speter    {
3392251881Speter      if (APR_STATUS_IS_EOF(status))
3393251881Speter        {
3394251881Speter          *hit_eof = TRUE;
3395251881Speter          return SVN_NO_ERROR;
3396251881Speter        }
3397251881Speter      else
3398251881Speter        *hit_eof = FALSE;
3399251881Speter    }
3400251881Speter
3401251881Speter  return do_io_file_wrapper_cleanup(file, status,
3402251881Speter                                    N_("Can't read file '%s'"),
3403251881Speter                                    N_("Can't read stream"),
3404251881Speter                                    pool);
3405251881Speter}
3406251881Speter
3407251881Speter
3408251881Spetersvn_error_t *
3409251881Spetersvn_io_file_seek(apr_file_t *file, apr_seek_where_t where,
3410251881Speter                 apr_off_t *offset, apr_pool_t *pool)
3411251881Speter{
3412251881Speter  return do_io_file_wrapper_cleanup(
3413251881Speter             file, apr_file_seek(file, where, offset),
3414251881Speter             N_("Can't set position pointer in file '%s'"),
3415251881Speter             N_("Can't set position pointer in stream"),
3416251881Speter             pool);
3417251881Speter}
3418251881Speter
3419251881Speter
3420251881Spetersvn_error_t *
3421251881Spetersvn_io_file_write(apr_file_t *file, const void *buf,
3422251881Speter                  apr_size_t *nbytes, apr_pool_t *pool)
3423251881Speter{
3424251881Speter  return svn_error_trace(do_io_file_wrapper_cleanup(
3425251881Speter     file, apr_file_write(file, buf, nbytes),
3426251881Speter     N_("Can't write to file '%s'"),
3427251881Speter     N_("Can't write to stream"),
3428251881Speter     pool));
3429251881Speter}
3430251881Speter
3431251881Speter
3432251881Spetersvn_error_t *
3433251881Spetersvn_io_file_write_full(apr_file_t *file, const void *buf,
3434251881Speter                       apr_size_t nbytes, apr_size_t *bytes_written,
3435251881Speter                       apr_pool_t *pool)
3436251881Speter{
3437251881Speter  /* We cannot simply call apr_file_write_full on Win32 as it may fail
3438251881Speter     for larger values of NBYTES. In that case, we have to emulate the
3439251881Speter     "_full" part here. Thus, always call apr_file_write directly on
3440251881Speter     Win32 as this minimizes overhead for small data buffers. */
3441251881Speter#ifdef WIN32
3442251881Speter#define MAXBUFSIZE 30*1024
3443251881Speter  apr_size_t bw = nbytes;
3444251881Speter  apr_size_t to_write = nbytes;
3445251881Speter
3446251881Speter  /* try a simple "write everything at once" first */
3447251881Speter  apr_status_t rv = apr_file_write(file, buf, &bw);
3448251881Speter  buf = (char *)buf + bw;
3449251881Speter  to_write -= bw;
3450251881Speter
3451251881Speter  /* if the OS cannot handle that, use smaller chunks */
3452251881Speter  if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY)
3453251881Speter      && nbytes > MAXBUFSIZE)
3454251881Speter    {
3455251881Speter      do {
3456251881Speter        bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write;
3457251881Speter        rv = apr_file_write(file, buf, &bw);
3458251881Speter        buf = (char *)buf + bw;
3459251881Speter        to_write -= bw;
3460251881Speter      } while (rv == APR_SUCCESS && to_write > 0);
3461251881Speter    }
3462251881Speter
3463251881Speter  /* bytes_written may actually be NULL */
3464251881Speter  if (bytes_written)
3465251881Speter    *bytes_written = nbytes - to_write;
3466251881Speter#undef MAXBUFSIZE
3467251881Speter#else
3468251881Speter  apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written);
3469251881Speter#endif
3470251881Speter
3471251881Speter  return svn_error_trace(do_io_file_wrapper_cleanup(
3472251881Speter     file, rv,
3473251881Speter     N_("Can't write to file '%s'"),
3474251881Speter     N_("Can't write to stream"),
3475251881Speter     pool));
3476251881Speter}
3477251881Speter
3478251881Speter
3479251881Spetersvn_error_t *
3480251881Spetersvn_io_write_unique(const char **tmp_path,
3481251881Speter                    const char *dirpath,
3482251881Speter                    const void *buf,
3483251881Speter                    apr_size_t nbytes,
3484251881Speter                    svn_io_file_del_t delete_when,
3485251881Speter                    apr_pool_t *pool)
3486251881Speter{
3487251881Speter  apr_file_t *new_file;
3488251881Speter  svn_error_t *err;
3489251881Speter
3490251881Speter  SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath,
3491251881Speter                                   delete_when, pool, pool));
3492251881Speter
3493251881Speter  err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool);
3494251881Speter
3495251881Speter  if (!err)
3496251881Speter    err = svn_io_file_flush_to_disk(new_file, pool);
3497251881Speter
3498251881Speter  return svn_error_trace(
3499251881Speter                  svn_error_compose_create(err,
3500251881Speter                                           svn_io_file_close(new_file, pool)));
3501251881Speter}
3502251881Speter
3503251881Speter
3504251881Spetersvn_error_t *
3505251881Spetersvn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool)
3506251881Speter{
3507251881Speter  /* This is a work-around. APR would flush the write buffer
3508251881Speter     _after_ truncating the file causing now invalid buffered
3509251881Speter     data to be written behind OFFSET. */
3510251881Speter  SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
3511251881Speter                                     N_("Can't flush file '%s'"),
3512251881Speter                                     N_("Can't flush stream"),
3513251881Speter                                     pool));
3514251881Speter
3515251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset),
3516251881Speter                                    N_("Can't truncate file '%s'"),
3517251881Speter                                    N_("Can't truncate stream"),
3518251881Speter                                    pool);
3519251881Speter}
3520251881Speter
3521251881Speter
3522251881Spetersvn_error_t *
3523251881Spetersvn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit,
3524251881Speter                        apr_pool_t *pool)
3525251881Speter{
3526251881Speter  /* variables */
3527251881Speter  apr_size_t total_read = 0;
3528251881Speter  svn_boolean_t eof = FALSE;
3529251881Speter  const char *name;
3530251881Speter  svn_error_t *err;
3531251881Speter  apr_size_t buf_size = *limit;
3532251881Speter
3533251881Speter  while (buf_size > 0)
3534251881Speter    {
3535251881Speter      /* read a fair chunk of data at once. But don't get too ambitious
3536251881Speter       * as that would result in too much waste. Also make sure we can
3537251881Speter       * put a NUL after the last byte read.
3538251881Speter       */
3539251881Speter      apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128;
3540251881Speter      apr_size_t bytes_read = 0;
3541251881Speter      char *eol;
3542251881Speter
3543253734Speter      if (to_read == 0)
3544253734Speter        break;
3545253734Speter
3546251881Speter      /* read data block (or just a part of it) */
3547251881Speter      SVN_ERR(svn_io_file_read_full2(file, buf, to_read,
3548251881Speter                                     &bytes_read, &eof, pool));
3549251881Speter
3550251881Speter      /* look or a newline char */
3551251881Speter      buf[bytes_read] = 0;
3552251881Speter      eol = strchr(buf, '\n');
3553251881Speter      if (eol)
3554251881Speter        {
3555251881Speter          apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read;
3556251881Speter
3557251881Speter          *eol = 0;
3558251881Speter          *limit = total_read + (eol - buf);
3559251881Speter
3560251881Speter          /* correct the file pointer:
3561251881Speter           * appear as though we just had read the newline char
3562251881Speter           */
3563251881Speter          SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
3564251881Speter
3565251881Speter          return SVN_NO_ERROR;
3566251881Speter        }
3567251881Speter      else if (eof)
3568251881Speter        {
3569251881Speter          /* no EOL found but we hit the end of the file.
3570251881Speter           * Generate a nice EOF error object and return it.
3571251881Speter           */
3572251881Speter          char dummy;
3573251881Speter          SVN_ERR(svn_io_file_getc(&dummy, file, pool));
3574251881Speter        }
3575251881Speter
3576251881Speter      /* next data chunk */
3577251881Speter      buf_size -= bytes_read;
3578251881Speter      buf += bytes_read;
3579251881Speter      total_read += bytes_read;
3580251881Speter    }
3581251881Speter
3582251881Speter  /* buffer limit has been exceeded without finding the EOL */
3583251881Speter  err = svn_io_file_name_get(&name, file, pool);
3584251881Speter  if (err)
3585251881Speter    name = NULL;
3586251881Speter  svn_error_clear(err);
3587251881Speter
3588251881Speter  if (name)
3589251881Speter    return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
3590251881Speter                             _("Can't read length line in file '%s'"),
3591251881Speter                             svn_dirent_local_style(name, pool));
3592251881Speter  else
3593251881Speter    return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
3594251881Speter                            _("Can't read length line in stream"));
3595251881Speter}
3596251881Speter
3597251881Speter
3598251881Spetersvn_error_t *
3599251881Spetersvn_io_stat(apr_finfo_t *finfo, const char *fname,
3600251881Speter            apr_int32_t wanted, apr_pool_t *pool)
3601251881Speter{
3602251881Speter  apr_status_t status;
3603251881Speter  const char *fname_apr;
3604251881Speter
3605251881Speter  /* APR doesn't like "" directories */
3606251881Speter  if (fname[0] == '\0')
3607251881Speter    fname = ".";
3608251881Speter
3609251881Speter  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
3610251881Speter
3611251881Speter  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
3612251881Speter  wanted &= ~SVN__APR_FINFO_MASK_OUT;
3613251881Speter
3614251881Speter  status = apr_stat(finfo, fname_apr, wanted, pool);
3615251881Speter  if (status)
3616251881Speter    return svn_error_wrap_apr(status, _("Can't stat '%s'"),
3617251881Speter                              svn_dirent_local_style(fname, pool));
3618251881Speter
3619251881Speter  return SVN_NO_ERROR;
3620251881Speter}
3621251881Speter
3622251881Speter
3623251881Spetersvn_error_t *
3624251881Spetersvn_io_file_rename(const char *from_path, const char *to_path,
3625251881Speter                   apr_pool_t *pool)
3626251881Speter{
3627251881Speter  apr_status_t status = APR_SUCCESS;
3628251881Speter  const char *from_path_apr, *to_path_apr;
3629251881Speter
3630251881Speter  SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool));
3631251881Speter  SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool));
3632251881Speter
3633251881Speter  status = apr_file_rename(from_path_apr, to_path_apr, pool);
3634251881Speter
3635251881Speter#if defined(WIN32) || defined(__OS2__)
3636251881Speter  /* If the target file is read only NTFS reports EACCESS and
3637251881Speter     FAT/FAT32 reports EEXIST */
3638251881Speter  if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
3639251881Speter    {
3640251881Speter      /* Set the destination file writable because Windows will not
3641251881Speter         allow us to rename when to_path is read-only, but will
3642251881Speter         allow renaming when from_path is read only. */
3643251881Speter      SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
3644251881Speter
3645251881Speter      status = apr_file_rename(from_path_apr, to_path_apr, pool);
3646251881Speter    }
3647251881Speter  WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool));
3648251881Speter#endif /* WIN32 || __OS2__ */
3649251881Speter
3650251881Speter  if (status)
3651251881Speter    return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"),
3652251881Speter                              svn_dirent_local_style(from_path, pool),
3653251881Speter                              svn_dirent_local_style(to_path, pool));
3654251881Speter
3655251881Speter  return SVN_NO_ERROR;
3656251881Speter}
3657251881Speter
3658251881Speter
3659251881Spetersvn_error_t *
3660251881Spetersvn_io_file_move(const char *from_path, const char *to_path,
3661251881Speter                 apr_pool_t *pool)
3662251881Speter{
3663251881Speter  svn_error_t *err = svn_io_file_rename(from_path, to_path, pool);
3664251881Speter
3665251881Speter  if (err && APR_STATUS_IS_EXDEV(err->apr_err))
3666251881Speter    {
3667251881Speter      const char *tmp_to_path;
3668251881Speter
3669251881Speter      svn_error_clear(err);
3670251881Speter
3671251881Speter      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path,
3672251881Speter                                       svn_dirent_dirname(to_path, pool),
3673251881Speter                                       svn_io_file_del_none,
3674251881Speter                                       pool, pool));
3675251881Speter
3676251881Speter      err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool);
3677251881Speter      if (err)
3678251881Speter        goto failed_tmp;
3679251881Speter
3680251881Speter      err = svn_io_file_rename(tmp_to_path, to_path, pool);
3681251881Speter      if (err)
3682251881Speter        goto failed_tmp;
3683251881Speter
3684251881Speter      err = svn_io_remove_file2(from_path, FALSE, pool);
3685251881Speter      if (! err)
3686251881Speter        return SVN_NO_ERROR;
3687251881Speter
3688251881Speter      svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool));
3689251881Speter
3690251881Speter      return err;
3691251881Speter
3692251881Speter    failed_tmp:
3693251881Speter      svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool));
3694251881Speter    }
3695251881Speter
3696251881Speter  return err;
3697251881Speter}
3698251881Speter
3699251881Speter/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden.
3700251881Speter   HIDDEN determines if the hidden attribute
3701251881Speter   should be set on the newly created directory. */
3702251881Speterstatic svn_error_t *
3703251881Speterdir_make(const char *path, apr_fileperms_t perm,
3704251881Speter         svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool)
3705251881Speter{
3706251881Speter  apr_status_t status;
3707251881Speter  const char *path_apr;
3708251881Speter
3709251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
3710251881Speter
3711251881Speter  /* APR doesn't like "" directories */
3712251881Speter  if (path_apr[0] == '\0')
3713251881Speter    path_apr = ".";
3714251881Speter
3715251881Speter#if (APR_OS_DEFAULT & APR_WSTICKY)
3716251881Speter  /* The APR shipped with httpd 2.0.50 contains a bug where
3717251881Speter     APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits.
3718251881Speter     There is a special case for file creation, but not directory
3719251881Speter     creation, so directories wind up getting created with the sticky
3720251881Speter     bit set.  (There is no such thing as a setuid directory, and the
3721251881Speter     setgid bit is apparently ignored at mkdir() time.)  If we detect
3722251881Speter     this problem, work around it by unsetting those bits if we are
3723251881Speter     passed APR_OS_DEFAULT. */
3724251881Speter  if (perm == APR_OS_DEFAULT)
3725251881Speter    perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY);
3726251881Speter#endif
3727251881Speter
3728251881Speter  status = apr_dir_make(path_apr, perm, pool);
3729251881Speter  WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
3730251881Speter
3731251881Speter  if (status)
3732251881Speter    return svn_error_wrap_apr(status, _("Can't create directory '%s'"),
3733251881Speter                              svn_dirent_local_style(path, pool));
3734251881Speter
3735251881Speter#ifdef APR_FILE_ATTR_HIDDEN
3736251881Speter  if (hidden)
3737251881Speter    {
3738251881Speter#ifndef WIN32
3739251881Speter      status = apr_file_attrs_set(path_apr,
3740251881Speter                                  APR_FILE_ATTR_HIDDEN,
3741251881Speter                                  APR_FILE_ATTR_HIDDEN,
3742251881Speter                                  pool);
3743251881Speter#else
3744251881Speter    /* on Windows, use our wrapper so we can also set the
3745251881Speter       FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */
3746251881Speter    status = io_win_file_attrs_set(path_apr,
3747251881Speter                                   FILE_ATTRIBUTE_HIDDEN |
3748251881Speter                                   FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
3749251881Speter                                   FILE_ATTRIBUTE_HIDDEN |
3750251881Speter                                   FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
3751251881Speter                                   pool);
3752251881Speter
3753251881Speter#endif
3754251881Speter      if (status)
3755251881Speter        return svn_error_wrap_apr(status, _("Can't hide directory '%s'"),
3756251881Speter                                  svn_dirent_local_style(path, pool));
3757251881Speter    }
3758251881Speter#endif
3759251881Speter
3760251881Speter/* Windows does not implement sgid. Skip here because retrieving
3761251881Speter   the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented
3762251881Speter   to be 'incredibly expensive'. */
3763251881Speter#ifndef WIN32
3764251881Speter  if (sgid)
3765251881Speter    {
3766251881Speter      apr_finfo_t finfo;
3767251881Speter
3768251881Speter      /* Per our contract, don't do error-checking.  Some filesystems
3769251881Speter       * don't support the sgid bit, and that's okay. */
3770251881Speter      status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool);
3771251881Speter
3772251881Speter      if (!status)
3773251881Speter        apr_file_perms_set(path_apr, finfo.protection | APR_GSETID);
3774251881Speter    }
3775251881Speter#endif
3776251881Speter
3777251881Speter  return SVN_NO_ERROR;
3778251881Speter}
3779251881Speter
3780251881Spetersvn_error_t *
3781251881Spetersvn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool)
3782251881Speter{
3783251881Speter  return dir_make(path, perm, FALSE, FALSE, pool);
3784251881Speter}
3785251881Speter
3786251881Spetersvn_error_t *
3787251881Spetersvn_io_dir_make_hidden(const char *path, apr_fileperms_t perm,
3788251881Speter                       apr_pool_t *pool)
3789251881Speter{
3790251881Speter  return dir_make(path, perm, TRUE, FALSE, pool);
3791251881Speter}
3792251881Speter
3793251881Spetersvn_error_t *
3794251881Spetersvn_io_dir_make_sgid(const char *path, apr_fileperms_t perm,
3795251881Speter                     apr_pool_t *pool)
3796251881Speter{
3797251881Speter  return dir_make(path, perm, FALSE, TRUE, pool);
3798251881Speter}
3799251881Speter
3800251881Speter
3801251881Spetersvn_error_t *
3802251881Spetersvn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool)
3803251881Speter{
3804251881Speter  apr_status_t status;
3805251881Speter  const char *dirname_apr;
3806251881Speter
3807251881Speter  /* APR doesn't like "" directories */
3808251881Speter  if (dirname[0] == '\0')
3809251881Speter    dirname = ".";
3810251881Speter
3811251881Speter  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3812251881Speter
3813251881Speter  status = apr_dir_open(new_dir, dirname_apr, pool);
3814251881Speter  if (status)
3815251881Speter    return svn_error_wrap_apr(status, _("Can't open directory '%s'"),
3816251881Speter                              svn_dirent_local_style(dirname, pool));
3817251881Speter
3818251881Speter  return SVN_NO_ERROR;
3819251881Speter}
3820251881Speter
3821251881Spetersvn_error_t *
3822251881Spetersvn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool)
3823251881Speter{
3824251881Speter  apr_status_t status;
3825251881Speter  const char *dirname_apr;
3826251881Speter
3827251881Speter  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3828251881Speter
3829251881Speter  status = apr_dir_remove(dirname_apr, pool);
3830251881Speter
3831251881Speter#ifdef WIN32
3832251881Speter  {
3833251881Speter    svn_boolean_t retry = TRUE;
3834251881Speter
3835251881Speter    if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY)
3836251881Speter      {
3837251881Speter        apr_status_t empty_status = dir_is_empty(dirname_apr, pool);
3838251881Speter
3839251881Speter        if (APR_STATUS_IS_ENOTEMPTY(empty_status))
3840251881Speter          retry = FALSE;
3841251881Speter      }
3842251881Speter
3843251881Speter    if (retry)
3844251881Speter      {
3845251881Speter        WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool));
3846251881Speter      }
3847251881Speter  }
3848251881Speter#endif
3849251881Speter  if (status)
3850251881Speter    return svn_error_wrap_apr(status, _("Can't remove directory '%s'"),
3851251881Speter                              svn_dirent_local_style(dirname, pool));
3852251881Speter
3853251881Speter  return SVN_NO_ERROR;
3854251881Speter}
3855251881Speter
3856251881Speter
3857251881Spetersvn_error_t *
3858251881Spetersvn_io_dir_read(apr_finfo_t *finfo,
3859251881Speter                apr_int32_t wanted,
3860251881Speter                apr_dir_t *thedir,
3861251881Speter                apr_pool_t *pool)
3862251881Speter{
3863251881Speter  apr_status_t status;
3864251881Speter
3865251881Speter  status = apr_dir_read(finfo, wanted, thedir);
3866251881Speter
3867251881Speter  if (status)
3868251881Speter    return svn_error_wrap_apr(status, _("Can't read directory"));
3869251881Speter
3870251881Speter  /* It would be nice to use entry_name_to_utf8() below, but can we
3871251881Speter     get the dir's path out of an apr_dir_t?  I don't see a reliable
3872251881Speter     way to do it. */
3873251881Speter
3874251881Speter  if (finfo->fname)
3875251881Speter    SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool));
3876251881Speter
3877251881Speter  if (finfo->name)
3878251881Speter    SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool));
3879251881Speter
3880251881Speter  return SVN_NO_ERROR;
3881251881Speter}
3882251881Speter
3883251881Spetersvn_error_t *
3884251881Spetersvn_io_dir_close(apr_dir_t *thedir)
3885251881Speter{
3886251881Speter  apr_status_t apr_err = apr_dir_close(thedir);
3887251881Speter  if (apr_err)
3888251881Speter    return svn_error_wrap_apr(apr_err, _("Error closing directory"));
3889251881Speter
3890251881Speter  return SVN_NO_ERROR;
3891251881Speter}
3892251881Speter
3893251881Spetersvn_error_t *
3894251881Spetersvn_io_dir_walk2(const char *dirname,
3895251881Speter                 apr_int32_t wanted,
3896251881Speter                 svn_io_walk_func_t walk_func,
3897251881Speter                 void *walk_baton,
3898251881Speter                 apr_pool_t *pool)
3899251881Speter{
3900251881Speter  apr_status_t apr_err;
3901251881Speter  apr_dir_t *handle;
3902251881Speter  apr_pool_t *subpool;
3903251881Speter  const char *dirname_apr;
3904251881Speter  apr_finfo_t finfo;
3905251881Speter
3906251881Speter  wanted |= APR_FINFO_TYPE | APR_FINFO_NAME;
3907251881Speter
3908251881Speter  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
3909251881Speter  wanted &= ~SVN__APR_FINFO_MASK_OUT;
3910251881Speter
3911251881Speter  /* The documentation for apr_dir_read used to state that "." and ".."
3912251881Speter     will be returned as the first two files, but it doesn't
3913251881Speter     work that way in practice, in particular ext3 on Linux-2.6 doesn't
3914251881Speter     follow the rules.  For details see
3915251881Speter     http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666
3916251881Speter
3917251881Speter     If APR ever does implement "dot-first" then it would be possible to
3918251881Speter     remove the svn_io_stat and walk_func calls and use the walk_func
3919251881Speter     inside the loop.
3920251881Speter
3921251881Speter     Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is
3922251881Speter     documented to provide it, so we have to do a bit extra. */
3923251881Speter  SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool));
3924251881Speter  SVN_ERR(cstring_from_utf8(&finfo.name,
3925251881Speter                            svn_dirent_basename(dirname, pool),
3926251881Speter                            pool));
3927251881Speter  finfo.valid |= APR_FINFO_NAME;
3928251881Speter  SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool));
3929251881Speter
3930251881Speter  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3931251881Speter
3932251881Speter  /* APR doesn't like "" directories */
3933251881Speter  if (dirname_apr[0] == '\0')
3934251881Speter    dirname_apr = ".";
3935251881Speter
3936251881Speter  apr_err = apr_dir_open(&handle, dirname_apr, pool);
3937251881Speter  if (apr_err)
3938251881Speter    return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"),
3939251881Speter                              svn_dirent_local_style(dirname, pool));
3940251881Speter
3941251881Speter  /* iteration subpool */
3942251881Speter  subpool = svn_pool_create(pool);
3943251881Speter
3944251881Speter  while (1)
3945251881Speter    {
3946251881Speter      const char *name_utf8;
3947251881Speter      const char *full_path;
3948251881Speter
3949251881Speter      svn_pool_clear(subpool);
3950251881Speter
3951251881Speter      apr_err = apr_dir_read(&finfo, wanted, handle);
3952251881Speter      if (APR_STATUS_IS_ENOENT(apr_err))
3953251881Speter        break;
3954251881Speter      else if (apr_err)
3955251881Speter        {
3956251881Speter          return svn_error_wrap_apr(apr_err,
3957251881Speter                                    _("Can't read directory entry in '%s'"),
3958251881Speter                                    svn_dirent_local_style(dirname, pool));
3959251881Speter        }
3960251881Speter
3961251881Speter      if (finfo.filetype == APR_DIR)
3962251881Speter        {
3963251881Speter          if (finfo.name[0] == '.'
3964251881Speter              && (finfo.name[1] == '\0'
3965251881Speter                  || (finfo.name[1] == '.' && finfo.name[2] == '\0')))
3966251881Speter            /* skip "." and ".." */
3967251881Speter            continue;
3968251881Speter
3969251881Speter          /* some other directory. recurse. it will be passed to the
3970251881Speter             callback inside the recursion. */
3971251881Speter          SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
3972251881Speter                                     subpool));
3973251881Speter          full_path = svn_dirent_join(dirname, name_utf8, subpool);
3974251881Speter          SVN_ERR(svn_io_dir_walk2(full_path,
3975251881Speter                                   wanted,
3976251881Speter                                   walk_func,
3977251881Speter                                   walk_baton,
3978251881Speter                                   subpool));
3979251881Speter        }
3980251881Speter      else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
3981251881Speter        {
3982251881Speter          /* some other directory. pass it to the callback. */
3983251881Speter          SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
3984251881Speter                                     subpool));
3985251881Speter          full_path = svn_dirent_join(dirname, name_utf8, subpool);
3986251881Speter          SVN_ERR((*walk_func)(walk_baton,
3987251881Speter                               full_path,
3988251881Speter                               &finfo,
3989251881Speter                               subpool));
3990251881Speter        }
3991251881Speter      /* else:
3992251881Speter         Some other type of file; skip it for now.  We've reserved the
3993251881Speter         right to expand our coverage here in the future, though,
3994251881Speter         without revving this API.
3995251881Speter      */
3996251881Speter    }
3997251881Speter
3998251881Speter  svn_pool_destroy(subpool);
3999251881Speter
4000251881Speter  apr_err = apr_dir_close(handle);
4001251881Speter  if (apr_err)
4002251881Speter    return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"),
4003251881Speter                              svn_dirent_local_style(dirname, pool));
4004251881Speter
4005251881Speter  return SVN_NO_ERROR;
4006251881Speter}
4007251881Speter
4008251881Speter
4009251881Speter
4010251881Speter/**
4011251881Speter * Determine if a directory is empty or not.
4012251881Speter * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not.
4013251881Speter * @param path The directory.
4014251881Speter * @param pool Used for temporary allocation.
4015251881Speter * @remark If path is not a directory, or some other error occurs,
4016251881Speter * then return the appropriate apr status code.
4017251881Speter *
4018251881Speter * (This function is written in APR style, in anticipation of
4019251881Speter * perhaps someday being moved to APR as 'apr_dir_is_empty'.)
4020251881Speter */
4021251881Speterstatic apr_status_t
4022251881Speterdir_is_empty(const char *dir, apr_pool_t *pool)
4023251881Speter{
4024251881Speter  apr_status_t apr_err;
4025251881Speter  apr_dir_t *dir_handle;
4026251881Speter  apr_finfo_t finfo;
4027251881Speter  apr_status_t retval = APR_SUCCESS;
4028251881Speter
4029251881Speter  /* APR doesn't like "" directories */
4030251881Speter  if (dir[0] == '\0')
4031251881Speter    dir = ".";
4032251881Speter
4033251881Speter  apr_err = apr_dir_open(&dir_handle, dir, pool);
4034251881Speter  if (apr_err != APR_SUCCESS)
4035251881Speter    return apr_err;
4036251881Speter
4037251881Speter  for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle);
4038251881Speter       apr_err == APR_SUCCESS;
4039251881Speter       apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle))
4040251881Speter    {
4041251881Speter      /* Ignore entries for this dir and its parent, robustly.
4042251881Speter         (APR promises that they'll come first, so technically
4043251881Speter         this guard could be moved outside the loop.  But Ryan Bloom
4044251881Speter         says he doesn't believe it, and I believe him. */
4045251881Speter      if (! (finfo.name[0] == '.'
4046251881Speter             && (finfo.name[1] == '\0'
4047251881Speter                 || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
4048251881Speter        {
4049251881Speter          retval = APR_ENOTEMPTY;
4050251881Speter          break;
4051251881Speter        }
4052251881Speter    }
4053251881Speter
4054251881Speter  /* Make sure we broke out of the loop for the right reason. */
4055251881Speter  if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err))
4056251881Speter    return apr_err;
4057251881Speter
4058251881Speter  apr_err = apr_dir_close(dir_handle);
4059251881Speter  if (apr_err != APR_SUCCESS)
4060251881Speter    return apr_err;
4061251881Speter
4062251881Speter  return retval;
4063251881Speter}
4064251881Speter
4065251881Speter
4066251881Spetersvn_error_t *
4067251881Spetersvn_io_dir_empty(svn_boolean_t *is_empty_p,
4068251881Speter                 const char *path,
4069251881Speter                 apr_pool_t *pool)
4070251881Speter{
4071251881Speter  apr_status_t status;
4072251881Speter  const char *path_apr;
4073251881Speter
4074251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
4075251881Speter
4076251881Speter  status = dir_is_empty(path_apr, pool);
4077251881Speter
4078251881Speter  if (!status)
4079251881Speter    *is_empty_p = TRUE;
4080251881Speter  else if (APR_STATUS_IS_ENOTEMPTY(status))
4081251881Speter    *is_empty_p = FALSE;
4082251881Speter  else
4083251881Speter    return svn_error_wrap_apr(status, _("Can't check directory '%s'"),
4084251881Speter                              svn_dirent_local_style(path, pool));
4085251881Speter
4086251881Speter  return SVN_NO_ERROR;
4087251881Speter}
4088251881Speter
4089251881Speter
4090251881Speter
4091251881Speter/*** Version/format files ***/
4092251881Speter
4093251881Spetersvn_error_t *
4094251881Spetersvn_io_write_version_file(const char *path,
4095251881Speter                          int version,
4096251881Speter                          apr_pool_t *pool)
4097251881Speter{
4098251881Speter  const char *path_tmp;
4099251881Speter  const char *format_contents = apr_psprintf(pool, "%d\n", version);
4100251881Speter
4101251881Speter  SVN_ERR_ASSERT(version >= 0);
4102251881Speter
4103251881Speter  SVN_ERR(svn_io_write_unique(&path_tmp,
4104251881Speter                              svn_dirent_dirname(path, pool),
4105251881Speter                              format_contents, strlen(format_contents),
4106251881Speter                              svn_io_file_del_none, pool));
4107251881Speter
4108251881Speter#if defined(WIN32) || defined(__OS2__)
4109251881Speter  /* make the destination writable, but only on Windows, because
4110251881Speter     Windows does not let us replace read-only files. */
4111251881Speter  SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
4112251881Speter#endif /* WIN32 || __OS2__ */
4113251881Speter
4114251881Speter  /* rename the temp file as the real destination */
4115251881Speter  SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
4116251881Speter
4117251881Speter  /* And finally remove the perms to make it read only */
4118251881Speter  return svn_io_set_file_read_only(path, FALSE, pool);
4119251881Speter}
4120251881Speter
4121251881Speter
4122251881Spetersvn_error_t *
4123251881Spetersvn_io_read_version_file(int *version,
4124251881Speter                         const char *path,
4125251881Speter                         apr_pool_t *pool)
4126251881Speter{
4127251881Speter  apr_file_t *format_file;
4128251881Speter  char buf[80];
4129251881Speter  apr_size_t len;
4130251881Speter  svn_error_t *err;
4131251881Speter
4132251881Speter  /* Read a chunk of data from PATH */
4133251881Speter  SVN_ERR(svn_io_file_open(&format_file, path, APR_READ,
4134251881Speter                           APR_OS_DEFAULT, pool));
4135251881Speter  len = sizeof(buf);
4136251881Speter  err = svn_io_file_read(format_file, buf, &len, pool);
4137251881Speter
4138251881Speter  /* Close the file. */
4139251881Speter  SVN_ERR(svn_error_compose_create(err,
4140251881Speter                                   svn_io_file_close(format_file, pool)));
4141251881Speter
4142251881Speter  /* If there was no data in PATH, return an error. */
4143251881Speter  if (len == 0)
4144251881Speter    return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
4145251881Speter                             _("Reading '%s'"),
4146251881Speter                             svn_dirent_local_style(path, pool));
4147251881Speter
4148251881Speter  /* Check that the first line contains only digits. */
4149251881Speter  {
4150251881Speter    apr_size_t i;
4151251881Speter
4152251881Speter    for (i = 0; i < len; ++i)
4153251881Speter      {
4154251881Speter        char c = buf[i];
4155251881Speter
4156251881Speter        if (i > 0 && (c == '\r' || c == '\n'))
4157251881Speter          {
4158251881Speter            buf[i] = '\0';
4159251881Speter            break;
4160251881Speter          }
4161251881Speter        if (! svn_ctype_isdigit(c))
4162251881Speter          return svn_error_createf
4163251881Speter            (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
4164251881Speter             _("First line of '%s' contains non-digit"),
4165251881Speter             svn_dirent_local_style(path, pool));
4166251881Speter      }
4167251881Speter  }
4168251881Speter
4169251881Speter  /* Convert to integer. */
4170251881Speter  SVN_ERR(svn_cstring_atoi(version, buf));
4171251881Speter
4172251881Speter  return SVN_NO_ERROR;
4173251881Speter}
4174251881Speter
4175251881Speter
4176251881Speter
4177251881Speter/* Do a byte-for-byte comparison of FILE1 and FILE2. */
4178251881Speterstatic svn_error_t *
4179251881Spetercontents_identical_p(svn_boolean_t *identical_p,
4180251881Speter                     const char *file1,
4181251881Speter                     const char *file2,
4182251881Speter                     apr_pool_t *pool)
4183251881Speter{
4184251881Speter  svn_error_t *err;
4185251881Speter  apr_size_t bytes_read1, bytes_read2;
4186251881Speter  char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
4187251881Speter  char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
4188251881Speter  apr_file_t *file1_h;
4189251881Speter  apr_file_t *file2_h;
4190251881Speter  svn_boolean_t eof1 = FALSE;
4191251881Speter  svn_boolean_t eof2 = FALSE;
4192251881Speter
4193251881Speter  SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
4194251881Speter                           pool));
4195251881Speter
4196251881Speter  err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
4197251881Speter                         pool);
4198251881Speter
4199251881Speter  if (err)
4200251881Speter    return svn_error_trace(
4201251881Speter               svn_error_compose_create(err,
4202251881Speter                                        svn_io_file_close(file1_h, pool)));
4203251881Speter
4204251881Speter  *identical_p = TRUE;  /* assume TRUE, until disproved below */
4205251881Speter  while (!err && !eof1 && !eof2)
4206251881Speter    {
4207251881Speter      err = svn_io_file_read_full2(file1_h, buf1,
4208251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read1,
4209251881Speter                                   &eof1, pool);
4210251881Speter      if (err)
4211251881Speter          break;
4212251881Speter
4213251881Speter      err = svn_io_file_read_full2(file2_h, buf2,
4214251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read2,
4215251881Speter                                   &eof2, pool);
4216251881Speter      if (err)
4217251881Speter          break;
4218251881Speter
4219251881Speter      if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1))
4220251881Speter        {
4221251881Speter          *identical_p = FALSE;
4222251881Speter          break;
4223251881Speter        }
4224251881Speter    }
4225251881Speter
4226251881Speter  /* Special case: one file being a prefix of the other and the shorter
4227251881Speter   * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */
4228251881Speter  if (!err && (eof1 != eof2))
4229251881Speter    *identical_p = FALSE;
4230251881Speter
4231251881Speter  return svn_error_trace(
4232251881Speter           svn_error_compose_create(
4233251881Speter                err,
4234251881Speter                svn_error_compose_create(svn_io_file_close(file1_h, pool),
4235251881Speter                                         svn_io_file_close(file2_h, pool))));
4236251881Speter}
4237251881Speter
4238251881Speter
4239251881Speter
4240251881Speter/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */
4241251881Speterstatic svn_error_t *
4242251881Spetercontents_three_identical_p(svn_boolean_t *identical_p12,
4243251881Speter                           svn_boolean_t *identical_p23,
4244251881Speter                           svn_boolean_t *identical_p13,
4245251881Speter                           const char *file1,
4246251881Speter                           const char *file2,
4247251881Speter                           const char *file3,
4248251881Speter                           apr_pool_t *scratch_pool)
4249251881Speter{
4250251881Speter  svn_error_t *err;
4251251881Speter  apr_size_t bytes_read1, bytes_read2, bytes_read3;
4252251881Speter  char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4253251881Speter  char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4254251881Speter  char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4255251881Speter  apr_file_t *file1_h;
4256251881Speter  apr_file_t *file2_h;
4257251881Speter  apr_file_t *file3_h;
4258251881Speter  svn_boolean_t eof1 = FALSE;
4259251881Speter  svn_boolean_t eof2 = FALSE;
4260251881Speter  svn_boolean_t eof3 = FALSE;
4261251881Speter  svn_boolean_t read_1, read_2, read_3;
4262251881Speter
4263251881Speter  SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
4264251881Speter                           scratch_pool));
4265251881Speter
4266251881Speter  err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
4267251881Speter                         scratch_pool);
4268251881Speter
4269251881Speter  if (err)
4270251881Speter    return svn_error_trace(
4271251881Speter               svn_error_compose_create(err,
4272251881Speter                                        svn_io_file_close(file1_h, scratch_pool)));
4273251881Speter
4274251881Speter  err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT,
4275251881Speter                         scratch_pool);
4276251881Speter
4277251881Speter  if (err)
4278251881Speter      return svn_error_trace(
4279251881Speter               svn_error_compose_create(
4280251881Speter                    err,
4281251881Speter                    svn_error_compose_create(svn_io_file_close(file1_h,
4282251881Speter                                                          scratch_pool),
4283251881Speter                                             svn_io_file_close(file2_h,
4284251881Speter                                                          scratch_pool))));
4285251881Speter
4286251881Speter  /* assume TRUE, until disproved below */
4287251881Speter  *identical_p12 = *identical_p23 = *identical_p13 = TRUE;
4288251881Speter  /* We need to read as long as no error occurs, and as long as one of the
4289251881Speter   * flags could still change due to a read operation */
4290251881Speter  while (!err
4291251881Speter        && ((*identical_p12 && !eof1 && !eof2)
4292251881Speter            || (*identical_p23 && !eof2 && !eof3)
4293251881Speter            || (*identical_p13 && !eof1 && !eof3)))
4294251881Speter    {
4295251881Speter      read_1 = read_2 = read_3 = FALSE;
4296251881Speter
4297251881Speter      /* As long as a file is not at the end yet, and it is still
4298251881Speter       * potentially identical to another file, we read the next chunk.*/
4299262253Speter      if (!eof1 && (*identical_p12 || *identical_p13))
4300251881Speter        {
4301251881Speter          err = svn_io_file_read_full2(file1_h, buf1,
4302251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read1,
4303251881Speter                                   &eof1, scratch_pool);
4304251881Speter          if (err)
4305251881Speter              break;
4306251881Speter          read_1 = TRUE;
4307251881Speter        }
4308251881Speter
4309262253Speter      if (!eof2 && (*identical_p12 || *identical_p23))
4310251881Speter        {
4311251881Speter          err = svn_io_file_read_full2(file2_h, buf2,
4312251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read2,
4313251881Speter                                   &eof2, scratch_pool);
4314251881Speter          if (err)
4315251881Speter              break;
4316251881Speter          read_2 = TRUE;
4317251881Speter        }
4318251881Speter
4319262253Speter      if (!eof3 && (*identical_p13 || *identical_p23))
4320251881Speter        {
4321251881Speter          err = svn_io_file_read_full2(file3_h, buf3,
4322251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read3,
4323251881Speter                                   &eof3, scratch_pool);
4324251881Speter          if (err)
4325251881Speter              break;
4326251881Speter          read_3 = TRUE;
4327251881Speter        }
4328251881Speter
4329251881Speter      /* If the files are still marked identical, and at least one of them
4330251881Speter       * is not at the end of file, we check whether they differ, and set
4331251881Speter       * their flag to false then. */
4332251881Speter      if (*identical_p12
4333251881Speter          && (read_1 || read_2)
4334251881Speter          && ((eof1 != eof2)
4335251881Speter              || (bytes_read1 != bytes_read2)
4336251881Speter              || memcmp(buf1, buf2, bytes_read1)))
4337251881Speter        {
4338251881Speter          *identical_p12 = FALSE;
4339251881Speter        }
4340251881Speter
4341251881Speter      if (*identical_p23
4342251881Speter          && (read_2 || read_3)
4343251881Speter          && ((eof2 != eof3)
4344251881Speter              || (bytes_read2 != bytes_read3)
4345251881Speter              || memcmp(buf2, buf3, bytes_read2)))
4346251881Speter        {
4347251881Speter          *identical_p23 = FALSE;
4348251881Speter        }
4349251881Speter
4350251881Speter      if (*identical_p13
4351251881Speter          && (read_1 || read_3)
4352251881Speter          && ((eof1 != eof3)
4353251881Speter              || (bytes_read1 != bytes_read3)
4354251881Speter              || memcmp(buf1, buf3, bytes_read3)))
4355251881Speter        {
4356251881Speter          *identical_p13 = FALSE;
4357251881Speter        }
4358251881Speter    }
4359251881Speter
4360251881Speter  return svn_error_trace(
4361251881Speter           svn_error_compose_create(
4362251881Speter                err,
4363251881Speter                svn_error_compose_create(
4364251881Speter                    svn_io_file_close(file1_h, scratch_pool),
4365251881Speter                    svn_error_compose_create(
4366251881Speter                        svn_io_file_close(file2_h, scratch_pool),
4367251881Speter                        svn_io_file_close(file3_h, scratch_pool)))));
4368251881Speter}
4369251881Speter
4370251881Speter
4371251881Speter
4372251881Spetersvn_error_t *
4373251881Spetersvn_io_files_contents_same_p(svn_boolean_t *same,
4374251881Speter                             const char *file1,
4375251881Speter                             const char *file2,
4376251881Speter                             apr_pool_t *pool)
4377251881Speter{
4378251881Speter  svn_boolean_t q;
4379251881Speter
4380251881Speter  SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool));
4381251881Speter
4382251881Speter  if (q)
4383251881Speter    {
4384251881Speter      *same = FALSE;
4385251881Speter      return SVN_NO_ERROR;
4386251881Speter    }
4387251881Speter
4388251881Speter  SVN_ERR(contents_identical_p(&q, file1, file2, pool));
4389251881Speter
4390251881Speter  if (q)
4391251881Speter    *same = TRUE;
4392251881Speter  else
4393251881Speter    *same = FALSE;
4394251881Speter
4395251881Speter  return SVN_NO_ERROR;
4396251881Speter}
4397251881Speter
4398251881Spetersvn_error_t *
4399251881Spetersvn_io_files_contents_three_same_p(svn_boolean_t *same12,
4400251881Speter                                   svn_boolean_t *same23,
4401251881Speter                                   svn_boolean_t *same13,
4402251881Speter                                   const char *file1,
4403251881Speter                                   const char *file2,
4404251881Speter                                   const char *file3,
4405251881Speter                                   apr_pool_t *scratch_pool)
4406251881Speter{
4407251881Speter  svn_boolean_t diff_size12, diff_size23, diff_size13;
4408251881Speter
4409251881Speter  SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12,
4410251881Speter                                             &diff_size23,
4411251881Speter                                             &diff_size13,
4412251881Speter                                             file1,
4413251881Speter                                             file2,
4414251881Speter                                             file3,
4415251881Speter                                             scratch_pool));
4416251881Speter
4417251881Speter  if (diff_size12 && diff_size23 && diff_size13)
4418251881Speter    {
4419251881Speter      *same12 = *same23 = *same13 = FALSE;
4420251881Speter    }
4421251881Speter  else if (diff_size12 && diff_size23)
4422251881Speter    {
4423251881Speter      *same12 = *same23 = FALSE;
4424251881Speter      SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool));
4425251881Speter    }
4426251881Speter  else if (diff_size23 && diff_size13)
4427251881Speter    {
4428251881Speter      *same23 = *same13 = FALSE;
4429251881Speter      SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool));
4430251881Speter    }
4431251881Speter  else if (diff_size12 && diff_size13)
4432251881Speter    {
4433251881Speter      *same12 = *same13 = FALSE;
4434251881Speter      SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool));
4435251881Speter    }
4436251881Speter  else
4437251881Speter    {
4438251881Speter      SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13);
4439251881Speter      SVN_ERR(contents_three_identical_p(same12, same23, same13,
4440251881Speter                                         file1, file2, file3,
4441251881Speter                                         scratch_pool));
4442251881Speter    }
4443251881Speter
4444251881Speter  return SVN_NO_ERROR;
4445251881Speter}
4446251881Speter
4447251881Speter#ifdef WIN32
4448251881Speter/* Counter value of file_mktemp request (used in a threadsafe way), to make
4449251881Speter   sure that a single process normally never generates the same tempname
4450251881Speter   twice */
4451251881Speterstatic volatile apr_uint32_t tempname_counter = 0;
4452251881Speter#endif
4453251881Speter
4454251881Speter/* Creates a new temporary file in DIRECTORY with apr flags FLAGS.
4455251881Speter   Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name.
4456251881Speter   Perform temporary allocations in SCRATCH_POOL and the result in
4457251881Speter   RESULT_POOL. */
4458251881Speterstatic svn_error_t *
4459251881Spetertemp_file_create(apr_file_t **new_file,
4460251881Speter                 const char **new_file_name,
4461251881Speter                 const char *directory,
4462251881Speter                 apr_int32_t flags,
4463251881Speter                 apr_pool_t *result_pool,
4464251881Speter                 apr_pool_t *scratch_pool)
4465251881Speter{
4466251881Speter#ifndef WIN32
4467251881Speter  const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool);
4468251881Speter  const char *templ_apr;
4469251881Speter  apr_status_t status;
4470251881Speter
4471251881Speter  SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool));
4472251881Speter
4473251881Speter  /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the
4474251881Speter         data available in POOL and we need a non-const pointer here,
4475251881Speter         as apr changes the template to return the new filename. */
4476251881Speter  status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool);
4477251881Speter
4478251881Speter  if (status)
4479251881Speter    return svn_error_wrap_apr(status, _("Can't create temporary file from "
4480251881Speter                              "template '%s'"), templ);
4481251881Speter
4482251881Speter  /* Translate the returned path back to utf-8 before returning it */
4483251881Speter  return svn_error_trace(svn_path_cstring_to_utf8(new_file_name,
4484251881Speter                                                  templ_apr,
4485251881Speter                                                  result_pool));
4486251881Speter#else
4487251881Speter  /* The Windows implementation of apr_file_mktemp doesn't handle access
4488251881Speter     denied errors correctly. Therefore we implement our own temp file
4489251881Speter     creation function here. */
4490251881Speter
4491251881Speter  /* ### Most of this is borrowed from the svn_io_open_uniquely_named(),
4492251881Speter     ### the function we used before. But we try to guess a more unique
4493251881Speter     ### name before trying if it exists. */
4494251881Speter
4495251881Speter  /* Offset by some time value and a unique request nr to make the number
4496251881Speter     +- unique for both this process and on the computer */
4497251881Speter  int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter)
4498251881Speter               + GetCurrentProcessId();
4499251881Speter  int i;
4500251881Speter
4501251881Speter  /* ### Maybe use an iterpool? */
4502251881Speter  for (i = 0; i <= 99999; i++)
4503251881Speter    {
4504251881Speter      apr_uint32_t unique_nr;
4505251881Speter      const char *unique_name;
4506251881Speter      const char *unique_name_apr;
4507251881Speter      apr_file_t *try_file;
4508251881Speter      apr_status_t apr_err;
4509251881Speter
4510251881Speter      /* Generate a number that should be unique for this application and
4511251881Speter         usually for the entire computer to reduce the number of cycles
4512251881Speter         through this loop. (A bit of calculation is much cheaper then
4513251881Speter         disk io) */
4514251881Speter      unique_nr = baseNr + 3 * i;
4515251881Speter
4516251881Speter      unique_name = svn_dirent_join(directory,
4517251881Speter                                    apr_psprintf(scratch_pool, "svn-%X",
4518251881Speter                                                 unique_nr),
4519251881Speter                                    scratch_pool);
4520251881Speter
4521251881Speter      SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool));
4522251881Speter
4523251881Speter      apr_err = file_open(&try_file, unique_name_apr, flags,
4524251881Speter                          APR_OS_DEFAULT, FALSE, scratch_pool);
4525251881Speter
4526251881Speter      if (APR_STATUS_IS_EEXIST(apr_err))
4527251881Speter          continue;
4528251881Speter      else if (apr_err)
4529251881Speter        {
4530251881Speter          /* On Win32, CreateFile fails with an "Access Denied" error
4531251881Speter             code, rather than "File Already Exists", if the colliding
4532251881Speter             name belongs to a directory. */
4533251881Speter
4534251881Speter          if (APR_STATUS_IS_EACCES(apr_err))
4535251881Speter            {
4536251881Speter              apr_finfo_t finfo;
4537251881Speter              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
4538251881Speter                                                APR_FINFO_TYPE, scratch_pool);
4539251881Speter
4540251881Speter              if (!apr_err_2 && finfo.filetype == APR_DIR)
4541251881Speter                continue;
4542251881Speter
4543251881Speter              apr_err_2 = APR_TO_OS_ERROR(apr_err);
4544251881Speter
4545251881Speter              if (apr_err_2 == ERROR_ACCESS_DENIED ||
4546251881Speter                  apr_err_2 == ERROR_SHARING_VIOLATION)
4547251881Speter                {
4548251881Speter                  /* The file is in use by another process or is hidden;
4549251881Speter                     create a new name, but don't do this 99999 times in
4550251881Speter                     case the folder is not writable */
4551251881Speter                  i += 797;
4552251881Speter                  continue;
4553251881Speter                }
4554251881Speter
4555251881Speter              /* Else fall through and return the original error. */
4556251881Speter            }
4557251881Speter
4558251881Speter          return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
4559251881Speter                                    svn_dirent_local_style(unique_name,
4560251881Speter                                                           scratch_pool));
4561251881Speter        }
4562251881Speter      else
4563251881Speter        {
4564251881Speter          /* Move file to the right pool */
4565251881Speter          apr_err = apr_file_setaside(new_file, try_file, result_pool);
4566251881Speter
4567251881Speter          if (apr_err)
4568251881Speter            return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"),
4569251881Speter                                      svn_dirent_local_style(unique_name,
4570251881Speter                                                             scratch_pool));
4571251881Speter
4572251881Speter          *new_file_name = apr_pstrdup(result_pool, unique_name);
4573251881Speter
4574251881Speter          return SVN_NO_ERROR;
4575251881Speter        }
4576251881Speter    }
4577251881Speter
4578251881Speter  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
4579251881Speter                           NULL,
4580251881Speter                           _("Unable to make name in '%s'"),
4581251881Speter                           svn_dirent_local_style(directory, scratch_pool));
4582251881Speter#endif
4583251881Speter}
4584251881Speter
4585251881Speter/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */
4586251881Spetersvn_error_t *
4587251881Spetersvn_io_file_name_get(const char **filename,
4588251881Speter                     apr_file_t *file,
4589251881Speter                     apr_pool_t *pool)
4590251881Speter{
4591251881Speter  const char *fname_apr;
4592251881Speter  apr_status_t status;
4593251881Speter
4594251881Speter  status = apr_file_name_get(&fname_apr, file);
4595251881Speter  if (status)
4596251881Speter    return svn_error_wrap_apr(status, _("Can't get file name"));
4597251881Speter
4598251881Speter  if (fname_apr)
4599251881Speter    SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool));
4600251881Speter  else
4601251881Speter    *filename = NULL;
4602251881Speter
4603251881Speter  return SVN_NO_ERROR;
4604251881Speter}
4605251881Speter
4606251881Speter
4607251881Spetersvn_error_t *
4608251881Spetersvn_io_open_unique_file3(apr_file_t **file,
4609251881Speter                         const char **unique_path,
4610251881Speter                         const char *dirpath,
4611251881Speter                         svn_io_file_del_t delete_when,
4612251881Speter                         apr_pool_t *result_pool,
4613251881Speter                         apr_pool_t *scratch_pool)
4614251881Speter{
4615251881Speter  apr_file_t *tempfile;
4616251881Speter  const char *tempname;
4617251881Speter  struct temp_file_cleanup_s *baton = NULL;
4618251881Speter  apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL |
4619251881Speter                       APR_BUFFERED | APR_BINARY);
4620251881Speter#if !defined(WIN32) && !defined(__OS2__)
4621251881Speter  apr_fileperms_t perms;
4622251881Speter  svn_boolean_t using_system_temp_dir = FALSE;
4623251881Speter#endif
4624251881Speter
4625251881Speter  SVN_ERR_ASSERT(file || unique_path);
4626251881Speter  if (file)
4627251881Speter    *file = NULL;
4628251881Speter  if (unique_path)
4629251881Speter    *unique_path = NULL;
4630251881Speter
4631251881Speter  if (dirpath == NULL)
4632251881Speter    {
4633251881Speter#if !defined(WIN32) && !defined(__OS2__)
4634251881Speter      using_system_temp_dir = TRUE;
4635251881Speter#endif
4636251881Speter      SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
4637251881Speter    }
4638251881Speter
4639251881Speter  switch (delete_when)
4640251881Speter    {
4641251881Speter      case svn_io_file_del_on_pool_cleanup:
4642251881Speter        baton = apr_palloc(result_pool, sizeof(*baton));
4643251881Speter        baton->pool = result_pool;
4644251881Speter        baton->fname_apr = NULL;
4645251881Speter
4646251881Speter        /* Because cleanups are run LIFO, we need to make sure to register
4647251881Speter           our cleanup before the apr_file_close cleanup:
4648251881Speter
4649251881Speter           On Windows, you can't remove an open file.
4650251881Speter        */
4651251881Speter        apr_pool_cleanup_register(result_pool, baton,
4652251881Speter                                  temp_file_plain_cleanup_handler,
4653251881Speter                                  temp_file_child_cleanup_handler);
4654251881Speter
4655251881Speter        break;
4656251881Speter      case svn_io_file_del_on_close:
4657251881Speter        flags |= APR_DELONCLOSE;
4658251881Speter        break;
4659251881Speter      default:
4660251881Speter        break;
4661251881Speter    }
4662251881Speter
4663251881Speter  SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags,
4664251881Speter                           result_pool, scratch_pool));
4665251881Speter
4666251881Speter#if !defined(WIN32) && !defined(__OS2__)
4667251881Speter  /* apr_file_mktemp() creates files with mode 0600.
4668251881Speter   * This is appropriate if we're using a system temp dir since we don't
4669251881Speter   * want to leak sensitive data into temp files other users can read.
4670251881Speter   * If we're not using a system temp dir we're probably using the
4671251881Speter   * .svn/tmp area and it's likely that the tempfile will end up being
4672251881Speter   * copied or renamed into the working copy.
4673251881Speter   * This would cause working files having mode 0600 while users might
4674251881Speter   * expect to see 0644 or 0664. So we tweak perms of the tempfile in this
4675251881Speter   * case, but only if the umask allows it. */
4676251881Speter  if (!using_system_temp_dir)
4677251881Speter    {
4678289166Speter      svn_error_t *err;
4679289166Speter
4680251881Speter      SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool));
4681289166Speter      err = file_perms_set2(tempfile, perms, scratch_pool);
4682289166Speter      if (err)
4683289166Speter        {
4684289166Speter          if (APR_STATUS_IS_INCOMPLETE(err->apr_err) ||
4685289166Speter              APR_STATUS_IS_ENOTIMPL(err->apr_err))
4686289166Speter            svn_error_clear(err);
4687289166Speter          else
4688289166Speter            {
4689289166Speter              const char *message;
4690289166Speter              message = apr_psprintf(scratch_pool,
4691289166Speter                                     _("Can't set permissions on '%s'"),
4692289166Speter                                     svn_dirent_local_style(tempname,
4693289166Speter                                                            scratch_pool));
4694289166Speter              return svn_error_quick_wrap(err, message);
4695289166Speter            }
4696289166Speter        }
4697251881Speter    }
4698251881Speter#endif
4699251881Speter
4700251881Speter  if (file)
4701251881Speter    *file = tempfile;
4702251881Speter  else
4703251881Speter    SVN_ERR(svn_io_file_close(tempfile, scratch_pool));
4704251881Speter
4705251881Speter  if (unique_path)
4706251881Speter    *unique_path = tempname; /* Was allocated in result_pool */
4707251881Speter
4708251881Speter  if (baton)
4709251881Speter    SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool));
4710251881Speter
4711251881Speter  return SVN_NO_ERROR;
4712251881Speter}
4713251881Speter
4714251881Spetersvn_error_t *
4715251881Spetersvn_io_file_readline(apr_file_t *file,
4716251881Speter                     svn_stringbuf_t **stringbuf,
4717251881Speter                     const char **eol,
4718251881Speter                     svn_boolean_t *eof,
4719251881Speter                     apr_size_t max_len,
4720251881Speter                     apr_pool_t *result_pool,
4721251881Speter                     apr_pool_t *scratch_pool)
4722251881Speter{
4723251881Speter  svn_stringbuf_t *str;
4724251881Speter  const char *eol_str;
4725251881Speter  apr_size_t numbytes;
4726251881Speter  char c;
4727251881Speter  apr_size_t len;
4728251881Speter  svn_boolean_t found_eof;
4729251881Speter
4730251881Speter  str = svn_stringbuf_create_ensure(80, result_pool);
4731251881Speter
4732251881Speter  /* Read bytes into STR up to and including, but not storing,
4733251881Speter   * the next EOL sequence. */
4734251881Speter  eol_str = NULL;
4735251881Speter  numbytes = 1;
4736251881Speter  len = 0;
4737251881Speter  found_eof = FALSE;
4738251881Speter  while (!found_eof)
4739251881Speter    {
4740251881Speter      if (len < max_len)
4741251881Speter        SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
4742251881Speter                                       &found_eof, scratch_pool));
4743251881Speter      len++;
4744251881Speter      if (numbytes != 1 || len > max_len)
4745251881Speter        {
4746251881Speter          found_eof = TRUE;
4747251881Speter          break;
4748251881Speter        }
4749251881Speter
4750251881Speter      if (c == '\n')
4751251881Speter        {
4752251881Speter          eol_str = "\n";
4753251881Speter        }
4754251881Speter      else if (c == '\r')
4755251881Speter        {
4756251881Speter          eol_str = "\r";
4757251881Speter
4758251881Speter          if (!found_eof && len < max_len)
4759251881Speter            {
4760251881Speter              apr_off_t pos;
4761251881Speter
4762251881Speter              /* Check for "\r\n" by peeking at the next byte. */
4763251881Speter              pos = 0;
4764251881Speter              SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
4765251881Speter              SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
4766251881Speter                                             &found_eof, scratch_pool));
4767251881Speter              if (numbytes == 1 && c == '\n')
4768251881Speter                {
4769251881Speter                  eol_str = "\r\n";
4770251881Speter                  len++;
4771251881Speter                }
4772251881Speter              else
4773251881Speter                {
4774251881Speter                  /* Pretend we never peeked. */
4775251881Speter                  SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
4776251881Speter                  found_eof = FALSE;
4777251881Speter                  numbytes = 1;
4778251881Speter                }
4779251881Speter            }
4780251881Speter        }
4781251881Speter      else
4782251881Speter        svn_stringbuf_appendbyte(str, c);
4783251881Speter
4784251881Speter      if (eol_str)
4785251881Speter        break;
4786251881Speter    }
4787251881Speter
4788251881Speter  if (eol)
4789251881Speter    *eol = eol_str;
4790251881Speter  if (eof)
4791251881Speter    *eof = found_eof;
4792251881Speter  *stringbuf = str;
4793251881Speter
4794251881Speter  return SVN_NO_ERROR;
4795251881Speter}
4796