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
50299742Sdim#if APR_HAVE_FCNTL_H
51299742Sdim#include <fcntl.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"
69299742Sdim#include "private/svn_utf_private.h"
70299742Sdim#include "private/svn_dep_compat.h"
71251881Speter
72251881Speter#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
73251881Speter
74251881Speter/*
75251881Speter  Windows is 'aided' by a number of types of applications that
76251881Speter  follow other applications around and open up files they have
77251881Speter  changed for various reasons (the most intrusive are virus
78251881Speter  scanners).  So, if one of these other apps has glommed onto
79251881Speter  our file we may get an 'access denied' error.
80251881Speter
81251881Speter  This retry loop does not completely solve the problem (who
82251881Speter  knows how long the other app is going to hold onto it for), but
83251881Speter  goes a long way towards minimizing it.  It is not an infinite
84251881Speter  loop because there might really be an error.
85251881Speter
86251881Speter  Another reason for retrying delete operations on Windows
87251881Speter  is that they are asynchronous -- the file or directory is not
88251881Speter  actually deleted until the last handle to it is closed.  The
89251881Speter  retry loop cannot completely solve this problem either, but can
90251881Speter  help mitigate it.
91251881Speter*/
92251881Speter#define RETRY_MAX_ATTEMPTS 100
93251881Speter#define RETRY_INITIAL_SLEEP 1000
94251881Speter#define RETRY_MAX_SLEEP 128000
95251881Speter
96251881Speter#define RETRY_LOOP(err, expr, retry_test, sleep_test)                      \
97251881Speter  do                                                                       \
98251881Speter    {                                                                      \
99251881Speter      apr_status_t os_err = APR_TO_OS_ERROR(err);                          \
100251881Speter      int sleep_count = RETRY_INITIAL_SLEEP;                               \
101251881Speter      int retries;                                                         \
102251881Speter      for (retries = 0;                                                    \
103251881Speter           retries < RETRY_MAX_ATTEMPTS && (retry_test);                   \
104251881Speter           os_err = APR_TO_OS_ERROR(err))                                  \
105251881Speter        {                                                                  \
106251881Speter          if (sleep_test)                                                  \
107251881Speter            {                                                              \
108251881Speter              ++retries;                                                   \
109251881Speter              apr_sleep(sleep_count);                                      \
110251881Speter              if (sleep_count < RETRY_MAX_SLEEP)                           \
111251881Speter                sleep_count *= 2;                                          \
112251881Speter            }                                                              \
113251881Speter          (err) = (expr);                                                  \
114251881Speter        }                                                                  \
115251881Speter    }                                                                      \
116251881Speter  while (0)
117251881Speter
118251881Speter#if defined(EDEADLK) && APR_HAS_THREADS
119251881Speter#define FILE_LOCK_RETRY_LOOP(err, expr)                                    \
120251881Speter  RETRY_LOOP(err,                                                          \
121251881Speter             expr,                                                         \
122251881Speter             (APR_STATUS_IS_EINTR(err) || os_err == EDEADLK),              \
123251881Speter             (!APR_STATUS_IS_EINTR(err)))
124251881Speter#else
125251881Speter#define FILE_LOCK_RETRY_LOOP(err, expr)                                    \
126251881Speter  RETRY_LOOP(err,                                                          \
127251881Speter             expr,                                                         \
128251881Speter             (APR_STATUS_IS_EINTR(err)),                                   \
129251881Speter             0)
130251881Speter#endif
131251881Speter
132251881Speter#ifndef WIN32_RETRY_LOOP
133251881Speter#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP)
134251881Speter#define WIN32_RETRY_LOOP(err, expr)                                        \
135251881Speter  RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED                     \
136251881Speter                         || os_err == ERROR_SHARING_VIOLATION              \
137251881Speter                         || os_err == ERROR_DIR_NOT_EMPTY),                \
138251881Speter             1)
139251881Speter#else
140251881Speter#define WIN32_RETRY_LOOP(err, expr) ((void)0)
141251881Speter#endif
142251881Speter#endif
143251881Speter
144299742Sdim#ifdef WIN32
145299742Sdim
146299742Sdim#if _WIN32_WINNT < 0x600 /* Does the SDK assume Windows Vista+? */
147299742Sdimtypedef struct _FILE_RENAME_INFO {
148299742Sdim  BOOL   ReplaceIfExists;
149299742Sdim  HANDLE RootDirectory;
150299742Sdim  DWORD  FileNameLength;
151299742Sdim  WCHAR  FileName[1];
152299742Sdim} FILE_RENAME_INFO, *PFILE_RENAME_INFO;
153299742Sdim
154299742Sdimtypedef struct _FILE_DISPOSITION_INFO {
155299742Sdim  BOOL DeleteFile;
156299742Sdim} FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO;
157299742Sdim
158299742Sdim#define FileRenameInfo 3
159299742Sdim#define FileDispositionInfo 4
160299742Sdim#endif /* WIN32 < Vista */
161299742Sdim
162299742Sdim/* One-time initialization of the late bound Windows API functions. */
163299742Sdimstatic volatile svn_atomic_t win_dynamic_imports_state = 0;
164299742Sdim
165299742Sdim/* Pointer to GetFinalPathNameByHandleW function from kernel32.dll. */
166299742Sdimtypedef DWORD (WINAPI *GETFINALPATHNAMEBYHANDLE)(
167299742Sdim               HANDLE hFile,
168299742Sdim               WCHAR *lpszFilePath,
169299742Sdim               DWORD cchFilePath,
170299742Sdim               DWORD dwFlags);
171299742Sdim
172299742Sdimtypedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile,
173299742Sdim                                                    int FileInformationClass,
174299742Sdim                                                    LPVOID lpFileInformation,
175299742Sdim                                                    DWORD dwBufferSize);
176299742Sdim
177299742Sdimstatic GETFINALPATHNAMEBYHANDLE get_final_path_name_by_handle_proc = NULL;
178299742Sdimstatic SetFileInformationByHandle_t set_file_information_by_handle_proc = NULL;
179299742Sdim
180299742Sdim/* Forward declaration. */
181299742Sdimstatic svn_error_t * io_win_read_link(svn_string_t **dest,
182299742Sdim                                      const char *path,
183299742Sdim                                      apr_pool_t *pool);
184299742Sdim
185299742Sdim#endif
186299742Sdim
187251881Speter/* Forward declaration */
188251881Speterstatic apr_status_t
189251881Speterdir_is_empty(const char *dir, apr_pool_t *pool);
190251881Speterstatic APR_INLINE svn_error_t *
191251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
192251881Speter                           const char *msg, const char *msg_no_name,
193251881Speter                           apr_pool_t *pool);
194251881Speter
195251881Speter/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on
196251881Speter * operating systems where APR always uses utf-8 as native path format */
197251881Speterstatic svn_error_t *
198251881Spetercstring_to_utf8(const char **path_utf8,
199251881Speter                const char *path_apr,
200251881Speter                apr_pool_t *pool)
201251881Speter{
202251881Speter#if defined(WIN32) || defined(DARWIN)
203251881Speter  *path_utf8 = path_apr;
204251881Speter  return SVN_NO_ERROR;
205251881Speter#else
206251881Speter  return svn_path_cstring_to_utf8(path_utf8, path_apr, pool);
207251881Speter#endif
208251881Speter}
209251881Speter
210251881Speter/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on
211251881Speter * operating systems where APR always uses utf-8 as native path format */
212251881Speterstatic svn_error_t *
213251881Spetercstring_from_utf8(const char **path_apr,
214251881Speter                  const char *path_utf8,
215251881Speter                  apr_pool_t *pool)
216251881Speter{
217251881Speter#if defined(WIN32) || defined(DARWIN)
218251881Speter  *path_apr = path_utf8;
219251881Speter  return SVN_NO_ERROR;
220251881Speter#else
221251881Speter  return svn_path_cstring_from_utf8(path_apr, path_utf8, pool);
222251881Speter#endif
223251881Speter}
224251881Speter
225251881Speter/* Helper function that allows to convert an APR-level PATH to something
226251881Speter * that we can pass the svn_error_wrap_apr. Since we use it in context
227251881Speter * of error reporting, having *some* path info may be more useful than
228251881Speter * having none.  Therefore, we use a best effort approach here.
229251881Speter *
230251881Speter * This is different from svn_io_file_name_get in that it uses a different
231251881Speter * signature style and will never fail.
232251881Speter */
233251881Speterstatic const char *
234251881Spetertry_utf8_from_internal_style(const char *path, apr_pool_t *pool)
235251881Speter{
236251881Speter  svn_error_t *error;
237251881Speter  const char *path_utf8;
238251881Speter
239251881Speter  /* Special case. */
240251881Speter  if (path == NULL)
241251881Speter    return "(NULL)";
242251881Speter
243251881Speter  /* (try to) convert PATH to UTF-8. If that fails, continue with the plain
244251881Speter   * PATH because it is the best we have. It may actually be UTF-8 already.
245251881Speter   */
246251881Speter  error = cstring_to_utf8(&path_utf8, path, pool);
247251881Speter  if (error)
248251881Speter    {
249251881Speter      /* fallback to best representation we have */
250251881Speter
251251881Speter      svn_error_clear(error);
252251881Speter      path_utf8 = path;
253251881Speter    }
254251881Speter
255251881Speter  /* Toggle (back-)slashes etc. as necessary.
256251881Speter   */
257251881Speter  return svn_dirent_local_style(path_utf8, pool);
258251881Speter}
259251881Speter
260251881Speter
261251881Speter/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
262251881Speter * NAME is in the internal encoding used by APR; PARENT is in
263251881Speter * UTF-8 and in internal (not local) style.
264251881Speter *
265251881Speter * Use PARENT only for generating an error string if the conversion
266251881Speter * fails because NAME could not be represented in UTF-8.  In that
267251881Speter * case, return a two-level error in which the outer error's message
268251881Speter * mentions PARENT, but the inner error's message does not mention
269251881Speter * NAME (except possibly in hex) since NAME may not be printable.
270251881Speter * Such a compound error at least allows the user to go looking in the
271251881Speter * right directory for the problem.
272251881Speter *
273251881Speter * If there is any other error, just return that error directly.
274251881Speter *
275251881Speter * If there is any error, the effect on *NAME_P is undefined.
276251881Speter *
277251881Speter * *NAME_P and NAME may refer to the same storage.
278251881Speter */
279251881Speterstatic svn_error_t *
280251881Speterentry_name_to_utf8(const char **name_p,
281251881Speter                   const char *name,
282251881Speter                   const char *parent,
283251881Speter                   apr_pool_t *pool)
284251881Speter{
285251881Speter#if defined(WIN32) || defined(DARWIN)
286251881Speter  *name_p = apr_pstrdup(pool, name);
287251881Speter  return SVN_NO_ERROR;
288251881Speter#else
289251881Speter  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
290251881Speter  if (err && err->apr_err == APR_EINVAL)
291251881Speter    {
292251881Speter      return svn_error_createf(err->apr_err, err,
293251881Speter                               _("Error converting entry "
294251881Speter                                 "in directory '%s' to UTF-8"),
295251881Speter                               svn_dirent_local_style(parent, pool));
296251881Speter    }
297251881Speter  return err;
298251881Speter#endif
299251881Speter}
300251881Speter
301251881Speter
302251881Speter
303251881Speterstatic void
304251881Spetermap_apr_finfo_to_node_kind(svn_node_kind_t *kind,
305251881Speter                           svn_boolean_t *is_special,
306251881Speter                           apr_finfo_t *finfo)
307251881Speter{
308251881Speter  *is_special = FALSE;
309251881Speter
310251881Speter  if (finfo->filetype == APR_REG)
311251881Speter    *kind = svn_node_file;
312251881Speter  else if (finfo->filetype == APR_DIR)
313251881Speter    *kind = svn_node_dir;
314251881Speter  else if (finfo->filetype == APR_LNK)
315251881Speter    {
316251881Speter      *is_special = TRUE;
317251881Speter      *kind = svn_node_file;
318251881Speter    }
319251881Speter  else
320251881Speter    *kind = svn_node_unknown;
321251881Speter}
322251881Speter
323251881Speter/* Helper for svn_io_check_path() and svn_io_check_resolved_path();
324251881Speter   essentially the same semantics as those two, with the obvious
325251881Speter   interpretation for RESOLVE_SYMLINKS. */
326251881Speterstatic svn_error_t *
327251881Speterio_check_path(const char *path,
328251881Speter              svn_boolean_t resolve_symlinks,
329251881Speter              svn_boolean_t *is_special_p,
330251881Speter              svn_node_kind_t *kind,
331251881Speter              apr_pool_t *pool)
332251881Speter{
333251881Speter  apr_int32_t flags;
334251881Speter  apr_finfo_t finfo;
335251881Speter  apr_status_t apr_err;
336251881Speter  const char *path_apr;
337251881Speter  svn_boolean_t is_special = FALSE;
338251881Speter
339251881Speter  if (path[0] == '\0')
340251881Speter    path = ".";
341251881Speter
342251881Speter  /* Not using svn_io_stat() here because we want to check the
343251881Speter     apr_err return explicitly. */
344251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
345251881Speter
346251881Speter  flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
347251881Speter  apr_err = apr_stat(&finfo, path_apr, flags, pool);
348251881Speter
349251881Speter  if (APR_STATUS_IS_ENOENT(apr_err))
350251881Speter    *kind = svn_node_none;
351251881Speter  else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err))
352251881Speter    *kind = svn_node_none;
353251881Speter  else if (apr_err)
354251881Speter    return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"),
355251881Speter                              svn_dirent_local_style(path, pool));
356251881Speter  else
357251881Speter    map_apr_finfo_to_node_kind(kind, &is_special, &finfo);
358251881Speter
359251881Speter  *is_special_p = is_special;
360251881Speter
361251881Speter  return SVN_NO_ERROR;
362251881Speter}
363251881Speter
364251881Speter
365251881Speter/* Wrapper for apr_file_open(), taking an APR-encoded filename. */
366251881Speterstatic apr_status_t
367251881Speterfile_open(apr_file_t **f,
368251881Speter          const char *fname_apr,
369251881Speter          apr_int32_t flag,
370251881Speter          apr_fileperms_t perm,
371251881Speter          svn_boolean_t retry_on_failure,
372251881Speter          apr_pool_t *pool)
373251881Speter{
374251881Speter  apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool);
375251881Speter
376251881Speter  if (retry_on_failure)
377251881Speter    {
378299742Sdim#ifdef WIN32
379299742Sdim      if (status == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED))
380299742Sdim        {
381299742Sdim          if ((flag & (APR_CREATE | APR_EXCL)) == (APR_CREATE | APR_EXCL))
382299742Sdim            return status; /* Can't create if there is something */
383299742Sdim
384299742Sdim          if (flag & (APR_WRITE | APR_CREATE))
385299742Sdim            {
386299742Sdim              apr_finfo_t finfo;
387299742Sdim
388299742Sdim              if (!apr_stat(&finfo, fname_apr, SVN__APR_FINFO_READONLY, pool))
389299742Sdim                {
390299742Sdim                  if (finfo.protection & APR_FREADONLY)
391299742Sdim                    return status; /* Retrying won't fix this */
392299742Sdim                }
393299742Sdim            }
394299742Sdim        }
395299742Sdim#endif
396299742Sdim
397251881Speter      WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool));
398251881Speter    }
399251881Speter  return status;
400251881Speter}
401251881Speter
402251881Speter
403251881Spetersvn_error_t *
404251881Spetersvn_io_check_resolved_path(const char *path,
405251881Speter                           svn_node_kind_t *kind,
406251881Speter                           apr_pool_t *pool)
407251881Speter{
408251881Speter  svn_boolean_t ignored;
409251881Speter  return io_check_path(path, TRUE, &ignored, kind, pool);
410251881Speter}
411251881Speter
412251881Spetersvn_error_t *
413251881Spetersvn_io_check_path(const char *path,
414251881Speter                  svn_node_kind_t *kind,
415251881Speter                  apr_pool_t *pool)
416251881Speter{
417251881Speter  svn_boolean_t ignored;
418251881Speter  return io_check_path(path, FALSE, &ignored, kind, pool);
419251881Speter}
420251881Speter
421251881Spetersvn_error_t *
422251881Spetersvn_io_check_special_path(const char *path,
423251881Speter                          svn_node_kind_t *kind,
424251881Speter                          svn_boolean_t *is_special,
425251881Speter                          apr_pool_t *pool)
426251881Speter{
427251881Speter  return io_check_path(path, FALSE, is_special, kind, pool);
428251881Speter}
429251881Speter
430251881Speterstruct temp_file_cleanup_s
431251881Speter{
432251881Speter  apr_pool_t *pool;
433251881Speter  /* The (APR-encoded) full path of the file to be removed, or NULL if
434251881Speter   * nothing to do. */
435251881Speter  const char *fname_apr;
436251881Speter};
437251881Speter
438251881Speter
439251881Speterstatic apr_status_t
440251881Spetertemp_file_plain_cleanup_handler(void *baton)
441251881Speter{
442251881Speter  struct  temp_file_cleanup_s *b = baton;
443251881Speter  apr_status_t apr_err = APR_SUCCESS;
444251881Speter
445251881Speter  if (b->fname_apr)
446251881Speter    {
447251881Speter      apr_err = apr_file_remove(b->fname_apr, b->pool);
448251881Speter      WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool));
449251881Speter    }
450251881Speter
451251881Speter  return apr_err;
452251881Speter}
453251881Speter
454251881Speter
455251881Speterstatic apr_status_t
456251881Spetertemp_file_child_cleanup_handler(void *baton)
457251881Speter{
458251881Speter  struct  temp_file_cleanup_s *b = baton;
459251881Speter
460251881Speter  apr_pool_cleanup_kill(b->pool, b,
461251881Speter                        temp_file_plain_cleanup_handler);
462251881Speter
463251881Speter  return APR_SUCCESS;
464251881Speter}
465251881Speter
466251881Speter
467251881Spetersvn_error_t *
468251881Spetersvn_io_open_uniquely_named(apr_file_t **file,
469251881Speter                           const char **unique_path,
470251881Speter                           const char *dirpath,
471251881Speter                           const char *filename,
472251881Speter                           const char *suffix,
473251881Speter                           svn_io_file_del_t delete_when,
474251881Speter                           apr_pool_t *result_pool,
475251881Speter                           apr_pool_t *scratch_pool)
476251881Speter{
477251881Speter  const char *path;
478251881Speter  unsigned int i;
479251881Speter  struct temp_file_cleanup_s *baton = NULL;
480251881Speter
481251881Speter  /* At the beginning, we don't know whether unique_path will need
482251881Speter     UTF8 conversion */
483251881Speter  svn_boolean_t needs_utf8_conversion = TRUE;
484251881Speter
485251881Speter  SVN_ERR_ASSERT(file || unique_path);
486251881Speter
487251881Speter  if (dirpath == NULL)
488251881Speter    SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
489251881Speter  if (filename == NULL)
490251881Speter    filename = "tempfile";
491251881Speter  if (suffix == NULL)
492251881Speter    suffix = ".tmp";
493251881Speter
494251881Speter  path = svn_dirent_join(dirpath, filename, scratch_pool);
495251881Speter
496251881Speter  if (delete_when == svn_io_file_del_on_pool_cleanup)
497251881Speter    {
498251881Speter      baton = apr_palloc(result_pool, sizeof(*baton));
499251881Speter
500251881Speter      baton->pool = result_pool;
501251881Speter      baton->fname_apr = NULL;
502251881Speter
503251881Speter      /* Because cleanups are run LIFO, we need to make sure to register
504251881Speter         our cleanup before the apr_file_close cleanup:
505251881Speter
506251881Speter         On Windows, you can't remove an open file.
507251881Speter      */
508251881Speter      apr_pool_cleanup_register(result_pool, baton,
509251881Speter                                temp_file_plain_cleanup_handler,
510251881Speter                                temp_file_child_cleanup_handler);
511251881Speter    }
512251881Speter
513251881Speter  for (i = 1; i <= 99999; i++)
514251881Speter    {
515251881Speter      const char *unique_name;
516251881Speter      const char *unique_name_apr;
517251881Speter      apr_file_t *try_file;
518251881Speter      apr_status_t apr_err;
519251881Speter      apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL
520251881Speter                          | APR_BUFFERED | APR_BINARY);
521251881Speter
522251881Speter      if (delete_when == svn_io_file_del_on_close)
523251881Speter        flag |= APR_DELONCLOSE;
524251881Speter
525251881Speter      /* Special case the first attempt -- if we can avoid having a
526251881Speter         generated numeric portion at all, that's best.  So first we
527251881Speter         try with just the suffix; then future tries add a number
528251881Speter         before the suffix.  (A do-while loop could avoid the repeated
529251881Speter         conditional, but it's not worth the clarity loss.)
530251881Speter
531251881Speter         If the first attempt fails, the first number will be "2".
532251881Speter         This is good, since "1" would misleadingly imply that
533251881Speter         the second attempt was actually the first... and if someone's
534251881Speter         got conflicts on their conflicts, we probably don't want to
535251881Speter         add to their confusion :-). */
536251881Speter      if (i == 1)
537251881Speter        unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix);
538251881Speter      else
539251881Speter        unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix);
540251881Speter
541251881Speter      /* Hmmm.  Ideally, we would append to a native-encoding buf
542251881Speter         before starting iteration, then convert back to UTF-8 for
543251881Speter         return. But I suppose that would make the appending code
544251881Speter         sensitive to i18n in a way it shouldn't be... Oh well. */
545251881Speter      if (needs_utf8_conversion)
546251881Speter        {
547251881Speter          SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name,
548251881Speter                                    scratch_pool));
549251881Speter          if (i == 1)
550251881Speter            {
551251881Speter              /* The variable parts of unique_name will not require UTF8
552251881Speter                 conversion. Therefore, if UTF8 conversion had no effect
553251881Speter                 on it in the first iteration, it won't require conversion
554251881Speter                 in any future iteration. */
555251881Speter              needs_utf8_conversion = strcmp(unique_name_apr, unique_name);
556251881Speter            }
557251881Speter        }
558251881Speter      else
559251881Speter        unique_name_apr = unique_name;
560251881Speter
561251881Speter      apr_err = file_open(&try_file, unique_name_apr, flag,
562251881Speter                          APR_OS_DEFAULT, FALSE, result_pool);
563251881Speter
564251881Speter      if (APR_STATUS_IS_EEXIST(apr_err))
565251881Speter        continue;
566251881Speter      else if (apr_err)
567251881Speter        {
568251881Speter          /* On Win32, CreateFile fails with an "Access Denied" error
569251881Speter             code, rather than "File Already Exists", if the colliding
570251881Speter             name belongs to a directory. */
571251881Speter          if (APR_STATUS_IS_EACCES(apr_err))
572251881Speter            {
573251881Speter              apr_finfo_t finfo;
574251881Speter              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
575251881Speter                                                APR_FINFO_TYPE, scratch_pool);
576251881Speter
577251881Speter              if (!apr_err_2 && finfo.filetype == APR_DIR)
578251881Speter                continue;
579251881Speter
580251881Speter#ifdef WIN32
581251881Speter              apr_err_2 = APR_TO_OS_ERROR(apr_err);
582251881Speter
583251881Speter              if (apr_err_2 == ERROR_ACCESS_DENIED ||
584251881Speter                  apr_err_2 == ERROR_SHARING_VIOLATION)
585251881Speter                {
586251881Speter                  /* The file is in use by another process or is hidden;
587251881Speter                     create a new name, but don't do this 99999 times in
588251881Speter                     case the folder is not writable */
589251881Speter                  i += 797;
590251881Speter                  continue;
591251881Speter                }
592251881Speter#endif
593251881Speter
594251881Speter              /* Else fall through and return the original error. */
595251881Speter            }
596251881Speter
597251881Speter          if (file)
598251881Speter            *file = NULL;
599251881Speter          if (unique_path)
600251881Speter            *unique_path = NULL;
601251881Speter          return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
602251881Speter                                    svn_dirent_local_style(unique_name,
603251881Speter                                                         scratch_pool));
604251881Speter        }
605251881Speter      else
606251881Speter        {
607251881Speter          if (delete_when == svn_io_file_del_on_pool_cleanup)
608251881Speter            baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr);
609251881Speter
610251881Speter          if (file)
611251881Speter            *file = try_file;
612251881Speter          else
613251881Speter            apr_file_close(try_file);
614251881Speter          if (unique_path)
615251881Speter            *unique_path = apr_pstrdup(result_pool, unique_name);
616251881Speter
617251881Speter          return SVN_NO_ERROR;
618251881Speter        }
619251881Speter    }
620251881Speter
621251881Speter  if (file)
622251881Speter    *file = NULL;
623251881Speter  if (unique_path)
624251881Speter    *unique_path = NULL;
625251881Speter  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
626251881Speter                           NULL,
627251881Speter                           _("Unable to make name for '%s'"),
628251881Speter                           svn_dirent_local_style(path, scratch_pool));
629251881Speter}
630251881Speter
631251881Spetersvn_error_t *
632251881Spetersvn_io_create_unique_link(const char **unique_name_p,
633251881Speter                          const char *path,
634251881Speter                          const char *dest,
635251881Speter                          const char *suffix,
636251881Speter                          apr_pool_t *pool)
637251881Speter{
638251881Speter#ifdef HAVE_SYMLINK
639251881Speter  unsigned int i;
640251881Speter  const char *unique_name;
641251881Speter  const char *unique_name_apr;
642251881Speter  const char *dest_apr;
643251881Speter  int rv;
644251881Speter
645251881Speter  SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool));
646251881Speter  for (i = 1; i <= 99999; i++)
647251881Speter    {
648251881Speter      apr_status_t apr_err;
649251881Speter
650251881Speter      /* Special case the first attempt -- if we can avoid having a
651251881Speter         generated numeric portion at all, that's best.  So first we
652251881Speter         try with just the suffix; then future tries add a number
653251881Speter         before the suffix.  (A do-while loop could avoid the repeated
654251881Speter         conditional, but it's not worth the clarity loss.)
655251881Speter
656251881Speter         If the first attempt fails, the first number will be "2".
657251881Speter         This is good, since "1" would misleadingly imply that
658251881Speter         the second attempt was actually the first... and if someone's
659251881Speter         got conflicts on their conflicts, we probably don't want to
660251881Speter         add to their confusion :-). */
661251881Speter      if (i == 1)
662251881Speter        unique_name = apr_psprintf(pool, "%s%s", path, suffix);
663251881Speter      else
664251881Speter        unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
665251881Speter
666251881Speter      /* Hmmm.  Ideally, we would append to a native-encoding buf
667251881Speter         before starting iteration, then convert back to UTF-8 for
668251881Speter         return. But I suppose that would make the appending code
669251881Speter         sensitive to i18n in a way it shouldn't be... Oh well. */
670251881Speter      SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool));
671251881Speter      do {
672251881Speter        rv = symlink(dest_apr, unique_name_apr);
673251881Speter      } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
674251881Speter
675251881Speter      apr_err = apr_get_os_error();
676251881Speter
677251881Speter      if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err))
678251881Speter        continue;
679251881Speter      else if (rv == -1 && apr_err)
680251881Speter        {
681251881Speter          /* On Win32, CreateFile fails with an "Access Denied" error
682251881Speter             code, rather than "File Already Exists", if the colliding
683251881Speter             name belongs to a directory. */
684251881Speter          if (APR_STATUS_IS_EACCES(apr_err))
685251881Speter            {
686251881Speter              apr_finfo_t finfo;
687251881Speter              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
688251881Speter                                                APR_FINFO_TYPE, pool);
689251881Speter
690251881Speter              if (!apr_err_2
691251881Speter                  && (finfo.filetype == APR_DIR))
692251881Speter                continue;
693251881Speter
694251881Speter              /* Else ignore apr_err_2; better to fall through and
695251881Speter                 return the original error. */
696251881Speter            }
697251881Speter
698251881Speter          *unique_name_p = NULL;
699251881Speter          return svn_error_wrap_apr(apr_err,
700251881Speter                                    _("Can't create symbolic link '%s'"),
701251881Speter                                    svn_dirent_local_style(unique_name, pool));
702251881Speter        }
703251881Speter      else
704251881Speter        {
705251881Speter          *unique_name_p = unique_name;
706251881Speter          return SVN_NO_ERROR;
707251881Speter        }
708251881Speter    }
709251881Speter
710251881Speter  *unique_name_p = NULL;
711251881Speter  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
712251881Speter                           NULL,
713251881Speter                           _("Unable to make name for '%s'"),
714251881Speter                           svn_dirent_local_style(path, pool));
715251881Speter#else
716251881Speter  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
717251881Speter                          _("Symbolic links are not supported on this "
718251881Speter                            "platform"));
719251881Speter#endif
720251881Speter}
721251881Speter
722251881Spetersvn_error_t *
723251881Spetersvn_io_read_link(svn_string_t **dest,
724251881Speter                 const char *path,
725251881Speter                 apr_pool_t *pool)
726251881Speter{
727299742Sdim#if defined(HAVE_READLINK)
728251881Speter  svn_string_t dest_apr;
729251881Speter  const char *path_apr;
730251881Speter  char buf[1025];
731251881Speter  ssize_t rv;
732251881Speter
733251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
734251881Speter  do {
735251881Speter    rv = readlink(path_apr, buf, sizeof(buf) - 1);
736251881Speter  } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
737251881Speter
738251881Speter  if (rv == -1)
739251881Speter    return svn_error_wrap_apr(apr_get_os_error(),
740251881Speter                              _("Can't read contents of link"));
741251881Speter
742251881Speter  buf[rv] = '\0';
743251881Speter  dest_apr.data = buf;
744251881Speter  dest_apr.len = rv;
745251881Speter
746251881Speter  /* ### Cast needed, one of these interfaces is wrong */
747251881Speter  return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool);
748299742Sdim#elif defined(WIN32)
749299742Sdim  return io_win_read_link(dest, path, pool);
750251881Speter#else
751251881Speter  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
752251881Speter                          _("Symbolic links are not supported on this "
753251881Speter                            "platform"));
754251881Speter#endif
755251881Speter}
756251881Speter
757251881Speter
758251881Spetersvn_error_t *
759251881Spetersvn_io_copy_link(const char *src,
760251881Speter                 const char *dst,
761251881Speter                 apr_pool_t *pool)
762251881Speter
763251881Speter{
764251881Speter#ifdef HAVE_READLINK
765251881Speter  svn_string_t *link_dest;
766251881Speter  const char *dst_tmp;
767251881Speter
768251881Speter  /* Notice what the link is pointing at... */
769251881Speter  SVN_ERR(svn_io_read_link(&link_dest, src, pool));
770251881Speter
771251881Speter  /* Make a tmp-link pointing at the same thing. */
772251881Speter  SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data,
773251881Speter                                    ".tmp", pool));
774251881Speter
775251881Speter  /* Move the tmp-link to link. */
776251881Speter  return svn_io_file_rename(dst_tmp, dst, pool);
777251881Speter
778251881Speter#else
779251881Speter  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
780251881Speter                          _("Symbolic links are not supported on this "
781251881Speter                            "platform"));
782251881Speter#endif
783251881Speter}
784251881Speter
785251881Speter/* Temporary directory name cache for svn_io_temp_dir() */
786251881Speterstatic volatile svn_atomic_t temp_dir_init_state = 0;
787251881Speterstatic const char *temp_dir;
788251881Speter
789251881Speter/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */
790251881Speterstatic svn_error_t *
791251881Speterinit_temp_dir(void *baton, apr_pool_t *scratch_pool)
792251881Speter{
793251881Speter  /* Global pool for the temp path */
794251881Speter  apr_pool_t *global_pool = svn_pool_create(NULL);
795251881Speter  const char *dir;
796251881Speter
797251881Speter  apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool);
798251881Speter
799251881Speter  if (apr_err)
800251881Speter    return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory"));
801251881Speter
802251881Speter  SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool));
803251881Speter
804251881Speter  dir = svn_dirent_internal_style(dir, scratch_pool);
805251881Speter
806251881Speter  SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool));
807251881Speter
808251881Speter  return SVN_NO_ERROR;
809251881Speter}
810251881Speter
811251881Speter
812251881Spetersvn_error_t *
813251881Spetersvn_io_temp_dir(const char **dir,
814251881Speter                apr_pool_t *pool)
815251881Speter{
816251881Speter  SVN_ERR(svn_atomic__init_once(&temp_dir_init_state,
817251881Speter                                init_temp_dir, NULL, pool));
818251881Speter
819251881Speter  *dir = apr_pstrdup(pool, temp_dir);
820251881Speter
821251881Speter  return SVN_NO_ERROR;
822251881Speter}
823251881Speter
824251881Speter
825251881Speter
826251881Speter
827251881Speter/*** Creating, copying and appending files. ***/
828251881Speter
829251881Speter/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary
830251881Speter * allocations.
831251881Speter *
832251881Speter * NOTE: We don't use apr_copy_file() for this, since it takes filenames
833251881Speter * as parameters.  Since we want to copy to a temporary file
834251881Speter * and rename for atomicity (see below), this would require an extra
835251881Speter * close/open pair, which can be expensive, especially on
836251881Speter * remote file systems.
837251881Speter */
838251881Speterstatic apr_status_t
839251881Spetercopy_contents(apr_file_t *from_file,
840251881Speter              apr_file_t *to_file,
841251881Speter              apr_pool_t *pool)
842251881Speter{
843251881Speter  /* Copy bytes till the cows come home. */
844251881Speter  while (1)
845251881Speter    {
846251881Speter      char buf[SVN__STREAM_CHUNK_SIZE];
847251881Speter      apr_size_t bytes_this_time = sizeof(buf);
848251881Speter      apr_status_t read_err;
849251881Speter      apr_status_t write_err;
850251881Speter
851251881Speter      /* Read 'em. */
852251881Speter      read_err = apr_file_read(from_file, buf, &bytes_this_time);
853251881Speter      if (read_err && !APR_STATUS_IS_EOF(read_err))
854251881Speter        {
855251881Speter          return read_err;
856251881Speter        }
857251881Speter
858251881Speter      /* Write 'em. */
859251881Speter      write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL);
860251881Speter      if (write_err)
861251881Speter        {
862251881Speter          return write_err;
863251881Speter        }
864251881Speter
865251881Speter      if (read_err && APR_STATUS_IS_EOF(read_err))
866251881Speter        {
867251881Speter          /* Return the results of this close: an error, or success. */
868251881Speter          return APR_SUCCESS;
869251881Speter        }
870251881Speter    }
871251881Speter  /* NOTREACHED */
872251881Speter}
873251881Speter
874251881Speter
875251881Spetersvn_error_t *
876251881Spetersvn_io_copy_file(const char *src,
877251881Speter                 const char *dst,
878251881Speter                 svn_boolean_t copy_perms,
879251881Speter                 apr_pool_t *pool)
880251881Speter{
881251881Speter  apr_file_t *from_file, *to_file;
882251881Speter  apr_status_t apr_err;
883251881Speter  const char *dst_tmp;
884251881Speter  svn_error_t *err;
885251881Speter
886251881Speter  /* ### NOTE: sometimes src == dst. In this case, because we copy to a
887251881Speter     ###   temporary file, and then rename over the top of the destination,
888251881Speter     ###   the net result is resetting the permissions on src/dst.
889251881Speter     ###
890251881Speter     ### Note: specifically, this can happen during a switch when the desired
891251881Speter     ###   permissions for a file change from one branch to another. See
892251881Speter     ###   switch_tests 17.
893251881Speter     ###
894251881Speter     ### ... yes, we should avoid copying to the same file, and we should
895251881Speter     ###     make the "reset perms" explicit. The switch *happens* to work
896251881Speter     ###     because of this copy-to-temp-then-rename implementation. If it
897251881Speter     ###     weren't for that, the switch would break.
898251881Speter  */
899251881Speter#ifdef CHECK_FOR_SAME_FILE
900251881Speter  if (strcmp(src, dst) == 0)
901251881Speter    return SVN_NO_ERROR;
902251881Speter#endif
903251881Speter
904251881Speter  SVN_ERR(svn_io_file_open(&from_file, src, APR_READ,
905251881Speter                           APR_OS_DEFAULT, pool));
906251881Speter
907251881Speter  /* For atomicity, we copy to a tmp file and then rename the tmp
908251881Speter     file over the real destination. */
909251881Speter
910251881Speter  SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp,
911251881Speter                                   svn_dirent_dirname(dst, pool),
912251881Speter                                   svn_io_file_del_none, pool, pool));
913251881Speter
914251881Speter  apr_err = copy_contents(from_file, to_file, pool);
915251881Speter
916251881Speter  if (apr_err)
917251881Speter    {
918251881Speter      err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"),
919251881Speter                               svn_dirent_local_style(src, pool),
920251881Speter                               svn_dirent_local_style(dst_tmp, pool));
921251881Speter    }
922251881Speter   else
923251881Speter     err = NULL;
924251881Speter
925251881Speter  err = svn_error_compose_create(err,
926251881Speter                                 svn_io_file_close(from_file, pool));
927251881Speter
928251881Speter  err = svn_error_compose_create(err,
929251881Speter                                 svn_io_file_close(to_file, pool));
930251881Speter
931251881Speter  if (err)
932251881Speter    {
933251881Speter      return svn_error_compose_create(
934251881Speter                                 err,
935251881Speter                                 svn_io_remove_file2(dst_tmp, TRUE, pool));
936251881Speter    }
937251881Speter
938251881Speter  /* If copying perms, set the perms on dst_tmp now, so they will be
939251881Speter     atomically inherited in the upcoming rename.  But note that we
940251881Speter     had to wait until now to set perms, because if they say
941251881Speter     read-only, then we'd have failed filling dst_tmp's contents. */
942251881Speter  if (copy_perms)
943251881Speter    SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool));
944251881Speter
945251881Speter  return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool));
946251881Speter}
947251881Speter
948251881Speter#if !defined(WIN32) && !defined(__OS2__)
949251881Speter/* Wrapper for apr_file_perms_set(), taking a UTF8-encoded filename. */
950251881Speterstatic svn_error_t *
951251881Speterfile_perms_set(const char *fname, apr_fileperms_t perms,
952251881Speter               apr_pool_t *pool)
953251881Speter{
954251881Speter  const char *fname_apr;
955251881Speter  apr_status_t status;
956251881Speter
957251881Speter  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
958251881Speter
959251881Speter  status = apr_file_perms_set(fname_apr, perms);
960251881Speter  if (status)
961251881Speter    return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
962251881Speter                              fname);
963251881Speter  else
964251881Speter    return SVN_NO_ERROR;
965251881Speter}
966251881Speter
967251881Speter/* Set permissions PERMS on the FILE. This is a cheaper variant of the
968251881Speter * file_perms_set wrapper() function because no locale-dependent string
969251881Speter * conversion is required. POOL will be used for allocations.
970251881Speter */
971251881Speterstatic svn_error_t *
972251881Speterfile_perms_set2(apr_file_t* file, apr_fileperms_t perms, apr_pool_t *pool)
973251881Speter{
974251881Speter  const char *fname_apr;
975251881Speter  apr_status_t status;
976251881Speter
977251881Speter  status = apr_file_name_get(&fname_apr, file);
978251881Speter  if (status)
979251881Speter    return svn_error_wrap_apr(status, _("Can't get file name"));
980251881Speter
981251881Speter  status = apr_file_perms_set(fname_apr, perms);
982251881Speter  if (status)
983251881Speter    return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
984251881Speter                              try_utf8_from_internal_style(fname_apr, pool));
985251881Speter  else
986251881Speter    return SVN_NO_ERROR;
987251881Speter}
988251881Speter
989251881Speter#endif /* !WIN32 && !__OS2__ */
990251881Speter
991251881Spetersvn_error_t *
992251881Spetersvn_io_copy_perms(const char *src,
993251881Speter                  const char *dst,
994251881Speter                  apr_pool_t *pool)
995251881Speter{
996251881Speter  /* ### On Windows or OS/2, apr_file_perms_set always returns APR_ENOTIMPL,
997251881Speter         and the path passed to apr_file_perms_set must be encoded
998251881Speter         in the platform-specific path encoding; not necessary UTF-8.
999251881Speter         We need a platform-specific implementation to get the
1000251881Speter         permissions right. */
1001251881Speter
1002251881Speter#if !defined(WIN32) && !defined(__OS2__)
1003251881Speter  {
1004251881Speter    apr_finfo_t finfo;
1005251881Speter    svn_node_kind_t kind;
1006251881Speter    svn_boolean_t is_special;
1007251881Speter    svn_error_t *err;
1008251881Speter
1009251881Speter    /* If DST is a symlink, don't bother copying permissions. */
1010251881Speter    SVN_ERR(svn_io_check_special_path(dst, &kind, &is_special, pool));
1011251881Speter    if (is_special)
1012251881Speter      return SVN_NO_ERROR;
1013251881Speter
1014251881Speter    SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_PROT, pool));
1015251881Speter    err = file_perms_set(dst, finfo.protection, pool);
1016251881Speter    if (err)
1017251881Speter      {
1018251881Speter        /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL
1019251881Speter           here under normal circumstances, because the perms themselves
1020251881Speter           came from a call to apr_file_info_get(), and we already know
1021251881Speter           this is the non-Win32 case.  But if it does happen, it's not
1022251881Speter           an error. */
1023251881Speter        if (APR_STATUS_IS_INCOMPLETE(err->apr_err) ||
1024251881Speter            APR_STATUS_IS_ENOTIMPL(err->apr_err))
1025251881Speter          svn_error_clear(err);
1026251881Speter        else
1027251881Speter          {
1028299742Sdim            return svn_error_quick_wrapf(
1029299742Sdim                     err, _("Can't set permissions on '%s'"),
1030299742Sdim                     svn_dirent_local_style(dst, pool));
1031251881Speter          }
1032251881Speter      }
1033251881Speter  }
1034251881Speter#endif /* !WIN32 && !__OS2__ */
1035251881Speter
1036251881Speter  return SVN_NO_ERROR;
1037251881Speter}
1038251881Speter
1039251881Speter
1040251881Spetersvn_error_t *
1041251881Spetersvn_io_append_file(const char *src, const char *dst, apr_pool_t *pool)
1042251881Speter{
1043251881Speter  apr_status_t apr_err;
1044251881Speter  const char *src_apr, *dst_apr;
1045251881Speter
1046251881Speter  SVN_ERR(cstring_from_utf8(&src_apr, src, pool));
1047251881Speter  SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool));
1048251881Speter
1049251881Speter  apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool);
1050251881Speter
1051251881Speter  if (apr_err)
1052251881Speter    return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"),
1053251881Speter                              svn_dirent_local_style(src, pool),
1054251881Speter                              svn_dirent_local_style(dst, pool));
1055251881Speter
1056251881Speter  return SVN_NO_ERROR;
1057251881Speter}
1058251881Speter
1059251881Speter
1060251881Spetersvn_error_t *svn_io_copy_dir_recursively(const char *src,
1061251881Speter                                         const char *dst_parent,
1062251881Speter                                         const char *dst_basename,
1063251881Speter                                         svn_boolean_t copy_perms,
1064251881Speter                                         svn_cancel_func_t cancel_func,
1065251881Speter                                         void *cancel_baton,
1066251881Speter                                         apr_pool_t *pool)
1067251881Speter{
1068251881Speter  svn_node_kind_t kind;
1069251881Speter  apr_status_t status;
1070251881Speter  const char *dst_path;
1071251881Speter  apr_dir_t *this_dir;
1072251881Speter  apr_finfo_t this_entry;
1073251881Speter  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
1074251881Speter
1075251881Speter  /* Make a subpool for recursion */
1076251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1077251881Speter
1078251881Speter  /* The 'dst_path' is simply dst_parent/dst_basename */
1079251881Speter  dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
1080251881Speter
1081251881Speter  /* Sanity checks:  SRC and DST_PARENT are directories, and
1082251881Speter     DST_BASENAME doesn't already exist in DST_PARENT. */
1083251881Speter  SVN_ERR(svn_io_check_path(src, &kind, subpool));
1084251881Speter  if (kind != svn_node_dir)
1085251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1086251881Speter                             _("Source '%s' is not a directory"),
1087251881Speter                             svn_dirent_local_style(src, pool));
1088251881Speter
1089251881Speter  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
1090251881Speter  if (kind != svn_node_dir)
1091251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1092251881Speter                             _("Destination '%s' is not a directory"),
1093251881Speter                             svn_dirent_local_style(dst_parent, pool));
1094251881Speter
1095251881Speter  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
1096251881Speter  if (kind != svn_node_none)
1097251881Speter    return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
1098251881Speter                             _("Destination '%s' already exists"),
1099251881Speter                             svn_dirent_local_style(dst_path, pool));
1100251881Speter
1101251881Speter  /* Create the new directory. */
1102251881Speter  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
1103251881Speter  SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool));
1104251881Speter
1105251881Speter  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
1106251881Speter  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
1107251881Speter
1108251881Speter  for (status = apr_dir_read(&this_entry, flags, this_dir);
1109251881Speter       status == APR_SUCCESS;
1110251881Speter       status = apr_dir_read(&this_entry, flags, this_dir))
1111251881Speter    {
1112251881Speter      if ((this_entry.name[0] == '.')
1113251881Speter          && ((this_entry.name[1] == '\0')
1114251881Speter              || ((this_entry.name[1] == '.')
1115251881Speter                  && (this_entry.name[2] == '\0'))))
1116251881Speter        {
1117251881Speter          continue;
1118251881Speter        }
1119251881Speter      else
1120251881Speter        {
1121251881Speter          const char *src_target, *entryname_utf8;
1122251881Speter
1123251881Speter          if (cancel_func)
1124251881Speter            SVN_ERR(cancel_func(cancel_baton));
1125251881Speter
1126251881Speter          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
1127251881Speter                                     src, subpool));
1128251881Speter          src_target = svn_dirent_join(src, entryname_utf8, subpool);
1129251881Speter
1130251881Speter          if (this_entry.filetype == APR_REG) /* regular file */
1131251881Speter            {
1132251881Speter              const char *dst_target = svn_dirent_join(dst_path,
1133251881Speter                                                       entryname_utf8,
1134251881Speter                                                       subpool);
1135251881Speter              SVN_ERR(svn_io_copy_file(src_target, dst_target,
1136251881Speter                                       copy_perms, subpool));
1137251881Speter            }
1138251881Speter          else if (this_entry.filetype == APR_LNK) /* symlink */
1139251881Speter            {
1140251881Speter              const char *dst_target = svn_dirent_join(dst_path,
1141251881Speter                                                       entryname_utf8,
1142251881Speter                                                       subpool);
1143251881Speter              SVN_ERR(svn_io_copy_link(src_target, dst_target,
1144251881Speter                                       subpool));
1145251881Speter            }
1146251881Speter          else if (this_entry.filetype == APR_DIR) /* recurse */
1147251881Speter            {
1148251881Speter              /* Prevent infinite recursion by filtering off our
1149251881Speter                 newly created destination path. */
1150251881Speter              if (strcmp(src, dst_parent) == 0
1151251881Speter                  && strcmp(entryname_utf8, dst_basename) == 0)
1152251881Speter                continue;
1153251881Speter
1154251881Speter              SVN_ERR(svn_io_copy_dir_recursively
1155251881Speter                      (src_target,
1156251881Speter                       dst_path,
1157251881Speter                       entryname_utf8,
1158251881Speter                       copy_perms,
1159251881Speter                       cancel_func,
1160251881Speter                       cancel_baton,
1161251881Speter                       subpool));
1162251881Speter            }
1163251881Speter          /* ### support other APR node types someday?? */
1164251881Speter
1165251881Speter        }
1166251881Speter    }
1167251881Speter
1168251881Speter  if (! (APR_STATUS_IS_ENOENT(status)))
1169251881Speter    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
1170251881Speter                              svn_dirent_local_style(src, pool));
1171251881Speter
1172251881Speter  status = apr_dir_close(this_dir);
1173251881Speter  if (status)
1174251881Speter    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
1175251881Speter                              svn_dirent_local_style(src, pool));
1176251881Speter
1177251881Speter  /* Free any memory used by recursion */
1178251881Speter  svn_pool_destroy(subpool);
1179251881Speter
1180251881Speter  return SVN_NO_ERROR;
1181251881Speter}
1182251881Speter
1183251881Speter
1184251881Spetersvn_error_t *
1185251881Spetersvn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
1186251881Speter{
1187251881Speter  const char *path_apr;
1188251881Speter  apr_status_t apr_err;
1189251881Speter
1190251881Speter  if (svn_path_is_empty(path))
1191251881Speter    /* Empty path (current dir) is assumed to always exist,
1192251881Speter       so we do nothing, per docs. */
1193251881Speter    return SVN_NO_ERROR;
1194251881Speter
1195251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1196251881Speter
1197251881Speter  apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool);
1198299742Sdim#ifdef WIN32
1199299742Sdim  /* Don't retry on ERROR_ACCESS_DENIED, as that typically signals a
1200299742Sdim     permanent error */
1201299742Sdim  if (apr_err == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION))
1202299742Sdim    WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr,
1203299742Sdim                                                     APR_OS_DEFAULT, pool));
1204299742Sdim#endif
1205251881Speter
1206251881Speter  if (apr_err)
1207251881Speter    return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"),
1208251881Speter                              svn_dirent_local_style(path, pool));
1209251881Speter
1210251881Speter  return SVN_NO_ERROR;
1211251881Speter}
1212251881Speter
1213299742Sdimsvn_error_t *
1214299742Sdimsvn_io_file_create_bytes(const char *file,
1215299742Sdim                         const void *contents,
1216299742Sdim                         apr_size_t length,
1217299742Sdim                         apr_pool_t *scratch_pool)
1218251881Speter{
1219251881Speter  apr_file_t *f;
1220251881Speter  apr_size_t written;
1221251881Speter  svn_error_t *err = SVN_NO_ERROR;
1222251881Speter
1223251881Speter  SVN_ERR(svn_io_file_open(&f, file,
1224251881Speter                           (APR_WRITE | APR_CREATE | APR_EXCL),
1225251881Speter                           APR_OS_DEFAULT,
1226299742Sdim                           scratch_pool));
1227299742Sdim  if (length)
1228299742Sdim    err = svn_io_file_write_full(f, contents, length, &written,
1229299742Sdim                                 scratch_pool);
1230251881Speter
1231299742Sdim  err = svn_error_compose_create(
1232299742Sdim                    err,
1233299742Sdim                    svn_io_file_close(f, scratch_pool));
1234251881Speter
1235299742Sdim  if (err)
1236299742Sdim    {
1237299742Sdim      /* Our caller doesn't know if we left a file or not if we return
1238299742Sdim         an error. Better to cleanup after ourselves if we created the
1239299742Sdim         file. */
1240299742Sdim      return svn_error_trace(
1241299742Sdim                svn_error_compose_create(
1242299742Sdim                    err,
1243299742Sdim                    svn_io_remove_file2(file, TRUE, scratch_pool)));
1244299742Sdim    }
1245299742Sdim
1246299742Sdim  return SVN_NO_ERROR;
1247251881Speter}
1248251881Speter
1249299742Sdimsvn_error_t *
1250299742Sdimsvn_io_file_create(const char *file,
1251299742Sdim                   const char *contents,
1252299742Sdim                   apr_pool_t *pool)
1253251881Speter{
1254299742Sdim  return svn_error_trace(svn_io_file_create_bytes(file, contents,
1255299742Sdim                                                  contents ? strlen(contents)
1256299742Sdim                                                           : 0,
1257299742Sdim                                                  pool));
1258299742Sdim}
1259299742Sdim
1260299742Sdimsvn_error_t *
1261299742Sdimsvn_io_file_create_empty(const char *file,
1262299742Sdim                         apr_pool_t *scratch_pool)
1263299742Sdim{
1264299742Sdim  return svn_error_trace(svn_io_file_create_bytes(file, NULL, 0,
1265299742Sdim                                                  scratch_pool));
1266299742Sdim}
1267299742Sdim
1268299742Sdimsvn_error_t *
1269299742Sdimsvn_io_dir_file_copy(const char *src_path,
1270299742Sdim                     const char *dest_path,
1271299742Sdim                     const char *file,
1272299742Sdim                     apr_pool_t *pool)
1273299742Sdim{
1274251881Speter  const char *file_dest_path = svn_dirent_join(dest_path, file, pool);
1275251881Speter  const char *file_src_path = svn_dirent_join(src_path, file, pool);
1276251881Speter
1277299742Sdim  return svn_error_trace(
1278299742Sdim            svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool));
1279251881Speter}
1280251881Speter
1281251881Speter
1282251881Speter/*** Modtime checking. ***/
1283251881Speter
1284251881Spetersvn_error_t *
1285251881Spetersvn_io_file_affected_time(apr_time_t *apr_time,
1286251881Speter                          const char *path,
1287251881Speter                          apr_pool_t *pool)
1288251881Speter{
1289251881Speter  apr_finfo_t finfo;
1290251881Speter
1291251881Speter  SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1292251881Speter
1293251881Speter  *apr_time = finfo.mtime;
1294251881Speter
1295251881Speter  return SVN_NO_ERROR;
1296251881Speter}
1297251881Speter
1298251881Speter
1299251881Spetersvn_error_t *
1300251881Spetersvn_io_set_file_affected_time(apr_time_t apr_time,
1301251881Speter                              const char *path,
1302251881Speter                              apr_pool_t *pool)
1303251881Speter{
1304251881Speter  apr_status_t status;
1305251881Speter  const char *native_path;
1306251881Speter
1307251881Speter  SVN_ERR(cstring_from_utf8(&native_path, path, pool));
1308251881Speter  status = apr_file_mtime_set(native_path, apr_time, pool);
1309251881Speter
1310251881Speter  if (status)
1311251881Speter    return svn_error_wrap_apr(status, _("Can't set access time of '%s'"),
1312251881Speter                              svn_dirent_local_style(path, pool));
1313251881Speter
1314251881Speter  return SVN_NO_ERROR;
1315251881Speter}
1316251881Speter
1317251881Speter
1318251881Spetervoid
1319251881Spetersvn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool)
1320251881Speter{
1321251881Speter  apr_time_t now, then;
1322251881Speter  svn_error_t *err;
1323251881Speter  char *sleep_env_var;
1324251881Speter
1325251881Speter  sleep_env_var = getenv(SVN_SLEEP_ENV_VAR);
1326251881Speter
1327251881Speter  if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0)
1328251881Speter    return; /* Allow skipping for testing */
1329251881Speter
1330251881Speter  now = apr_time_now();
1331251881Speter
1332251881Speter  /* Calculate 0.02 seconds after the next second wallclock tick. */
1333251881Speter  then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50);
1334251881Speter
1335251881Speter  /* Worst case is waiting one second, so we can use that time to determine
1336251881Speter     if we can sleep shorter than that */
1337251881Speter  if (path)
1338251881Speter    {
1339251881Speter      apr_finfo_t finfo;
1340251881Speter
1341251881Speter      err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool);
1342251881Speter
1343251881Speter      if (err)
1344251881Speter        {
1345251881Speter          svn_error_clear(err); /* Fall back on original behavior */
1346251881Speter        }
1347251881Speter      else if (finfo.mtime % APR_USEC_PER_SEC)
1348251881Speter        {
1349251881Speter          /* Very simplistic but safe approach:
1350251881Speter              If the filesystem has < sec mtime we can be reasonably sure
1351269847Speter              that the filesystem has some sub-second resolution.  On Windows
1352269847Speter              it is likely to be sub-millisecond; on Linux systems it depends
1353269847Speter              on the filesystem, ext4 is typically 1ms, 4ms or 10ms resolution.
1354251881Speter
1355251881Speter             ## Perhaps find a better algorithm here. This will fail once
1356269847Speter                in every 1000 cases on a millisecond precision filesystem
1357269847Speter                if the mtime happens to be an exact second.
1358251881Speter
1359251881Speter                But better to fail once in every thousand cases than every
1360251881Speter                time, like we did before.
1361251881Speter
1362251881Speter             Note for further research on algorithm:
1363269847Speter               FAT32 has < 1 sec precision on ctime, but 2 sec on mtime.
1364251881Speter
1365269847Speter               Linux/ext4 with CONFIG_HZ=250 has high resolution
1366269847Speter               apr_time_now and although the filesystem timestamps
1367269847Speter               have similar high precision they are only updated with
1368269847Speter               a coarser 4ms resolution. */
1369251881Speter
1370269847Speter          /* 10 milliseconds after now. */
1371269847Speter#ifndef SVN_HI_RES_SLEEP_MS
1372269847Speter#define SVN_HI_RES_SLEEP_MS 10
1373269847Speter#endif
1374269847Speter          then = now + apr_time_from_msec(SVN_HI_RES_SLEEP_MS);
1375251881Speter        }
1376251881Speter
1377269847Speter      /* Remove time taken to do stat() from sleep. */
1378269847Speter      now = apr_time_now();
1379251881Speter    }
1380251881Speter
1381269847Speter  if (now >= then)
1382269847Speter    return; /* Passing negative values may suspend indefinitely (Windows) */
1383269847Speter
1384269847Speter  /* (t < 1000 will be round to 0 in apr) */
1385269847Speter  if (then - now < 1000)
1386269847Speter    apr_sleep(1000);
1387269847Speter  else
1388269847Speter    apr_sleep(then - now);
1389251881Speter}
1390251881Speter
1391251881Speter
1392251881Spetersvn_error_t *
1393251881Spetersvn_io_filesizes_different_p(svn_boolean_t *different_p,
1394251881Speter                             const char *file1,
1395251881Speter                             const char *file2,
1396251881Speter                             apr_pool_t *pool)
1397251881Speter{
1398251881Speter  apr_finfo_t finfo1;
1399251881Speter  apr_finfo_t finfo2;
1400251881Speter  apr_status_t status;
1401251881Speter  const char *file1_apr, *file2_apr;
1402251881Speter
1403251881Speter  /* Not using svn_io_stat() because don't want to generate
1404251881Speter     svn_error_t objects for non-error conditions. */
1405251881Speter
1406251881Speter  SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool));
1407251881Speter  SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool));
1408251881Speter
1409251881Speter  /* Stat both files */
1410251881Speter  status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool);
1411251881Speter  if (status)
1412251881Speter    {
1413251881Speter      /* If we got an error stat'ing a file, it could be because the
1414251881Speter         file was removed... or who knows.  Whatever the case, we
1415251881Speter         don't know if the filesizes are definitely different, so
1416251881Speter         assume that they're not. */
1417251881Speter      *different_p = FALSE;
1418251881Speter      return SVN_NO_ERROR;
1419251881Speter    }
1420251881Speter
1421251881Speter  status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool);
1422251881Speter  if (status)
1423251881Speter    {
1424251881Speter      /* See previous comment. */
1425251881Speter      *different_p = FALSE;
1426251881Speter      return SVN_NO_ERROR;
1427251881Speter    }
1428251881Speter
1429251881Speter  /* Examine file sizes */
1430251881Speter  if (finfo1.size == finfo2.size)
1431251881Speter    *different_p = FALSE;
1432251881Speter  else
1433251881Speter    *different_p = TRUE;
1434251881Speter
1435251881Speter  return SVN_NO_ERROR;
1436251881Speter}
1437251881Speter
1438251881Speter
1439251881Spetersvn_error_t *
1440251881Spetersvn_io_filesizes_three_different_p(svn_boolean_t *different_p12,
1441251881Speter                                   svn_boolean_t *different_p23,
1442251881Speter                                   svn_boolean_t *different_p13,
1443251881Speter                                   const char *file1,
1444251881Speter                                   const char *file2,
1445251881Speter                                   const char *file3,
1446251881Speter                                   apr_pool_t *scratch_pool)
1447251881Speter{
1448251881Speter  apr_finfo_t finfo1, finfo2, finfo3;
1449251881Speter  apr_status_t status1, status2, status3;
1450251881Speter  const char *file1_apr, *file2_apr, *file3_apr;
1451251881Speter
1452251881Speter  /* Not using svn_io_stat() because don't want to generate
1453251881Speter     svn_error_t objects for non-error conditions. */
1454251881Speter
1455251881Speter  SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool));
1456251881Speter  SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool));
1457251881Speter  SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool));
1458251881Speter
1459251881Speter  /* Stat all three files */
1460251881Speter  status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool);
1461251881Speter  status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool);
1462251881Speter  status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool);
1463251881Speter
1464251881Speter  /* If we got an error stat'ing a file, it could be because the
1465251881Speter     file was removed... or who knows.  Whatever the case, we
1466251881Speter     don't know if the filesizes are definitely different, so
1467251881Speter     assume that they're not. */
1468251881Speter  *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size;
1469251881Speter  *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size;
1470251881Speter  *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size;
1471251881Speter
1472251881Speter  return SVN_NO_ERROR;
1473251881Speter}
1474251881Speter
1475251881Speter
1476251881Spetersvn_error_t *
1477251881Spetersvn_io_file_checksum2(svn_checksum_t **checksum,
1478251881Speter                      const char *file,
1479251881Speter                      svn_checksum_kind_t kind,
1480251881Speter                      apr_pool_t *pool)
1481251881Speter{
1482251881Speter  svn_stream_t *file_stream;
1483251881Speter  svn_stream_t *checksum_stream;
1484251881Speter  apr_file_t* f;
1485251881Speter
1486251881Speter  SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool));
1487251881Speter  file_stream = svn_stream_from_aprfile2(f, FALSE, pool);
1488251881Speter  checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind,
1489251881Speter                                            TRUE, pool);
1490251881Speter
1491251881Speter  /* Because the checksummed stream will force the reading (and
1492251881Speter     checksumming) of all the file's bytes, we can just close the stream
1493251881Speter     and let its magic work. */
1494251881Speter  return svn_stream_close(checksum_stream);
1495251881Speter}
1496251881Speter
1497251881Speter
1498251881Spetersvn_error_t *
1499251881Spetersvn_io_file_checksum(unsigned char digest[],
1500251881Speter                     const char *file,
1501251881Speter                     apr_pool_t *pool)
1502251881Speter{
1503251881Speter  svn_checksum_t *checksum;
1504251881Speter
1505251881Speter  SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool));
1506251881Speter  memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE);
1507251881Speter
1508251881Speter  return SVN_NO_ERROR;
1509251881Speter}
1510251881Speter
1511251881Speter
1512251881Speter
1513251881Speter/*** Permissions and modes. ***/
1514251881Speter
1515251881Speter#if !defined(WIN32) && !defined(__OS2__)
1516251881Speter/* Given the file specified by PATH, attempt to create an
1517251881Speter   identical version of it owned by the current user.  This is done by
1518251881Speter   moving it to a temporary location, copying the file back to its old
1519251881Speter   path, then deleting the temporarily moved version.  All temporary
1520251881Speter   allocations are done in POOL. */
1521251881Speterstatic svn_error_t *
1522251881Speterreown_file(const char *path,
1523251881Speter           apr_pool_t *pool)
1524251881Speter{
1525251881Speter  const char *unique_name;
1526251881Speter
1527251881Speter  SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name,
1528251881Speter                                   svn_dirent_dirname(path, pool),
1529251881Speter                                   svn_io_file_del_none, pool, pool));
1530251881Speter  SVN_ERR(svn_io_file_rename(path, unique_name, pool));
1531251881Speter  SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool));
1532251881Speter  return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool));
1533251881Speter}
1534251881Speter
1535251881Speter/* Determine what the PERMS for a new file should be by looking at the
1536251881Speter   permissions of a temporary file that we create.
1537251881Speter   Unfortunately, umask() as defined in POSIX provides no thread-safe way
1538251881Speter   to get at the current value of the umask, so what we're doing here is
1539251881Speter   the only way we have to determine which combination of write bits
1540251881Speter   (User/Group/World) should be set by default.
1541251881Speter   Make temporary allocations in SCRATCH_POOL.  */
1542251881Speterstatic svn_error_t *
1543251881Speterget_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool)
1544251881Speter{
1545251881Speter  /* the default permissions as read from the temp folder */
1546251881Speter  static apr_fileperms_t default_perms = 0;
1547251881Speter
1548251881Speter  /* Technically, this "racy": Multiple threads may use enter here and
1549251881Speter     try to figure out the default permission concurrently. That's fine
1550251881Speter     since they will end up with the same results. Even more technical,
1551251881Speter     apr_fileperms_t is an atomic type on 32+ bit machines.
1552251881Speter   */
1553251881Speter  if (default_perms == 0)
1554251881Speter    {
1555251881Speter      apr_finfo_t finfo;
1556251881Speter      apr_file_t *fd;
1557251881Speter      const char *fname_base, *fname;
1558251881Speter      apr_uint32_t randomish;
1559251881Speter      svn_error_t *err;
1560251881Speter
1561251881Speter      /* Get the perms for a newly created file to find out what bits
1562251881Speter        should be set.
1563251881Speter
1564299742Sdim        Explicitly delete the file because we want this file to be as
1565251881Speter        short-lived as possible since its presence means other
1566251881Speter        processes may have to try multiple names.
1567251881Speter
1568251881Speter        Using svn_io_open_uniquely_named() here because other tempfile
1569251881Speter        creation functions tweak the permission bits of files they create.
1570251881Speter      */
1571251881Speter      randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool
1572251881Speter                   + (apr_uint32_t)apr_time_now());
1573251881Speter      fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish);
1574251881Speter
1575251881Speter      SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base,
1576251881Speter                                         NULL, svn_io_file_del_none,
1577251881Speter                                         scratch_pool, scratch_pool));
1578251881Speter      err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool);
1579251881Speter      err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool));
1580251881Speter      err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE,
1581251881Speter                                                              scratch_pool));
1582251881Speter      SVN_ERR(err);
1583251881Speter      *perms = finfo.protection;
1584251881Speter      default_perms = finfo.protection;
1585251881Speter    }
1586251881Speter  else
1587251881Speter    *perms = default_perms;
1588251881Speter
1589251881Speter  return SVN_NO_ERROR;
1590251881Speter}
1591251881Speter
1592251881Speter/* OR together permission bits of the file FD and the default permissions
1593251881Speter   of a file as determined by get_default_file_perms(). Do temporary
1594251881Speter   allocations in SCRATCH_POOL. */
1595251881Speterstatic svn_error_t *
1596251881Spetermerge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms,
1597251881Speter                         apr_pool_t *scratch_pool)
1598251881Speter{
1599251881Speter  apr_finfo_t finfo;
1600251881Speter  apr_fileperms_t default_perms;
1601251881Speter
1602251881Speter  SVN_ERR(get_default_file_perms(&default_perms, scratch_pool));
1603251881Speter  SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool));
1604251881Speter
1605251881Speter  /* Glom the perms together. */
1606251881Speter  *perms = default_perms | finfo.protection;
1607251881Speter  return SVN_NO_ERROR;
1608251881Speter}
1609251881Speter
1610251881Speter/* This is a helper function for the svn_io_set_file_read* functions
1611251881Speter   that attempts to honor the users umask when dealing with
1612251881Speter   permission changes.  It is a no-op when invoked on a symlink. */
1613251881Speterstatic svn_error_t *
1614251881Speterio_set_file_perms(const char *path,
1615251881Speter                  svn_boolean_t change_readwrite,
1616251881Speter                  svn_boolean_t enable_write,
1617251881Speter                  svn_boolean_t change_executable,
1618251881Speter                  svn_boolean_t executable,
1619251881Speter                  svn_boolean_t ignore_enoent,
1620251881Speter                  apr_pool_t *pool)
1621251881Speter{
1622251881Speter  apr_status_t status;
1623251881Speter  const char *path_apr;
1624251881Speter  apr_finfo_t finfo;
1625251881Speter  apr_fileperms_t perms_to_set;
1626251881Speter
1627251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1628251881Speter
1629251881Speter  /* Try to change only a minimal amount of the perms first
1630251881Speter     by getting the current perms and adding bits
1631251881Speter     only on where read perms are granted.  If this fails
1632251881Speter     fall through to just setting file attributes. */
1633251881Speter  status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool);
1634251881Speter  if (status)
1635251881Speter    {
1636299742Sdim      if (ignore_enoent && (APR_STATUS_IS_ENOENT(status)
1637299742Sdim                            || SVN__APR_STATUS_IS_ENOTDIR(status)))
1638251881Speter        return SVN_NO_ERROR;
1639251881Speter      else if (status != APR_ENOTIMPL)
1640251881Speter        return svn_error_wrap_apr(status,
1641251881Speter                                  _("Can't change perms of file '%s'"),
1642251881Speter                                  svn_dirent_local_style(path, pool));
1643251881Speter      return SVN_NO_ERROR;
1644251881Speter    }
1645251881Speter
1646251881Speter  if (finfo.filetype == APR_LNK)
1647251881Speter    return SVN_NO_ERROR;
1648251881Speter
1649251881Speter  perms_to_set = finfo.protection;
1650251881Speter  if (change_readwrite)
1651251881Speter    {
1652251881Speter      if (enable_write) /* Make read-write. */
1653251881Speter        {
1654262253Speter          /* Tweak the owner bits only. The group/other bits aren't safe to
1655262253Speter           * touch because we may end up setting them in undesired ways. */
1656262253Speter          perms_to_set |= (APR_UREAD|APR_UWRITE);
1657251881Speter        }
1658251881Speter      else
1659251881Speter        {
1660251881Speter          if (finfo.protection & APR_UREAD)
1661251881Speter            perms_to_set &= ~APR_UWRITE;
1662251881Speter          if (finfo.protection & APR_GREAD)
1663251881Speter            perms_to_set &= ~APR_GWRITE;
1664251881Speter          if (finfo.protection & APR_WREAD)
1665251881Speter            perms_to_set &= ~APR_WWRITE;
1666251881Speter        }
1667251881Speter    }
1668251881Speter
1669251881Speter  if (change_executable)
1670251881Speter    {
1671251881Speter      if (executable)
1672251881Speter        {
1673251881Speter          if (finfo.protection & APR_UREAD)
1674251881Speter            perms_to_set |= APR_UEXECUTE;
1675251881Speter          if (finfo.protection & APR_GREAD)
1676251881Speter            perms_to_set |= APR_GEXECUTE;
1677251881Speter          if (finfo.protection & APR_WREAD)
1678251881Speter            perms_to_set |= APR_WEXECUTE;
1679251881Speter        }
1680251881Speter      else
1681251881Speter        {
1682251881Speter          if (finfo.protection & APR_UREAD)
1683251881Speter            perms_to_set &= ~APR_UEXECUTE;
1684251881Speter          if (finfo.protection & APR_GREAD)
1685251881Speter            perms_to_set &= ~APR_GEXECUTE;
1686251881Speter          if (finfo.protection & APR_WREAD)
1687251881Speter            perms_to_set &= ~APR_WEXECUTE;
1688251881Speter        }
1689251881Speter    }
1690251881Speter
1691251881Speter  /* If we aren't changing anything then just return, this saves
1692251881Speter     some system calls and helps with shared working copies */
1693251881Speter  if (perms_to_set == finfo.protection)
1694251881Speter    return SVN_NO_ERROR;
1695251881Speter
1696251881Speter  status = apr_file_perms_set(path_apr, perms_to_set);
1697251881Speter  if (!status)
1698251881Speter    return SVN_NO_ERROR;
1699251881Speter
1700251881Speter  if (APR_STATUS_IS_EPERM(status))
1701251881Speter    {
1702251881Speter      /* We don't have permissions to change the
1703251881Speter         permissions!  Try a move, copy, and delete
1704251881Speter         workaround to see if we can get the file owned by
1705251881Speter         us.  If these succeed, try the permissions set
1706251881Speter         again.
1707251881Speter
1708251881Speter         Note that we only attempt this in the
1709251881Speter         stat-available path.  This assumes that the
1710251881Speter         move-copy workaround will only be helpful on
1711251881Speter         platforms that implement apr_stat. */
1712251881Speter      SVN_ERR(reown_file(path, pool));
1713251881Speter      status = apr_file_perms_set(path_apr, perms_to_set);
1714251881Speter    }
1715251881Speter
1716251881Speter  if (!status)
1717251881Speter    return SVN_NO_ERROR;
1718251881Speter
1719251881Speter  if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1720251881Speter    return SVN_NO_ERROR;
1721251881Speter  else if (status == APR_ENOTIMPL)
1722251881Speter    {
1723251881Speter      /* At least try to set the attributes. */
1724251881Speter      apr_fileattrs_t attrs = 0;
1725251881Speter      apr_fileattrs_t attrs_values = 0;
1726251881Speter
1727251881Speter      if (change_readwrite)
1728251881Speter        {
1729251881Speter          attrs = APR_FILE_ATTR_READONLY;
1730251881Speter          if (!enable_write)
1731251881Speter            attrs_values = APR_FILE_ATTR_READONLY;
1732251881Speter        }
1733251881Speter      if (change_executable)
1734251881Speter        {
1735251881Speter          attrs = APR_FILE_ATTR_EXECUTABLE;
1736251881Speter          if (executable)
1737251881Speter            attrs_values = APR_FILE_ATTR_EXECUTABLE;
1738251881Speter        }
1739251881Speter      status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
1740251881Speter    }
1741251881Speter
1742251881Speter  return svn_error_wrap_apr(status,
1743251881Speter                            _("Can't change perms of file '%s'"),
1744251881Speter                            svn_dirent_local_style(path, pool));
1745251881Speter}
1746251881Speter#endif /* !WIN32 && !__OS2__ */
1747251881Speter
1748251881Speter#ifdef WIN32
1749299742Sdim/* This is semantically the same as the APR utf8_to_unicode_path
1750299742Sdim   function, but reimplemented here because APR does not export it. */
1751299742Sdimsvn_error_t*
1752299742Sdimsvn_io__utf8_to_unicode_longpath(const WCHAR **result,
1753299742Sdim                                 const char *source,
1754299742Sdim                                 apr_pool_t *result_pool)
1755251881Speter{
1756251881Speter    /* This is correct, we don't twist the filename if it will
1757251881Speter     * definitely be shorter than 248 characters.  It merits some
1758251881Speter     * performance testing to see if this has any effect, but there
1759251881Speter     * seem to be applications that get confused by the resulting
1760251881Speter     * Unicode \\?\ style file names, especially if they use argv[0]
1761251881Speter     * or call the Win32 API functions such as GetModuleName, etc.
1762251881Speter     * Not every application is prepared to handle such names.
1763251881Speter     *
1764251881Speter     * Note also this is shorter than MAX_PATH, as directory paths
1765251881Speter     * are actually limited to 248 characters.
1766251881Speter     *
1767251881Speter     * Note that a utf-8 name can never result in more wide chars
1768251881Speter     * than the original number of utf-8 narrow chars.
1769251881Speter     */
1770299742Sdim    const WCHAR *prefix = NULL;
1771299742Sdim    const int srclen = strlen(source);
1772299742Sdim    WCHAR *buffer;
1773299742Sdim
1774299742Sdim    if (srclen > 248)
1775299742Sdim    {
1776299742Sdim        if (svn_ctype_isalpha(source[0]) && source[1] == ':'
1777299742Sdim            && (source[2] == '/' || source[2] == '\\'))
1778299742Sdim        {
1779299742Sdim            /* This is an ordinary absolute path. */
1780299742Sdim            prefix = L"\\\\?\\";
1781251881Speter        }
1782299742Sdim        else if ((source[0] == '/' || source[0] == '\\')
1783299742Sdim                 && (source[1] == '/' || source[1] == '\\')
1784299742Sdim                 && source[2] != '?')
1785299742Sdim        {
1786299742Sdim            /* This is a UNC path */
1787299742Sdim            source += 2;        /* Skip the leading slashes */
1788299742Sdim            prefix = L"\\\\?\\UNC\\";
1789251881Speter        }
1790251881Speter    }
1791251881Speter
1792299742Sdim    SVN_ERR(svn_utf__win32_utf8_to_utf16(&(const WCHAR*)buffer, source,
1793299742Sdim                                         prefix, result_pool));
1794299742Sdim
1795299742Sdim    /* Convert slashes to backslashes because the \\?\ path format
1796299742Sdim       does not allow backslashes as path separators. */
1797299742Sdim    *result = buffer;
1798299742Sdim    for (; *buffer; ++buffer)
1799299742Sdim    {
1800299742Sdim        if (*buffer == '/')
1801299742Sdim            *buffer = '\\';
1802251881Speter    }
1803299742Sdim    return SVN_NO_ERROR;
1804299742Sdim}
1805299742Sdim
1806299742Sdim/* This is semantically the same as the APR unicode_to_utf8_path
1807299742Sdim   function, but reimplemented here because APR does not export it. */
1808299742Sdimstatic svn_error_t *
1809299742Sdimio_unicode_to_utf8_path(const char **result,
1810299742Sdim                        const WCHAR *source,
1811299742Sdim                        apr_pool_t *result_pool)
1812299742Sdim{
1813299742Sdim    const char *utf8_buffer;
1814299742Sdim    char *buffer;
1815299742Sdim
1816299742Sdim    SVN_ERR(svn_utf__win32_utf16_to_utf8(&utf8_buffer, source,
1817299742Sdim                                         NULL, result_pool));
1818299742Sdim    if (!*utf8_buffer)
1819299742Sdim      {
1820299742Sdim        *result = utf8_buffer;
1821299742Sdim        return SVN_NO_ERROR;
1822299742Sdim      }
1823299742Sdim
1824299742Sdim    /* We know that the non-empty buffer returned from the UTF-16 to
1825299742Sdim       UTF-8 conversion function is in fact writable. */
1826299742Sdim    buffer = (char*)utf8_buffer;
1827299742Sdim
1828299742Sdim    /* Skip the leading 4 characters if the path begins \\?\, or substitute
1829299742Sdim     * // for the \\?\UNC\ path prefix, allocating the maximum string
1830299742Sdim     * length based on the remaining string, plus the trailing null.
1831299742Sdim     * then transform \\'s back into /'s since the \\?\ form never
1832299742Sdim     * allows '/' path separators, and APR always uses '/'s.
1833299742Sdim     */
1834299742Sdim    if (0 == strncmp(buffer, "\\\\?\\", 4))
1835299742Sdim    {
1836299742Sdim        buffer += 4;
1837299742Sdim        if (0 == strncmp(buffer, "UNC\\", 4))
1838299742Sdim        {
1839299742Sdim            buffer += 2;
1840299742Sdim            *buffer = '/';
1841299742Sdim        }
1842251881Speter    }
1843299742Sdim
1844299742Sdim    *result = buffer;
1845299742Sdim    for (; *buffer; ++buffer)
1846299742Sdim    {
1847299742Sdim        if (*buffer == '\\')
1848299742Sdim            *buffer = '/';
1849299742Sdim    }
1850299742Sdim    return SVN_NO_ERROR;
1851251881Speter}
1852251881Speter
1853299742Sdimstatic svn_error_t *
1854299742Sdimio_win_file_attrs_set(const char *fname,
1855299742Sdim                      DWORD attributes,
1856299742Sdim                      DWORD attr_mask,
1857299742Sdim                      apr_pool_t *pool)
1858251881Speter{
1859251881Speter    /* this is an implementation of apr_file_attrs_set() but one
1860251881Speter       that uses the proper Windows attributes instead of the apr
1861251881Speter       attributes. This way, we can apply any Windows file and
1862251881Speter       folder attributes even if apr doesn't implement them */
1863251881Speter    DWORD flags;
1864299742Sdim    const WCHAR *wfname;
1865251881Speter
1866299742Sdim    SVN_ERR(svn_io__utf8_to_unicode_longpath(&wfname, fname, pool));
1867299742Sdim
1868299742Sdim    flags = GetFileAttributesW(wfname);
1869299742Sdim    if (flags == 0xFFFFFFFF)
1870299742Sdim        return svn_error_wrap_apr(apr_get_os_error(),
1871299742Sdim                                  _("Can't get attributes of file '%s'"),
1872299742Sdim                                  svn_dirent_local_style(fname, pool));
1873299742Sdim
1874299742Sdim    flags &= ~attr_mask;
1875299742Sdim    flags |= (attributes & attr_mask);
1876299742Sdim
1877299742Sdim    if (!SetFileAttributesW(wfname, flags))
1878299742Sdim        return svn_error_wrap_apr(apr_get_os_error(),
1879299742Sdim                                  _("Can't set attributes of file '%s'"),
1880299742Sdim                                  svn_dirent_local_style(fname, pool));
1881299742Sdim
1882299742Sdim    return SVN_NO_ERROR;;
1883299742Sdim}
1884299742Sdim
1885299742Sdimstatic svn_error_t *win_init_dynamic_imports(void *baton, apr_pool_t *pool)
1886299742Sdim{
1887299742Sdim  HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
1888299742Sdim
1889299742Sdim  if (kernel32)
1890251881Speter    {
1891299742Sdim      get_final_path_name_by_handle_proc = (GETFINALPATHNAMEBYHANDLE)
1892299742Sdim        GetProcAddress(kernel32, "GetFinalPathNameByHandleW");
1893299742Sdim
1894299742Sdim      set_file_information_by_handle_proc = (SetFileInformationByHandle_t)
1895299742Sdim        GetProcAddress(kernel32, "SetFileInformationByHandle");
1896251881Speter    }
1897299742Sdim
1898299742Sdim  return SVN_NO_ERROR;
1899299742Sdim}
1900299742Sdim
1901299742Sdimstatic svn_error_t * io_win_read_link(svn_string_t **dest,
1902299742Sdim                                      const char *path,
1903299742Sdim                                      apr_pool_t *pool)
1904299742Sdim{
1905299742Sdim    SVN_ERR(svn_atomic__init_once(&win_dynamic_imports_state,
1906299742Sdim                                  win_init_dynamic_imports, NULL, pool));
1907299742Sdim
1908299742Sdim    if (get_final_path_name_by_handle_proc)
1909299742Sdim      {
1910299742Sdim        DWORD rv;
1911299742Sdim        apr_status_t status;
1912299742Sdim        apr_file_t *file;
1913299742Sdim        apr_os_file_t filehand;
1914299742Sdim        WCHAR wdest[APR_PATH_MAX];
1915299742Sdim        const char *data;
1916299742Sdim
1917299742Sdim        /* reserve one char for terminating zero. */
1918299742Sdim        DWORD wdest_len = sizeof(wdest)/sizeof(wdest[0]) - 1;
1919299742Sdim
1920299742Sdim        status = apr_file_open(&file, path, APR_OPENINFO, APR_OS_DEFAULT, pool);
1921299742Sdim
1922299742Sdim        if (status)
1923299742Sdim          return svn_error_wrap_apr(status,
1924299742Sdim                                    _("Can't read contents of link"));
1925299742Sdim
1926299742Sdim        apr_os_file_get(&filehand, file);
1927299742Sdim
1928299742Sdim        rv = get_final_path_name_by_handle_proc(
1929299742Sdim               filehand, wdest, wdest_len,
1930299742Sdim               FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
1931299742Sdim
1932299742Sdim        /* Save error code. */
1933299742Sdim        status = apr_get_os_error();
1934299742Sdim
1935299742Sdim        /* Close file/directory handle in any case. */
1936299742Sdim        apr_file_close(file);
1937299742Sdim
1938299742Sdim        /* GetFinaPathNameByHandleW returns number of characters copied to
1939299742Sdim         * output buffer. Returns zero on error. Returns required buffer size
1940299742Sdim         * if supplied buffer is not enough. */
1941299742Sdim        if (rv > wdest_len || rv == 0)
1942299742Sdim          {
1943299742Sdim            return svn_error_wrap_apr(status,
1944299742Sdim                                      _("Can't read contents of link"));
1945299742Sdim          }
1946299742Sdim
1947299742Sdim        /* GetFinaPathNameByHandleW doesn't add terminating NUL. */
1948299742Sdim        wdest[rv] = 0;
1949299742Sdim        SVN_ERR(io_unicode_to_utf8_path(&data, wdest, pool));
1950299742Sdim
1951299742Sdim        /* The result is already in the correct pool, so avoid copying
1952299742Sdim           it to create the string. */
1953299742Sdim        *dest = svn_string_create_empty(pool);
1954299742Sdim        if (*data)
1955299742Sdim          {
1956299742Sdim            (*dest)->data = data;
1957299742Sdim            (*dest)->len = strlen(data);
1958299742Sdim          }
1959299742Sdim
1960299742Sdim        return SVN_NO_ERROR;
1961299742Sdim      }
1962299742Sdim    else
1963299742Sdim      {
1964299742Sdim        return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1965299742Sdim                                _("Symbolic links are not supported on this "
1966299742Sdim                                "platform"));
1967299742Sdim      }
1968299742Sdim}
1969299742Sdim
1970299742Sdim/* Wrapper around Windows API function SetFileInformationByHandle() that
1971299742Sdim * returns APR status instead of boolean flag. */
1972299742Sdimstatic apr_status_t
1973299742Sdimwin32_set_file_information_by_handle(HANDLE hFile,
1974299742Sdim                                     int FileInformationClass,
1975299742Sdim                                     LPVOID lpFileInformation,
1976299742Sdim                                     DWORD dwBufferSize)
1977299742Sdim{
1978299742Sdim  svn_error_clear(svn_atomic__init_once(&win_dynamic_imports_state,
1979299742Sdim                                        win_init_dynamic_imports,
1980299742Sdim                                        NULL, NULL));
1981299742Sdim
1982299742Sdim  if (!set_file_information_by_handle_proc)
1983251881Speter    {
1984299742Sdim      return SVN_ERR_UNSUPPORTED_FEATURE;
1985251881Speter    }
1986251881Speter
1987299742Sdim  if (!set_file_information_by_handle_proc(hFile, FileInformationClass,
1988299742Sdim                                           lpFileInformation,
1989299742Sdim                                           dwBufferSize))
1990299742Sdim    {
1991299742Sdim      return apr_get_os_error();
1992299742Sdim    }
1993251881Speter
1994299742Sdim  return APR_SUCCESS;
1995299742Sdim}
1996251881Speter
1997299742Sdimsvn_error_t *
1998299742Sdimsvn_io__win_delete_file_on_close(apr_file_t *file,
1999299742Sdim                                 const char *path,
2000299742Sdim                                 apr_pool_t *pool)
2001299742Sdim{
2002299742Sdim  FILE_DISPOSITION_INFO disposition_info;
2003299742Sdim  HANDLE hFile;
2004299742Sdim  apr_status_t status;
2005299742Sdim
2006299742Sdim  apr_os_file_get(&hFile, file);
2007299742Sdim
2008299742Sdim  disposition_info.DeleteFile = TRUE;
2009299742Sdim
2010299742Sdim  status = win32_set_file_information_by_handle(hFile, FileDispositionInfo,
2011299742Sdim                                                &disposition_info,
2012299742Sdim                                                sizeof(disposition_info));
2013299742Sdim
2014299742Sdim  if (status)
2015251881Speter    {
2016299742Sdim      return svn_error_wrap_apr(status, _("Can't remove file '%s'"),
2017299742Sdim                                svn_dirent_local_style(path, pool));
2018251881Speter    }
2019299742Sdim
2020299742Sdim  return SVN_NO_ERROR;
2021299742Sdim}
2022299742Sdim
2023299742Sdimsvn_error_t *
2024299742Sdimsvn_io__win_rename_open_file(apr_file_t *file,
2025299742Sdim                             const char *from_path,
2026299742Sdim                             const char *to_path,
2027299742Sdim                             apr_pool_t *pool)
2028299742Sdim{
2029299742Sdim  WCHAR *w_final_abspath;
2030299742Sdim  size_t path_len;
2031299742Sdim  size_t rename_size;
2032299742Sdim  FILE_RENAME_INFO *rename_info;
2033299742Sdim  HANDLE hFile;
2034299742Sdim  apr_status_t status;
2035299742Sdim
2036299742Sdim  apr_os_file_get(&hFile, file);
2037299742Sdim
2038299742Sdim  SVN_ERR(svn_io__utf8_to_unicode_longpath(
2039299742Sdim            &w_final_abspath, svn_dirent_local_style(to_path,pool),
2040299742Sdim            pool));
2041299742Sdim
2042299742Sdim  path_len = wcslen(w_final_abspath);
2043299742Sdim  rename_size = sizeof(*rename_info) + sizeof(WCHAR) * path_len;
2044299742Sdim
2045299742Sdim  /* The rename info struct doesn't need hacks for long paths,
2046299742Sdim     so no ugly escaping calls here */
2047299742Sdim  rename_info = apr_pcalloc(pool, rename_size);
2048299742Sdim  rename_info->ReplaceIfExists = TRUE;
2049299742Sdim  rename_info->FileNameLength = path_len;
2050299742Sdim  memcpy(rename_info->FileName, w_final_abspath, path_len * sizeof(WCHAR));
2051299742Sdim
2052299742Sdim  status = win32_set_file_information_by_handle(hFile, FileRenameInfo,
2053299742Sdim                                                rename_info,
2054299742Sdim                                                rename_size);
2055299742Sdim
2056299742Sdim  if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
2057251881Speter    {
2058299742Sdim      /* Set the destination file writable because Windows will not allow
2059299742Sdim         us to rename when final_abspath is read-only. */
2060299742Sdim      SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
2061299742Sdim
2062299742Sdim      status = win32_set_file_information_by_handle(hFile,
2063299742Sdim                                                    FileRenameInfo,
2064299742Sdim                                                    rename_info,
2065299742Sdim                                                    rename_size);
2066299742Sdim   }
2067299742Sdim
2068299742Sdim  /* Windows returns Vista+ client accessing network share stored on Windows
2069299742Sdim     Server 2003 returns ERROR_ACCESS_DENIED. The same happens when Vista+
2070299742Sdim     client access Windows Server 2008 with disabled SMBv2 protocol.
2071299742Sdim
2072299742Sdim     So return SVN_ERR_UNSUPPORTED_FEATURE in this case like we do when
2073299742Sdim     SetFileInformationByHandle() is not available and let caller to
2074299742Sdim     handle it.
2075299742Sdim
2076299742Sdim     See "Access denied error on checkout-commit after updating to 1.9.X"
2077299742Sdim     discussion on dev@s.a.o:
2078299742Sdim     http://svn.haxx.se/dev/archive-2015-09/0054.shtml */
2079299742Sdim  if (status == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED))
2080299742Sdim    {
2081299742Sdim      status = SVN_ERR_UNSUPPORTED_FEATURE;
2082251881Speter    }
2083251881Speter
2084299742Sdim  if (status)
2085299742Sdim    {
2086299742Sdim      return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"),
2087299742Sdim                                svn_dirent_local_style(from_path, pool),
2088299742Sdim                                svn_dirent_local_style(to_path, pool));
2089299742Sdim    }
2090251881Speter
2091299742Sdim  return SVN_NO_ERROR;
2092251881Speter}
2093251881Speter
2094299742Sdim#endif /* WIN32 */
2095251881Speter
2096251881Spetersvn_error_t *
2097251881Spetersvn_io_set_file_read_write_carefully(const char *path,
2098251881Speter                                     svn_boolean_t enable_write,
2099251881Speter                                     svn_boolean_t ignore_enoent,
2100251881Speter                                     apr_pool_t *pool)
2101251881Speter{
2102251881Speter  if (enable_write)
2103251881Speter    return svn_io_set_file_read_write(path, ignore_enoent, pool);
2104251881Speter  return svn_io_set_file_read_only(path, ignore_enoent, pool);
2105251881Speter}
2106251881Speter
2107251881Spetersvn_error_t *
2108251881Spetersvn_io_set_file_read_only(const char *path,
2109251881Speter                          svn_boolean_t ignore_enoent,
2110251881Speter                          apr_pool_t *pool)
2111251881Speter{
2112251881Speter  /* On Windows and OS/2, just set the file attributes -- on unix call
2113251881Speter     our internal function which attempts to honor the umask. */
2114251881Speter#if !defined(WIN32) && !defined(__OS2__)
2115251881Speter  return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
2116251881Speter                           ignore_enoent, pool);
2117251881Speter#else
2118251881Speter  apr_status_t status;
2119251881Speter  const char *path_apr;
2120251881Speter
2121251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
2122251881Speter
2123251881Speter  status = apr_file_attrs_set(path_apr,
2124251881Speter                              APR_FILE_ATTR_READONLY,
2125251881Speter                              APR_FILE_ATTR_READONLY,
2126251881Speter                              pool);
2127251881Speter
2128251881Speter  if (status && status != APR_ENOTIMPL)
2129299742Sdim    if (!(ignore_enoent && (APR_STATUS_IS_ENOENT(status)
2130299742Sdim                            || SVN__APR_STATUS_IS_ENOTDIR(status))))
2131251881Speter      return svn_error_wrap_apr(status,
2132251881Speter                                _("Can't set file '%s' read-only"),
2133251881Speter                                svn_dirent_local_style(path, pool));
2134251881Speter
2135251881Speter  return SVN_NO_ERROR;
2136251881Speter#endif
2137251881Speter}
2138251881Speter
2139251881Speter
2140251881Spetersvn_error_t *
2141251881Spetersvn_io_set_file_read_write(const char *path,
2142251881Speter                           svn_boolean_t ignore_enoent,
2143251881Speter                           apr_pool_t *pool)
2144251881Speter{
2145251881Speter  /* On Windows and OS/2, just set the file attributes -- on unix call
2146251881Speter     our internal function which attempts to honor the umask. */
2147251881Speter#if !defined(WIN32) && !defined(__OS2__)
2148251881Speter  return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
2149251881Speter                           ignore_enoent, pool);
2150251881Speter#else
2151251881Speter  apr_status_t status;
2152251881Speter  const char *path_apr;
2153251881Speter
2154251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
2155251881Speter
2156251881Speter  status = apr_file_attrs_set(path_apr,
2157251881Speter                              0,
2158251881Speter                              APR_FILE_ATTR_READONLY,
2159251881Speter                              pool);
2160251881Speter
2161251881Speter  if (status && status != APR_ENOTIMPL)
2162251881Speter    if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
2163251881Speter      return svn_error_wrap_apr(status,
2164251881Speter                                _("Can't set file '%s' read-write"),
2165251881Speter                                svn_dirent_local_style(path, pool));
2166251881Speter
2167251881Speter  return SVN_NO_ERROR;
2168251881Speter#endif
2169251881Speter}
2170251881Speter
2171251881Spetersvn_error_t *
2172251881Spetersvn_io_set_file_executable(const char *path,
2173251881Speter                           svn_boolean_t executable,
2174251881Speter                           svn_boolean_t ignore_enoent,
2175251881Speter                           apr_pool_t *pool)
2176251881Speter{
2177251881Speter  /* On Windows and OS/2, just exit -- on unix call our internal function
2178251881Speter  which attempts to honor the umask. */
2179251881Speter#if (!defined(WIN32) && !defined(__OS2__))
2180251881Speter  return io_set_file_perms(path, FALSE, FALSE, TRUE, executable,
2181251881Speter                           ignore_enoent, pool);
2182251881Speter#else
2183251881Speter  return SVN_NO_ERROR;
2184251881Speter#endif
2185251881Speter}
2186251881Speter
2187251881Speter
2188251881Spetersvn_error_t *
2189251881Spetersvn_io__is_finfo_read_only(svn_boolean_t *read_only,
2190251881Speter                           apr_finfo_t *file_info,
2191251881Speter                           apr_pool_t *pool)
2192251881Speter{
2193251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
2194251881Speter  apr_status_t apr_err;
2195251881Speter  apr_uid_t uid;
2196251881Speter  apr_gid_t gid;
2197251881Speter
2198251881Speter  *read_only = FALSE;
2199251881Speter
2200251881Speter  apr_err = apr_uid_current(&uid, &gid, pool);
2201251881Speter
2202251881Speter  if (apr_err)
2203251881Speter    return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
2204251881Speter
2205251881Speter  /* Check write bit for current user. */
2206251881Speter  if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
2207251881Speter    *read_only = !(file_info->protection & APR_UWRITE);
2208251881Speter
2209251881Speter  else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
2210251881Speter    *read_only = !(file_info->protection & APR_GWRITE);
2211251881Speter
2212251881Speter  else
2213251881Speter    *read_only = !(file_info->protection & APR_WWRITE);
2214251881Speter
2215251881Speter#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
2216251881Speter  *read_only = (file_info->protection & APR_FREADONLY);
2217251881Speter#endif
2218251881Speter
2219251881Speter  return SVN_NO_ERROR;
2220251881Speter}
2221251881Speter
2222251881Spetersvn_error_t *
2223251881Spetersvn_io__is_finfo_executable(svn_boolean_t *executable,
2224251881Speter                            apr_finfo_t *file_info,
2225251881Speter                            apr_pool_t *pool)
2226251881Speter{
2227251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
2228251881Speter  apr_status_t apr_err;
2229251881Speter  apr_uid_t uid;
2230251881Speter  apr_gid_t gid;
2231251881Speter
2232251881Speter  *executable = FALSE;
2233251881Speter
2234251881Speter  apr_err = apr_uid_current(&uid, &gid, pool);
2235251881Speter
2236251881Speter  if (apr_err)
2237251881Speter    return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
2238251881Speter
2239251881Speter  /* Check executable bit for current user. */
2240251881Speter  if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
2241251881Speter    *executable = (file_info->protection & APR_UEXECUTE);
2242251881Speter
2243251881Speter  else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
2244251881Speter    *executable = (file_info->protection & APR_GEXECUTE);
2245251881Speter
2246251881Speter  else
2247251881Speter    *executable = (file_info->protection & APR_WEXECUTE);
2248251881Speter
2249251881Speter#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
2250251881Speter  *executable = FALSE;
2251251881Speter#endif
2252251881Speter
2253251881Speter  return SVN_NO_ERROR;
2254251881Speter}
2255251881Speter
2256251881Spetersvn_error_t *
2257251881Spetersvn_io_is_file_executable(svn_boolean_t *executable,
2258251881Speter                          const char *path,
2259251881Speter                          apr_pool_t *pool)
2260251881Speter{
2261251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
2262251881Speter  apr_finfo_t file_info;
2263251881Speter
2264251881Speter  SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER,
2265251881Speter                      pool));
2266251881Speter  SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool));
2267251881Speter
2268251881Speter#else  /* WIN32 || __OS2__ || !APR_HAS_USER */
2269251881Speter  *executable = FALSE;
2270251881Speter#endif
2271251881Speter
2272251881Speter  return SVN_NO_ERROR;
2273251881Speter}
2274251881Speter
2275251881Speter
2276251881Speter/*** File locking. ***/
2277251881Speter#if !defined(WIN32) && !defined(__OS2__)
2278251881Speter/* Clear all outstanding locks on ARG, an open apr_file_t *. */
2279251881Speterstatic apr_status_t
2280251881Speterfile_clear_locks(void *arg)
2281251881Speter{
2282251881Speter  apr_status_t apr_err;
2283251881Speter  apr_file_t *f = arg;
2284251881Speter
2285251881Speter  /* Remove locks. */
2286251881Speter  apr_err = apr_file_unlock(f);
2287251881Speter  if (apr_err)
2288251881Speter    return apr_err;
2289251881Speter
2290251881Speter  return 0;
2291251881Speter}
2292251881Speter#endif
2293251881Speter
2294251881Spetersvn_error_t *
2295251881Spetersvn_io_lock_open_file(apr_file_t *lockfile_handle,
2296251881Speter                      svn_boolean_t exclusive,
2297251881Speter                      svn_boolean_t nonblocking,
2298251881Speter                      apr_pool_t *pool)
2299251881Speter{
2300251881Speter  int locktype = APR_FLOCK_SHARED;
2301251881Speter  apr_status_t apr_err;
2302251881Speter  const char *fname;
2303251881Speter
2304251881Speter  if (exclusive)
2305251881Speter    locktype = APR_FLOCK_EXCLUSIVE;
2306251881Speter  if (nonblocking)
2307251881Speter    locktype |= APR_FLOCK_NONBLOCK;
2308251881Speter
2309251881Speter  /* We need this only in case of an error but this is cheap to get -
2310251881Speter   * so we do it here for clarity. */
2311251881Speter  apr_err = apr_file_name_get(&fname, lockfile_handle);
2312251881Speter  if (apr_err)
2313251881Speter    return svn_error_wrap_apr(apr_err, _("Can't get file name"));
2314251881Speter
2315251881Speter  /* Get lock on the filehandle. */
2316251881Speter  apr_err = apr_file_lock(lockfile_handle, locktype);
2317251881Speter
2318251881Speter  /* In deployments with two or more multithreaded servers running on
2319251881Speter     the same system serving two or more fsfs repositories it is
2320251881Speter     possible for a deadlock to occur when getting a write lock on
2321251881Speter     db/txn-current-lock:
2322251881Speter
2323251881Speter     Process 1                         Process 2
2324251881Speter     ---------                         ---------
2325251881Speter     thread 1: get lock in repos A
2326251881Speter                                       thread 1: get lock in repos B
2327251881Speter                                       thread 2: block getting lock in repos A
2328251881Speter     thread 2: try to get lock in B *** deadlock ***
2329251881Speter
2330251881Speter     Retry for a while for the deadlock to clear. */
2331251881Speter  FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype));
2332251881Speter
2333251881Speter  if (apr_err)
2334251881Speter    {
2335251881Speter      switch (locktype & APR_FLOCK_TYPEMASK)
2336251881Speter        {
2337251881Speter        case APR_FLOCK_SHARED:
2338251881Speter          return svn_error_wrap_apr(apr_err,
2339251881Speter                                    _("Can't get shared lock on file '%s'"),
2340251881Speter                                    try_utf8_from_internal_style(fname, pool));
2341251881Speter        case APR_FLOCK_EXCLUSIVE:
2342251881Speter          return svn_error_wrap_apr(apr_err,
2343251881Speter                                    _("Can't get exclusive lock on file '%s'"),
2344251881Speter                                    try_utf8_from_internal_style(fname, pool));
2345251881Speter        default:
2346251881Speter          SVN_ERR_MALFUNCTION();
2347251881Speter        }
2348251881Speter    }
2349251881Speter
2350251881Speter/* On Windows and OS/2 file locks are automatically released when
2351251881Speter   the file handle closes */
2352251881Speter#if !defined(WIN32) && !defined(__OS2__)
2353251881Speter  apr_pool_cleanup_register(pool, lockfile_handle,
2354251881Speter                            file_clear_locks,
2355251881Speter                            apr_pool_cleanup_null);
2356251881Speter#endif
2357251881Speter
2358251881Speter  return SVN_NO_ERROR;
2359251881Speter}
2360251881Speter
2361251881Spetersvn_error_t *
2362251881Spetersvn_io_unlock_open_file(apr_file_t *lockfile_handle,
2363251881Speter                        apr_pool_t *pool)
2364251881Speter{
2365251881Speter  const char *fname;
2366251881Speter  apr_status_t apr_err;
2367251881Speter
2368251881Speter  /* We need this only in case of an error but this is cheap to get -
2369251881Speter   * so we do it here for clarity. */
2370251881Speter  apr_err = apr_file_name_get(&fname, lockfile_handle);
2371251881Speter  if (apr_err)
2372251881Speter    return svn_error_wrap_apr(apr_err, _("Can't get file name"));
2373251881Speter
2374251881Speter  /* The actual unlock attempt. */
2375251881Speter  apr_err = apr_file_unlock(lockfile_handle);
2376251881Speter  if (apr_err)
2377251881Speter    return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"),
2378251881Speter                              try_utf8_from_internal_style(fname, pool));
2379251881Speter
2380251881Speter/* On Windows and OS/2 file locks are automatically released when
2381251881Speter   the file handle closes */
2382251881Speter#if !defined(WIN32) && !defined(__OS2__)
2383251881Speter  apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks);
2384251881Speter#endif
2385251881Speter
2386251881Speter  return SVN_NO_ERROR;
2387251881Speter}
2388251881Speter
2389251881Spetersvn_error_t *
2390251881Spetersvn_io_file_lock2(const char *lock_file,
2391251881Speter                  svn_boolean_t exclusive,
2392251881Speter                  svn_boolean_t nonblocking,
2393251881Speter                  apr_pool_t *pool)
2394251881Speter{
2395251881Speter  int locktype = APR_FLOCK_SHARED;
2396251881Speter  apr_file_t *lockfile_handle;
2397251881Speter  apr_int32_t flags;
2398251881Speter
2399251881Speter  if (exclusive)
2400251881Speter    locktype = APR_FLOCK_EXCLUSIVE;
2401251881Speter
2402251881Speter  flags = APR_READ;
2403251881Speter  if (locktype == APR_FLOCK_EXCLUSIVE)
2404251881Speter    flags |= APR_WRITE;
2405251881Speter
2406251881Speter  /* locktype is never read after this block, so we don't need to bother
2407251881Speter     setting it.  If that were to ever change, uncomment the following
2408251881Speter     block.
2409251881Speter  if (nonblocking)
2410251881Speter    locktype |= APR_FLOCK_NONBLOCK;
2411251881Speter  */
2412251881Speter
2413251881Speter  SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags,
2414251881Speter                           APR_OS_DEFAULT,
2415251881Speter                           pool));
2416251881Speter
2417251881Speter  /* Get lock on the filehandle. */
2418251881Speter  return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool);
2419251881Speter}
2420251881Speter
2421299742Sdimsvn_error_t *
2422299742Sdimsvn_io__file_lock_autocreate(const char *lock_file,
2423299742Sdim                             apr_pool_t *pool)
2424299742Sdim{
2425299742Sdim  svn_error_t *err
2426299742Sdim    = svn_io_file_lock2(lock_file, TRUE, FALSE, pool);
2427299742Sdim  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
2428299742Sdim    {
2429299742Sdim      /* No lock file?  No big deal; these are just empty files anyway.
2430299742Sdim         Create it and try again. */
2431299742Sdim      svn_error_clear(err);
2432251881Speter
2433299742Sdim      /* This file creation is racy.
2434299742Sdim         We don't care as long as file gets created at all. */
2435299742Sdim      err = svn_io_file_create_empty(lock_file, pool);
2436299742Sdim      if (err && APR_STATUS_IS_EEXIST(err->apr_err))
2437299742Sdim        {
2438299742Sdim          svn_error_clear(err);
2439299742Sdim          err = NULL;
2440299742Sdim        }
2441299742Sdim
2442299742Sdim      /* Finally, lock the file - if it exists */
2443299742Sdim      if (!err)
2444299742Sdim        err = svn_io_file_lock2(lock_file, TRUE, FALSE, pool);
2445299742Sdim    }
2446299742Sdim
2447299742Sdim  return svn_error_trace(err);
2448299742Sdim}
2449299742Sdim
2450299742Sdim
2451251881Speter
2452251881Speter/* Data consistency/coherency operations. */
2453251881Speter
2454251881Spetersvn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
2455251881Speter                                       apr_pool_t *pool)
2456251881Speter{
2457251881Speter  apr_os_file_t filehand;
2458251881Speter
2459299742Sdim  /* ### In apr 1.4+ we could delegate most of this function to
2460299742Sdim         apr_file_sync(). The only major difference is that this doesn't
2461299742Sdim         contain the retry loop for EINTR on linux. */
2462299742Sdim
2463251881Speter  /* First make sure that any user-space buffered data is flushed. */
2464299742Sdim  SVN_ERR(svn_io_file_flush(file, pool));
2465251881Speter
2466251881Speter  apr_os_file_get(&filehand, file);
2467251881Speter
2468251881Speter  /* Call the operating system specific function to actually force the
2469251881Speter     data to disk. */
2470251881Speter  {
2471251881Speter#ifdef WIN32
2472251881Speter
2473251881Speter    if (! FlushFileBuffers(filehand))
2474251881Speter        return svn_error_wrap_apr(apr_get_os_error(),
2475251881Speter                                  _("Can't flush file to disk"));
2476251881Speter
2477251881Speter#else
2478251881Speter      int rv;
2479251881Speter
2480251881Speter      do {
2481299742Sdim#ifdef F_FULLFSYNC
2482299742Sdim        rv = fcntl(filehand, F_FULLFSYNC, 0);
2483299742Sdim#else
2484251881Speter        rv = fsync(filehand);
2485299742Sdim#endif
2486251881Speter      } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
2487251881Speter
2488251881Speter      /* If the file is in a memory filesystem, fsync() may return
2489251881Speter         EINVAL.  Presumably the user knows the risks, and we can just
2490251881Speter         ignore the error. */
2491251881Speter      if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error()))
2492251881Speter        return SVN_NO_ERROR;
2493251881Speter
2494251881Speter      if (rv == -1)
2495251881Speter        return svn_error_wrap_apr(apr_get_os_error(),
2496251881Speter                                  _("Can't flush file to disk"));
2497251881Speter
2498251881Speter#endif
2499251881Speter  }
2500251881Speter  return SVN_NO_ERROR;
2501251881Speter}
2502251881Speter
2503251881Speter
2504251881Speter
2505251881Speter/* TODO write test for these two functions, then refactor. */
2506251881Speter
2507251881Speter/* Set RESULT to an svn_stringbuf_t containing the contents of FILE.
2508251881Speter   FILENAME is the FILE's on-disk APR-safe name, or NULL if that name
2509251881Speter   isn't known.  If CHECK_SIZE is TRUE, the function will attempt to
2510251881Speter   first stat() the file to determine it's size before sucking its
2511251881Speter   contents into the stringbuf.  (Doing so can prevent unnecessary
2512251881Speter   memory usage, an unwanted side effect of the stringbuf growth and
2513251881Speter   reallocation mechanism.)  */
2514251881Speterstatic svn_error_t *
2515251881Speterstringbuf_from_aprfile(svn_stringbuf_t **result,
2516251881Speter                       const char *filename,
2517251881Speter                       apr_file_t *file,
2518251881Speter                       svn_boolean_t check_size,
2519251881Speter                       apr_pool_t *pool)
2520251881Speter{
2521251881Speter  apr_size_t len;
2522251881Speter  svn_error_t *err;
2523251881Speter  svn_stringbuf_t *res = NULL;
2524251881Speter  apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE;
2525299742Sdim  char *buf;
2526251881Speter
2527251881Speter  /* If our caller wants us to check the size of the file for
2528251881Speter     efficient memory handling, we'll try to do so. */
2529251881Speter  if (check_size)
2530251881Speter    {
2531299742Sdim      apr_finfo_t finfo = { 0 };
2532251881Speter
2533299742Sdim      /* In some cases we get size 0 and no error for non files,
2534299742Sdim          so we also check for the name. (= cached in apr_file_t) */
2535299742Sdim      if (! apr_file_info_get(&finfo, APR_FINFO_SIZE, file) && finfo.fname)
2536251881Speter        {
2537299742Sdim          /* we've got the file length. Now, read it in one go. */
2538299742Sdim          svn_boolean_t eof;
2539299742Sdim          res_initial_len = (apr_size_t)finfo.size;
2540299742Sdim          res = svn_stringbuf_create_ensure(res_initial_len, pool);
2541299742Sdim          SVN_ERR(svn_io_file_read_full2(file, res->data,
2542299742Sdim                                         res_initial_len, &res->len,
2543299742Sdim                                         &eof, pool));
2544299742Sdim          res->data[res->len] = 0;
2545251881Speter
2546299742Sdim          *result = res;
2547299742Sdim          return SVN_NO_ERROR;
2548251881Speter        }
2549251881Speter    }
2550251881Speter
2551251881Speter  /* XXX: We should check the incoming data for being of type binary. */
2552299742Sdim  buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
2553251881Speter  res = svn_stringbuf_create_ensure(res_initial_len, pool);
2554251881Speter
2555251881Speter  /* apr_file_read will not return data and eof in the same call. So this loop
2556251881Speter   * is safe from missing read data.  */
2557251881Speter  len = SVN__STREAM_CHUNK_SIZE;
2558251881Speter  err = svn_io_file_read(file, buf, &len, pool);
2559251881Speter  while (! err)
2560251881Speter    {
2561251881Speter      svn_stringbuf_appendbytes(res, buf, len);
2562251881Speter      len = SVN__STREAM_CHUNK_SIZE;
2563251881Speter      err = svn_io_file_read(file, buf, &len, pool);
2564251881Speter    }
2565251881Speter
2566251881Speter  /* Having read all the data we *expect* EOF */
2567251881Speter  if (err && !APR_STATUS_IS_EOF(err->apr_err))
2568299742Sdim    return svn_error_trace(err);
2569251881Speter  svn_error_clear(err);
2570251881Speter
2571251881Speter  *result = res;
2572251881Speter  return SVN_NO_ERROR;
2573251881Speter}
2574251881Speter
2575251881Spetersvn_error_t *
2576251881Spetersvn_stringbuf_from_file2(svn_stringbuf_t **result,
2577251881Speter                         const char *filename,
2578251881Speter                         apr_pool_t *pool)
2579251881Speter{
2580251881Speter  apr_file_t *f;
2581251881Speter
2582251881Speter  if (filename[0] == '-' && filename[1] == '\0')
2583251881Speter    {
2584251881Speter      apr_status_t apr_err;
2585251881Speter      if ((apr_err = apr_file_open_stdin(&f, pool)))
2586251881Speter        return svn_error_wrap_apr(apr_err, _("Can't open stdin"));
2587251881Speter      SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool));
2588251881Speter    }
2589251881Speter  else
2590251881Speter    {
2591251881Speter      SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool));
2592251881Speter      SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool));
2593251881Speter    }
2594251881Speter  return svn_io_file_close(f, pool);
2595251881Speter}
2596251881Speter
2597251881Speter
2598251881Spetersvn_error_t *
2599251881Spetersvn_stringbuf_from_file(svn_stringbuf_t **result,
2600251881Speter                        const char *filename,
2601251881Speter                        apr_pool_t *pool)
2602251881Speter{
2603251881Speter  if (filename[0] == '-' && filename[1] == '\0')
2604251881Speter    return svn_error_create
2605251881Speter        (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2606251881Speter         _("Reading from stdin is disallowed"));
2607251881Speter  return svn_stringbuf_from_file2(result, filename, pool);
2608251881Speter}
2609251881Speter
2610251881Spetersvn_error_t *
2611251881Spetersvn_stringbuf_from_aprfile(svn_stringbuf_t **result,
2612251881Speter                           apr_file_t *file,
2613251881Speter                           apr_pool_t *pool)
2614251881Speter{
2615251881Speter  return stringbuf_from_aprfile(result, NULL, file, TRUE, pool);
2616251881Speter}
2617251881Speter
2618251881Speter
2619251881Speter
2620251881Speter/* Deletion. */
2621251881Speter
2622251881Spetersvn_error_t *
2623251881Spetersvn_io_remove_file2(const char *path,
2624251881Speter                    svn_boolean_t ignore_enoent,
2625251881Speter                    apr_pool_t *scratch_pool)
2626251881Speter{
2627251881Speter  apr_status_t apr_err;
2628251881Speter  const char *path_apr;
2629251881Speter
2630251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool));
2631251881Speter
2632251881Speter  apr_err = apr_file_remove(path_apr, scratch_pool);
2633251881Speter
2634251881Speter#ifdef WIN32
2635251881Speter  /* If the target is read only NTFS reports EACCESS and FAT/FAT32
2636251881Speter     reports EEXIST */
2637251881Speter  if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err))
2638251881Speter    {
2639251881Speter      /* Set the destination file writable because Windows will not
2640251881Speter         allow us to delete when path is read-only */
2641251881Speter      SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool));
2642251881Speter      apr_err = apr_file_remove(path_apr, scratch_pool);
2643251881Speter
2644251881Speter      if (!apr_err)
2645251881Speter        return SVN_NO_ERROR;
2646251881Speter    }
2647251881Speter
2648299742Sdim  /* Check to make sure we aren't trying to delete a directory */
2649299742Sdim  if (apr_err == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED)
2650299742Sdim      || apr_err == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION))
2651251881Speter    {
2652299742Sdim      apr_finfo_t finfo;
2653299742Sdim
2654299742Sdim      if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool)
2655299742Sdim          && finfo.filetype == APR_REG)
2656251881Speter        {
2657299742Sdim          WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr, scratch_pool));
2658251881Speter        }
2659299742Sdim    }
2660251881Speter
2661299742Sdim  /* Just return the delete error */
2662251881Speter#endif
2663251881Speter
2664299742Sdim  if (!apr_err)
2665299742Sdim    {
2666299742Sdim      return SVN_NO_ERROR;
2667299742Sdim    }
2668299742Sdim  else if (ignore_enoent && (APR_STATUS_IS_ENOENT(apr_err)
2669299742Sdim                             || SVN__APR_STATUS_IS_ENOTDIR(apr_err)))
2670299742Sdim    {
2671299742Sdim      return SVN_NO_ERROR;
2672299742Sdim    }
2673299742Sdim  else
2674299742Sdim    {
2675299742Sdim      return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"),
2676299742Sdim                                svn_dirent_local_style(path, scratch_pool));
2677299742Sdim    }
2678251881Speter}
2679251881Speter
2680251881Speter
2681251881Spetersvn_error_t *
2682251881Spetersvn_io_remove_dir(const char *path, apr_pool_t *pool)
2683251881Speter{
2684251881Speter  return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
2685251881Speter}
2686251881Speter
2687251881Speter/*
2688251881Speter Mac OS X has a bug where if you're reading the contents of a
2689251881Speter directory via readdir in a loop, and you remove one of the entries in
2690251881Speter the directory and the directory has 338 or more files in it you will
2691251881Speter skip over some of the entries in the directory.  Needless to say,
2692251881Speter this causes problems if you are using this kind of loop inside a
2693251881Speter function that is recursively deleting a directory, because when you
2694251881Speter get around to removing the directory it will still have something in
2695251881Speter it. A similar problem has been observed in other BSDs. This bug has
2696251881Speter since been fixed. See http://www.vnode.ch/fixing_seekdir for details.
2697251881Speter
2698251881Speter The workaround is to delete the files only _after_ the initial
2699251881Speter directory scan.  A previous workaround involving rewinddir is
2700251881Speter problematic on Win32 and some NFS clients, notably NetBSD.
2701251881Speter
2702251881Speter See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and
2703251881Speter http://subversion.tigris.org/issues/show_bug.cgi?id=3501.
2704251881Speter*/
2705251881Speter
2706251881Speter/* Neither windows nor unix allows us to delete a non-empty
2707251881Speter   directory.
2708251881Speter
2709251881Speter   This is a function to perform the equivalent of 'rm -rf'. */
2710251881Spetersvn_error_t *
2711251881Spetersvn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
2712251881Speter                   svn_cancel_func_t cancel_func, void *cancel_baton,
2713251881Speter                   apr_pool_t *pool)
2714251881Speter{
2715251881Speter  svn_error_t *err;
2716251881Speter  apr_pool_t *subpool;
2717251881Speter  apr_hash_t *dirents;
2718251881Speter  apr_hash_index_t *hi;
2719251881Speter
2720251881Speter  /* Check for pending cancellation request.
2721251881Speter     If we need to bail out, do so early. */
2722251881Speter
2723251881Speter  if (cancel_func)
2724251881Speter    SVN_ERR((*cancel_func)(cancel_baton));
2725251881Speter
2726251881Speter  subpool = svn_pool_create(pool);
2727251881Speter
2728251881Speter  err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool);
2729251881Speter  if (err)
2730251881Speter    {
2731251881Speter      /* if the directory doesn't exist, our mission is accomplished */
2732299742Sdim      if (ignore_enoent && (APR_STATUS_IS_ENOENT(err->apr_err)
2733299742Sdim                            || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
2734251881Speter        {
2735251881Speter          svn_error_clear(err);
2736251881Speter          return SVN_NO_ERROR;
2737251881Speter        }
2738251881Speter      return svn_error_trace(err);
2739251881Speter    }
2740251881Speter
2741251881Speter  for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
2742251881Speter    {
2743299742Sdim      const char *name = apr_hash_this_key(hi);
2744299742Sdim      const svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
2745251881Speter      const char *fullpath;
2746251881Speter
2747251881Speter      fullpath = svn_dirent_join(path, name, subpool);
2748251881Speter      if (dirent->kind == svn_node_dir)
2749251881Speter        {
2750251881Speter          /* Don't check for cancellation, the callee will immediately do so */
2751251881Speter          SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func,
2752251881Speter                                     cancel_baton, subpool));
2753251881Speter        }
2754251881Speter      else
2755251881Speter        {
2756251881Speter          if (cancel_func)
2757251881Speter            SVN_ERR((*cancel_func)(cancel_baton));
2758251881Speter
2759251881Speter          err = svn_io_remove_file2(fullpath, FALSE, subpool);
2760251881Speter          if (err)
2761251881Speter            return svn_error_createf
2762251881Speter              (err->apr_err, err, _("Can't remove '%s'"),
2763251881Speter               svn_dirent_local_style(fullpath, subpool));
2764251881Speter        }
2765251881Speter    }
2766251881Speter
2767251881Speter  svn_pool_destroy(subpool);
2768251881Speter
2769251881Speter  return svn_io_dir_remove_nonrecursive(path, pool);
2770251881Speter}
2771251881Speter
2772251881Spetersvn_error_t *
2773251881Spetersvn_io_get_dir_filenames(apr_hash_t **dirents,
2774251881Speter                         const char *path,
2775251881Speter                         apr_pool_t *pool)
2776251881Speter{
2777251881Speter  return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE,
2778251881Speter                                             pool, pool));
2779251881Speter}
2780251881Speter
2781251881Spetersvn_io_dirent2_t *
2782251881Spetersvn_io_dirent2_create(apr_pool_t *result_pool)
2783251881Speter{
2784251881Speter  svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent));
2785251881Speter
2786251881Speter  /*dirent->kind = svn_node_none;
2787251881Speter  dirent->special = FALSE;*/
2788251881Speter  dirent->filesize = SVN_INVALID_FILESIZE;
2789251881Speter  /*dirent->mtime = 0;*/
2790251881Speter
2791251881Speter  return dirent;
2792251881Speter}
2793251881Speter
2794251881Spetersvn_io_dirent2_t *
2795251881Spetersvn_io_dirent2_dup(const svn_io_dirent2_t *item,
2796251881Speter                   apr_pool_t *result_pool)
2797251881Speter{
2798251881Speter  return apr_pmemdup(result_pool,
2799251881Speter                     item,
2800251881Speter                     sizeof(*item));
2801251881Speter}
2802251881Speter
2803251881Spetersvn_error_t *
2804251881Spetersvn_io_get_dirents3(apr_hash_t **dirents,
2805251881Speter                    const char *path,
2806251881Speter                    svn_boolean_t only_check_type,
2807251881Speter                    apr_pool_t *result_pool,
2808251881Speter                    apr_pool_t *scratch_pool)
2809251881Speter{
2810251881Speter  apr_status_t status;
2811251881Speter  apr_dir_t *this_dir;
2812251881Speter  apr_finfo_t this_entry;
2813251881Speter  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
2814251881Speter
2815251881Speter  if (!only_check_type)
2816251881Speter    flags |= APR_FINFO_SIZE | APR_FINFO_MTIME;
2817251881Speter
2818251881Speter  *dirents = apr_hash_make(result_pool);
2819251881Speter
2820251881Speter  SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool));
2821251881Speter
2822251881Speter  for (status = apr_dir_read(&this_entry, flags, this_dir);
2823251881Speter       status == APR_SUCCESS;
2824251881Speter       status = apr_dir_read(&this_entry, flags, this_dir))
2825251881Speter    {
2826251881Speter      if ((this_entry.name[0] == '.')
2827251881Speter          && ((this_entry.name[1] == '\0')
2828251881Speter              || ((this_entry.name[1] == '.')
2829251881Speter                  && (this_entry.name[2] == '\0'))))
2830251881Speter        {
2831251881Speter          continue;
2832251881Speter        }
2833251881Speter      else
2834251881Speter        {
2835251881Speter          const char *name;
2836251881Speter          svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool);
2837251881Speter
2838251881Speter          SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool));
2839251881Speter
2840251881Speter          map_apr_finfo_to_node_kind(&(dirent->kind),
2841251881Speter                                     &(dirent->special),
2842251881Speter                                     &this_entry);
2843251881Speter
2844251881Speter          if (!only_check_type)
2845251881Speter            {
2846251881Speter              dirent->filesize = this_entry.size;
2847251881Speter              dirent->mtime = this_entry.mtime;
2848251881Speter            }
2849251881Speter
2850251881Speter          svn_hash_sets(*dirents, name, dirent);
2851251881Speter        }
2852251881Speter    }
2853251881Speter
2854251881Speter  if (! (APR_STATUS_IS_ENOENT(status)))
2855251881Speter    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
2856251881Speter                              svn_dirent_local_style(path, scratch_pool));
2857251881Speter
2858251881Speter  status = apr_dir_close(this_dir);
2859251881Speter  if (status)
2860251881Speter    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
2861251881Speter                              svn_dirent_local_style(path, scratch_pool));
2862251881Speter
2863251881Speter  return SVN_NO_ERROR;
2864251881Speter}
2865251881Speter
2866251881Spetersvn_error_t *
2867251881Spetersvn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p,
2868251881Speter                    const char *path,
2869251881Speter                    svn_boolean_t verify_truename,
2870251881Speter                    svn_boolean_t ignore_enoent,
2871251881Speter                    apr_pool_t *result_pool,
2872251881Speter                    apr_pool_t *scratch_pool)
2873251881Speter{
2874251881Speter  apr_finfo_t finfo;
2875251881Speter  svn_io_dirent2_t *dirent;
2876251881Speter  svn_error_t *err;
2877251881Speter  apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK
2878251881Speter                       | APR_FINFO_SIZE | APR_FINFO_MTIME;
2879251881Speter
2880251881Speter#if defined(WIN32) || defined(__OS2__)
2881251881Speter  if (verify_truename)
2882251881Speter    wanted |= APR_FINFO_NAME;
2883251881Speter#endif
2884251881Speter
2885251881Speter  err = svn_io_stat(&finfo, path, wanted, scratch_pool);
2886251881Speter
2887251881Speter  if (err && ignore_enoent &&
2888251881Speter      (APR_STATUS_IS_ENOENT(err->apr_err)
2889251881Speter       || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
2890251881Speter    {
2891251881Speter      svn_error_clear(err);
2892251881Speter      dirent = svn_io_dirent2_create(result_pool);
2893251881Speter      SVN_ERR_ASSERT(dirent->kind == svn_node_none);
2894251881Speter
2895251881Speter      *dirent_p = dirent;
2896251881Speter      return SVN_NO_ERROR;
2897251881Speter    }
2898251881Speter  SVN_ERR(err);
2899251881Speter
2900251881Speter#if defined(WIN32) || defined(__OS2__) || defined(DARWIN)
2901251881Speter  if (verify_truename)
2902251881Speter    {
2903251881Speter      const char *requested_name = svn_dirent_basename(path, NULL);
2904251881Speter
2905251881Speter      if (requested_name[0] == '\0')
2906251881Speter        {
2907251881Speter          /* No parent directory. No need to stat/verify */
2908251881Speter        }
2909251881Speter#if defined(WIN32) || defined(__OS2__)
2910251881Speter      else if (finfo.name)
2911251881Speter        {
2912251881Speter          const char *name_on_disk;
2913251881Speter          SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path,
2914251881Speter                                     scratch_pool));
2915251881Speter
2916251881Speter          if (strcmp(name_on_disk, requested_name) /* != 0 */)
2917251881Speter            {
2918251881Speter              if (ignore_enoent)
2919251881Speter                {
2920251881Speter                  *dirent_p = svn_io_dirent2_create(result_pool);
2921251881Speter                  return SVN_NO_ERROR;
2922251881Speter                }
2923251881Speter              else
2924251881Speter                return svn_error_createf(APR_ENOENT, NULL,
2925251881Speter                          _("Path '%s' not found, case obstructed by '%s'"),
2926251881Speter                          svn_dirent_local_style(path, scratch_pool),
2927251881Speter                          name_on_disk);
2928251881Speter            }
2929251881Speter        }
2930251881Speter#elif defined(DARWIN)
2931251881Speter      /* Currently apr doesn't set finfo.name on DARWIN, returning
2932251881Speter                   APR_INCOMPLETE.
2933251881Speter         ### Can we optimize this in another way? */
2934251881Speter      else
2935251881Speter        {
2936251881Speter          apr_hash_t *dirents;
2937251881Speter
2938251881Speter          err = svn_io_get_dirents3(&dirents,
2939251881Speter                                    svn_dirent_dirname(path, scratch_pool),
2940251881Speter                                    TRUE /* only_check_type */,
2941251881Speter                                    scratch_pool, scratch_pool);
2942251881Speter
2943251881Speter          if (err && ignore_enoent
2944251881Speter              && (APR_STATUS_IS_ENOENT(err->apr_err)
2945251881Speter                  || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
2946251881Speter            {
2947251881Speter              svn_error_clear(err);
2948251881Speter
2949251881Speter              *dirent_p = svn_io_dirent2_create(result_pool);
2950251881Speter              return SVN_NO_ERROR;
2951251881Speter            }
2952251881Speter          else
2953251881Speter            SVN_ERR(err);
2954251881Speter
2955251881Speter          if (! svn_hash_gets(dirents, requested_name))
2956251881Speter            {
2957251881Speter              if (ignore_enoent)
2958251881Speter                {
2959251881Speter                  *dirent_p = svn_io_dirent2_create(result_pool);
2960251881Speter                  return SVN_NO_ERROR;
2961251881Speter                }
2962251881Speter              else
2963251881Speter                return svn_error_createf(APR_ENOENT, NULL,
2964251881Speter                          _("Path '%s' not found"),
2965251881Speter                          svn_dirent_local_style(path, scratch_pool));
2966251881Speter            }
2967251881Speter        }
2968251881Speter#endif
2969251881Speter    }
2970251881Speter#endif
2971251881Speter
2972251881Speter  dirent = svn_io_dirent2_create(result_pool);
2973251881Speter  map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo);
2974251881Speter
2975251881Speter  dirent->filesize = finfo.size;
2976251881Speter  dirent->mtime = finfo.mtime;
2977251881Speter
2978251881Speter  *dirent_p = dirent;
2979251881Speter
2980251881Speter  return SVN_NO_ERROR;
2981251881Speter}
2982251881Speter
2983251881Speter/* Pool userdata key for the error file passed to svn_io_start_cmd(). */
2984251881Speter#define ERRFILE_KEY "svn-io-start-cmd-errfile"
2985251881Speter
2986251881Speter/* Handle an error from the child process (before command execution) by
2987251881Speter   printing DESC and the error string corresponding to STATUS to stderr. */
2988251881Speterstatic void
2989251881Speterhandle_child_process_error(apr_pool_t *pool, apr_status_t status,
2990251881Speter                           const char *desc)
2991251881Speter{
2992251881Speter  char errbuf[256];
2993251881Speter  apr_file_t *errfile;
2994251881Speter  void *p;
2995251881Speter
2996251881Speter  /* We can't do anything if we get an error here, so just return. */
2997251881Speter  if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool))
2998251881Speter    return;
2999251881Speter  errfile = p;
3000251881Speter
3001251881Speter  if (errfile)
3002251881Speter    /* What we get from APR is in native encoding. */
3003251881Speter    apr_file_printf(errfile, "%s: %s",
3004251881Speter                    desc, apr_strerror(status, errbuf,
3005251881Speter                                       sizeof(errbuf)));
3006251881Speter}
3007251881Speter
3008251881Speter
3009251881Spetersvn_error_t *
3010251881Spetersvn_io_start_cmd3(apr_proc_t *cmd_proc,
3011251881Speter                  const char *path,
3012251881Speter                  const char *cmd,
3013251881Speter                  const char *const *args,
3014251881Speter                  const char *const *env,
3015251881Speter                  svn_boolean_t inherit,
3016251881Speter                  svn_boolean_t infile_pipe,
3017251881Speter                  apr_file_t *infile,
3018251881Speter                  svn_boolean_t outfile_pipe,
3019251881Speter                  apr_file_t *outfile,
3020251881Speter                  svn_boolean_t errfile_pipe,
3021251881Speter                  apr_file_t *errfile,
3022251881Speter                  apr_pool_t *pool)
3023251881Speter{
3024251881Speter  apr_status_t apr_err;
3025251881Speter  apr_procattr_t *cmdproc_attr;
3026251881Speter  int num_args;
3027251881Speter  const char **args_native;
3028251881Speter  const char *cmd_apr;
3029251881Speter
3030251881Speter  SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe));
3031251881Speter  SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe));
3032251881Speter  SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe));
3033251881Speter
3034251881Speter  /* Create the process attributes. */
3035251881Speter  apr_err = apr_procattr_create(&cmdproc_attr, pool);
3036251881Speter  if (apr_err)
3037251881Speter    return svn_error_wrap_apr(apr_err,
3038251881Speter                              _("Can't create process '%s' attributes"),
3039251881Speter                              cmd);
3040251881Speter
3041251881Speter  /* Make sure we invoke cmd directly, not through a shell. */
3042251881Speter  apr_err = apr_procattr_cmdtype_set(cmdproc_attr,
3043251881Speter                                     inherit ? APR_PROGRAM_PATH : APR_PROGRAM);
3044251881Speter  if (apr_err)
3045251881Speter    return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"),
3046251881Speter                              cmd);
3047251881Speter
3048251881Speter  /* Set the process's working directory. */
3049251881Speter  if (path)
3050251881Speter    {
3051251881Speter      const char *path_apr;
3052251881Speter
3053299742Sdim      /* APR doesn't like our canonical path format for current directory */
3054299742Sdim      if (path[0] == '\0')
3055299742Sdim        path = ".";
3056299742Sdim
3057251881Speter      SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
3058251881Speter      apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr);
3059251881Speter      if (apr_err)
3060251881Speter        return svn_error_wrap_apr(apr_err,
3061251881Speter                                  _("Can't set process '%s' directory"),
3062251881Speter                                  cmd);
3063251881Speter    }
3064251881Speter
3065251881Speter  /* Use requested inputs and outputs.
3066251881Speter
3067251881Speter     ### Unfortunately each of these apr functions creates a pipe and then
3068251881Speter     overwrites the pipe file descriptor with the descriptor we pass
3069251881Speter     in. The pipes can then never be closed. This is an APR bug. */
3070251881Speter  if (infile)
3071251881Speter    {
3072251881Speter      apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL);
3073251881Speter      if (apr_err)
3074251881Speter        return svn_error_wrap_apr(apr_err,
3075251881Speter                                  _("Can't set process '%s' child input"),
3076251881Speter                                  cmd);
3077251881Speter    }
3078251881Speter  if (outfile)
3079251881Speter    {
3080251881Speter      apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL);
3081251881Speter      if (apr_err)
3082251881Speter        return svn_error_wrap_apr(apr_err,
3083251881Speter                                  _("Can't set process '%s' child outfile"),
3084251881Speter                                  cmd);
3085251881Speter    }
3086251881Speter  if (errfile)
3087251881Speter    {
3088251881Speter      apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL);
3089251881Speter      if (apr_err)
3090251881Speter        return svn_error_wrap_apr(apr_err,
3091251881Speter                                  _("Can't set process '%s' child errfile"),
3092251881Speter                                  cmd);
3093251881Speter    }
3094251881Speter
3095251881Speter  /* Forward request for pipes to APR. */
3096251881Speter  if (infile_pipe || outfile_pipe || errfile_pipe)
3097251881Speter    {
3098251881Speter      apr_err = apr_procattr_io_set(cmdproc_attr,
3099251881Speter                                    infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
3100251881Speter                                    outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
3101251881Speter                                    errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE);
3102251881Speter
3103251881Speter      if (apr_err)
3104251881Speter        return svn_error_wrap_apr(apr_err,
3105251881Speter                                  _("Can't set process '%s' stdio pipes"),
3106251881Speter                                  cmd);
3107251881Speter    }
3108251881Speter
3109251881Speter  /* Have the child print any problems executing its program to errfile. */
3110251881Speter  apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool);
3111251881Speter  if (apr_err)
3112251881Speter    return svn_error_wrap_apr(apr_err,
3113251881Speter                              _("Can't set process '%s' child errfile for "
3114251881Speter                                "error handler"),
3115251881Speter                              cmd);
3116251881Speter  apr_err = apr_procattr_child_errfn_set(cmdproc_attr,
3117251881Speter                                         handle_child_process_error);
3118251881Speter  if (apr_err)
3119251881Speter    return svn_error_wrap_apr(apr_err,
3120251881Speter                              _("Can't set process '%s' error handler"),
3121251881Speter                              cmd);
3122251881Speter
3123251881Speter  /* Convert cmd and args from UTF-8 */
3124251881Speter  SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool));
3125251881Speter  for (num_args = 0; args[num_args]; num_args++)
3126251881Speter    ;
3127251881Speter  args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *));
3128251881Speter  args_native[num_args] = NULL;
3129251881Speter  while (num_args--)
3130251881Speter    {
3131251881Speter      /* ### Well, it turns out that on APR on Windows expects all
3132251881Speter             program args to be in UTF-8. Callers of svn_io_run_cmd
3133251881Speter             should be aware of that. */
3134251881Speter      SVN_ERR(cstring_from_utf8(&args_native[num_args],
3135251881Speter                                args[num_args], pool));
3136251881Speter    }
3137251881Speter
3138251881Speter
3139251881Speter  /* Start the cmd command. */
3140251881Speter  apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native,
3141251881Speter                            inherit ? NULL : env, cmdproc_attr, pool);
3142251881Speter  if (apr_err)
3143251881Speter    return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd);
3144251881Speter
3145251881Speter  return SVN_NO_ERROR;
3146251881Speter}
3147251881Speter
3148251881Speter#undef ERRFILE_KEY
3149251881Speter
3150251881Spetersvn_error_t *
3151251881Spetersvn_io_wait_for_cmd(apr_proc_t *cmd_proc,
3152251881Speter                    const char *cmd,
3153251881Speter                    int *exitcode,
3154251881Speter                    apr_exit_why_e *exitwhy,
3155251881Speter                    apr_pool_t *pool)
3156251881Speter{
3157251881Speter  apr_status_t apr_err;
3158251881Speter  apr_exit_why_e exitwhy_val;
3159251881Speter  int exitcode_val;
3160251881Speter
3161251881Speter  /* The Win32 apr_proc_wait doesn't set this... */
3162251881Speter  exitwhy_val = APR_PROC_EXIT;
3163251881Speter
3164251881Speter  /* Wait for the cmd command to finish. */
3165251881Speter  apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT);
3166251881Speter  if (!APR_STATUS_IS_CHILD_DONE(apr_err))
3167251881Speter    return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"),
3168251881Speter                              cmd);
3169251881Speter
3170251881Speter  if (exitwhy)
3171251881Speter    *exitwhy = exitwhy_val;
3172251881Speter  else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)
3173251881Speter           && APR_PROC_CHECK_CORE_DUMP(exitwhy_val))
3174251881Speter    return svn_error_createf
3175251881Speter      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
3176251881Speter       _("Process '%s' failed (signal %d, core dumped)"),
3177251881Speter       cmd, exitcode_val);
3178251881Speter  else if (APR_PROC_CHECK_SIGNALED(exitwhy_val))
3179251881Speter    return svn_error_createf
3180251881Speter      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
3181251881Speter       _("Process '%s' failed (signal %d)"),
3182251881Speter       cmd, exitcode_val);
3183251881Speter  else if (! APR_PROC_CHECK_EXIT(exitwhy_val))
3184251881Speter    /* Don't really know what happened here. */
3185251881Speter    return svn_error_createf
3186251881Speter      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
3187251881Speter       _("Process '%s' failed (exitwhy %d, exitcode %d)"),
3188251881Speter       cmd, exitwhy_val, exitcode_val);
3189251881Speter
3190251881Speter  if (exitcode)
3191251881Speter    *exitcode = exitcode_val;
3192251881Speter  else if (exitcode_val != 0)
3193251881Speter    return svn_error_createf
3194251881Speter      (SVN_ERR_EXTERNAL_PROGRAM, NULL,
3195251881Speter       _("Process '%s' returned error exitcode %d"), cmd, exitcode_val);
3196251881Speter
3197251881Speter  return SVN_NO_ERROR;
3198251881Speter}
3199251881Speter
3200251881Speter
3201251881Spetersvn_error_t *
3202251881Spetersvn_io_run_cmd(const char *path,
3203251881Speter               const char *cmd,
3204251881Speter               const char *const *args,
3205251881Speter               int *exitcode,
3206251881Speter               apr_exit_why_e *exitwhy,
3207251881Speter               svn_boolean_t inherit,
3208251881Speter               apr_file_t *infile,
3209251881Speter               apr_file_t *outfile,
3210251881Speter               apr_file_t *errfile,
3211251881Speter               apr_pool_t *pool)
3212251881Speter{
3213251881Speter  apr_proc_t cmd_proc;
3214251881Speter
3215251881Speter  SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit,
3216251881Speter                            FALSE, infile, FALSE, outfile, FALSE, errfile,
3217251881Speter                            pool));
3218251881Speter
3219251881Speter  return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool);
3220251881Speter}
3221251881Speter
3222251881Speter
3223251881Spetersvn_error_t *
3224251881Spetersvn_io_run_diff2(const char *dir,
3225251881Speter                 const char *const *user_args,
3226251881Speter                 int num_user_args,
3227251881Speter                 const char *label1,
3228251881Speter                 const char *label2,
3229251881Speter                 const char *from,
3230251881Speter                 const char *to,
3231251881Speter                 int *pexitcode,
3232251881Speter                 apr_file_t *outfile,
3233251881Speter                 apr_file_t *errfile,
3234251881Speter                 const char *diff_cmd,
3235251881Speter                 apr_pool_t *pool)
3236251881Speter{
3237251881Speter  const char **args;
3238251881Speter  int i;
3239251881Speter  int exitcode;
3240251881Speter  int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */
3241251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
3242251881Speter
3243251881Speter  if (pexitcode == NULL)
3244251881Speter    pexitcode = &exitcode;
3245251881Speter
3246251881Speter  if (user_args != NULL)
3247251881Speter    nargs += num_user_args;
3248251881Speter  else
3249251881Speter    nargs += 1; /* -u */
3250251881Speter
3251251881Speter  if (label1 != NULL)
3252251881Speter    nargs += 2; /* the -L and the label itself */
3253251881Speter  if (label2 != NULL)
3254251881Speter    nargs += 2; /* the -L and the label itself */
3255251881Speter
3256251881Speter  args = apr_palloc(subpool, nargs * sizeof(char *));
3257251881Speter
3258251881Speter  i = 0;
3259251881Speter  args[i++] = diff_cmd;
3260251881Speter
3261251881Speter  if (user_args != NULL)
3262251881Speter    {
3263251881Speter      int j;
3264251881Speter      for (j = 0; j < num_user_args; ++j)
3265251881Speter        args[i++] = user_args[j];
3266251881Speter    }
3267251881Speter  else
3268251881Speter    args[i++] = "-u"; /* assume -u if the user didn't give us any args */
3269251881Speter
3270251881Speter  if (label1 != NULL)
3271251881Speter    {
3272251881Speter      args[i++] = "-L";
3273251881Speter      args[i++] = label1;
3274251881Speter    }
3275251881Speter  if (label2 != NULL)
3276251881Speter    {
3277251881Speter      args[i++] = "-L";
3278251881Speter      args[i++] = label2;
3279251881Speter    }
3280251881Speter
3281251881Speter  args[i++] = svn_dirent_local_style(from, subpool);
3282251881Speter  args[i++] = svn_dirent_local_style(to, subpool);
3283251881Speter  args[i++] = NULL;
3284251881Speter
3285251881Speter  SVN_ERR_ASSERT(i == nargs);
3286251881Speter
3287251881Speter  SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE,
3288251881Speter                         NULL, outfile, errfile, subpool));
3289251881Speter
3290251881Speter  /* The man page for (GNU) diff describes the return value as:
3291251881Speter
3292251881Speter       "An exit status of 0 means no differences were found, 1 means
3293251881Speter        some differences were found, and 2 means trouble."
3294251881Speter
3295251881Speter     A return value of 2 typically occurs when diff cannot read its input
3296251881Speter     or write to its output, but in any case we probably ought to return an
3297251881Speter     error for anything other than 0 or 1 as the output is likely to be
3298251881Speter     corrupt.
3299251881Speter   */
3300251881Speter  if (*pexitcode != 0 && *pexitcode != 1)
3301251881Speter    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
3302251881Speter                             _("'%s' returned %d"),
3303251881Speter                             svn_dirent_local_style(diff_cmd, pool),
3304251881Speter                             *pexitcode);
3305251881Speter
3306251881Speter  svn_pool_destroy(subpool);
3307251881Speter
3308251881Speter  return SVN_NO_ERROR;
3309251881Speter}
3310251881Speter
3311251881Speter
3312251881Spetersvn_error_t *
3313251881Spetersvn_io_run_diff3_3(int *exitcode,
3314251881Speter                   const char *dir,
3315251881Speter                   const char *mine,
3316251881Speter                   const char *older,
3317251881Speter                   const char *yours,
3318251881Speter                   const char *mine_label,
3319251881Speter                   const char *older_label,
3320251881Speter                   const char *yours_label,
3321251881Speter                   apr_file_t *merged,
3322251881Speter                   const char *diff3_cmd,
3323251881Speter                   const apr_array_header_t *user_args,
3324251881Speter                   apr_pool_t *pool)
3325251881Speter{
3326251881Speter  const char **args = apr_palloc(pool,
3327251881Speter                                 sizeof(char*) * (13
3328251881Speter                                                  + (user_args
3329251881Speter                                                     ? user_args->nelts
3330251881Speter                                                     : 1)));
3331251881Speter#ifndef NDEBUG
3332251881Speter  int nargs = 12;
3333251881Speter#endif
3334251881Speter  int i = 0;
3335251881Speter
3336251881Speter  /* Labels fall back to sensible defaults if not specified. */
3337251881Speter  if (mine_label == NULL)
3338251881Speter    mine_label = ".working";
3339251881Speter  if (older_label == NULL)
3340251881Speter    older_label = ".old";
3341251881Speter  if (yours_label == NULL)
3342251881Speter    yours_label = ".new";
3343251881Speter
3344251881Speter  /* Set up diff3 command line. */
3345251881Speter  args[i++] = diff3_cmd;
3346251881Speter  if (user_args)
3347251881Speter    {
3348251881Speter      int j;
3349251881Speter      for (j = 0; j < user_args->nelts; ++j)
3350251881Speter        args[i++] = APR_ARRAY_IDX(user_args, j, const char *);
3351251881Speter#ifndef NDEBUG
3352251881Speter      nargs += user_args->nelts;
3353251881Speter#endif
3354251881Speter    }
3355251881Speter  else
3356251881Speter    {
3357251881Speter      args[i++] = "-E";             /* We tried "-A" here, but that caused
3358251881Speter                                       overlapping identical changes to
3359251881Speter                                       conflict.  See issue #682. */
3360251881Speter#ifndef NDEBUG
3361251881Speter      ++nargs;
3362251881Speter#endif
3363251881Speter    }
3364251881Speter  args[i++] = "-m";
3365251881Speter  args[i++] = "-L";
3366251881Speter  args[i++] = mine_label;
3367251881Speter  args[i++] = "-L";
3368251881Speter  args[i++] = older_label;      /* note:  this label is ignored if
3369251881Speter                                   using 2-part markers, which is the
3370251881Speter                                   case with "-E". */
3371251881Speter  args[i++] = "-L";
3372251881Speter  args[i++] = yours_label;
3373251881Speter#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG
3374251881Speter  {
3375251881Speter    svn_boolean_t has_arg;
3376251881Speter
3377251881Speter    /* ### FIXME: we really shouldn't be reading the config here;
3378251881Speter       instead, the necessary bits should be passed in by the caller.
3379251881Speter       But should we add another parameter to this function, when the
3380251881Speter       whole external diff3 thing might eventually go away?  */
3381251881Speter    apr_hash_t *config;
3382251881Speter    svn_config_t *cfg;
3383251881Speter
3384251881Speter    SVN_ERR(svn_config_get_config(&config, pool));
3385251881Speter    cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
3386251881Speter    SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS,
3387251881Speter                                SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG,
3388251881Speter                                TRUE));
3389251881Speter    if (has_arg)
3390251881Speter      {
3391251881Speter        const char *diff_cmd, *diff_utf8;
3392251881Speter        svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
3393251881Speter                       SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF);
3394251881Speter        SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool));
3395299742Sdim        args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8,
3396299742Sdim                                SVN_VA_NULL);
3397251881Speter#ifndef NDEBUG
3398251881Speter        ++nargs;
3399251881Speter#endif
3400251881Speter      }
3401251881Speter  }
3402251881Speter#endif
3403251881Speter  args[i++] = svn_dirent_local_style(mine, pool);
3404251881Speter  args[i++] = svn_dirent_local_style(older, pool);
3405251881Speter  args[i++] = svn_dirent_local_style(yours, pool);
3406251881Speter  args[i++] = NULL;
3407251881Speter#ifndef NDEBUG
3408251881Speter  SVN_ERR_ASSERT(i == nargs);
3409251881Speter#endif
3410251881Speter
3411251881Speter  /* Run diff3, output the merged text into the scratch file. */
3412251881Speter  SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args,
3413251881Speter                         exitcode, NULL,
3414251881Speter                         TRUE, /* keep environment */
3415251881Speter                         NULL, merged, NULL,
3416251881Speter                         pool));
3417251881Speter
3418251881Speter  /* According to the diff3 docs, a '0' means the merge was clean, and
3419251881Speter     '1' means conflict markers were found.  Anything else is real
3420251881Speter     error. */
3421251881Speter  if ((*exitcode != 0) && (*exitcode != 1))
3422251881Speter    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
3423251881Speter                             _("Error running '%s':  exitcode was %d, "
3424251881Speter                               "args were:"
3425251881Speter                               "\nin directory '%s', basenames:\n%s\n%s\n%s"),
3426251881Speter                             svn_dirent_local_style(diff3_cmd, pool),
3427251881Speter                             *exitcode,
3428251881Speter                             svn_dirent_local_style(dir, pool),
3429251881Speter                             /* Don't call svn_path_local_style() on
3430251881Speter                                the basenames.  We don't want them to
3431251881Speter                                be absolute, and we don't need the
3432251881Speter                                separator conversion. */
3433251881Speter                             mine, older, yours);
3434251881Speter
3435251881Speter  return SVN_NO_ERROR;
3436251881Speter}
3437251881Speter
3438251881Speter
3439251881Speter/* Canonicalize a string for hashing.  Modifies KEY in place. */
3440251881Speterstatic APR_INLINE char *
3441251881Speterfileext_tolower(char *key)
3442251881Speter{
3443251881Speter  register char *p;
3444251881Speter  for (p = key; *p != 0; ++p)
3445251881Speter    *p = (char)apr_tolower(*p);
3446251881Speter  return key;
3447251881Speter}
3448251881Speter
3449251881Speter
3450251881Spetersvn_error_t *
3451251881Spetersvn_io_parse_mimetypes_file(apr_hash_t **type_map,
3452251881Speter                            const char *mimetypes_file,
3453251881Speter                            apr_pool_t *pool)
3454251881Speter{
3455251881Speter  svn_error_t *err = SVN_NO_ERROR;
3456251881Speter  apr_hash_t *types = apr_hash_make(pool);
3457251881Speter  svn_boolean_t eof = FALSE;
3458251881Speter  svn_stringbuf_t *buf;
3459251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
3460251881Speter  apr_file_t *types_file;
3461251881Speter  svn_stream_t *mimetypes_stream;
3462251881Speter
3463251881Speter  SVN_ERR(svn_io_file_open(&types_file, mimetypes_file,
3464251881Speter                           APR_READ, APR_OS_DEFAULT, pool));
3465251881Speter  mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool);
3466251881Speter
3467251881Speter  while (1)
3468251881Speter    {
3469251881Speter      apr_array_header_t *tokens;
3470251881Speter      const char *type;
3471251881Speter
3472251881Speter      svn_pool_clear(subpool);
3473251881Speter
3474251881Speter      /* Read a line. */
3475251881Speter      if ((err = svn_stream_readline(mimetypes_stream, &buf,
3476251881Speter                                     APR_EOL_STR, &eof, subpool)))
3477251881Speter        break;
3478251881Speter
3479251881Speter      /* Only pay attention to non-empty, non-comment lines. */
3480251881Speter      if (buf->len)
3481251881Speter        {
3482251881Speter          int i;
3483251881Speter
3484251881Speter          if (buf->data[0] == '#')
3485251881Speter            continue;
3486251881Speter
3487251881Speter          /* Tokenize (into our return pool). */
3488251881Speter          tokens = svn_cstring_split(buf->data, " \t", TRUE, pool);
3489251881Speter          if (tokens->nelts < 2)
3490251881Speter            continue;
3491251881Speter
3492251881Speter          /* The first token in a multi-token line is the media type.
3493251881Speter             Subsequent tokens are filename extensions associated with
3494251881Speter             that media type. */
3495251881Speter          type = APR_ARRAY_IDX(tokens, 0, const char *);
3496251881Speter          for (i = 1; i < tokens->nelts; i++)
3497251881Speter            {
3498251881Speter              /* We can safely address 'ext' as a non-const string because
3499251881Speter               * we know svn_cstring_split() allocated it in 'pool' for us. */
3500251881Speter              char *ext = APR_ARRAY_IDX(tokens, i, char *);
3501251881Speter              fileext_tolower(ext);
3502251881Speter              svn_hash_sets(types, ext, type);
3503251881Speter            }
3504251881Speter        }
3505251881Speter      if (eof)
3506251881Speter        break;
3507251881Speter    }
3508251881Speter  svn_pool_destroy(subpool);
3509251881Speter
3510251881Speter  /* If there was an error above, close the file (ignoring any error
3511251881Speter     from *that*) and return the originally error. */
3512251881Speter  if (err)
3513251881Speter    {
3514251881Speter      svn_error_clear(svn_stream_close(mimetypes_stream));
3515251881Speter      return err;
3516251881Speter    }
3517251881Speter
3518251881Speter  /* Close the stream (which closes the underlying file, too). */
3519251881Speter  SVN_ERR(svn_stream_close(mimetypes_stream));
3520251881Speter
3521251881Speter  *type_map = types;
3522251881Speter  return SVN_NO_ERROR;
3523251881Speter}
3524251881Speter
3525251881Speter
3526251881Spetersvn_error_t *
3527251881Spetersvn_io_detect_mimetype2(const char **mimetype,
3528251881Speter                        const char *file,
3529251881Speter                        apr_hash_t *mimetype_map,
3530251881Speter                        apr_pool_t *pool)
3531251881Speter{
3532251881Speter  static const char * const generic_binary = "application/octet-stream";
3533251881Speter
3534251881Speter  svn_node_kind_t kind;
3535251881Speter  apr_file_t *fh;
3536251881Speter  svn_error_t *err;
3537251881Speter  unsigned char block[1024];
3538251881Speter  apr_size_t amt_read = sizeof(block);
3539251881Speter
3540251881Speter  /* Default return value is NULL. */
3541251881Speter  *mimetype = NULL;
3542251881Speter
3543251881Speter  /* If there is a mimetype_map provided, we'll first try to look up
3544251881Speter     our file's extension in the map.  Failing that, we'll run the
3545251881Speter     heuristic. */
3546251881Speter  if (mimetype_map)
3547251881Speter    {
3548251881Speter      const char *type_from_map;
3549251881Speter      char *path_ext; /* Can point to physical const memory but only when
3550251881Speter                         svn_path_splitext sets it to "". */
3551251881Speter      svn_path_splitext(NULL, (const char **)&path_ext, file, pool);
3552251881Speter      fileext_tolower(path_ext);
3553251881Speter      if ((type_from_map = svn_hash_gets(mimetype_map, path_ext)))
3554251881Speter        {
3555251881Speter          *mimetype = type_from_map;
3556251881Speter          return SVN_NO_ERROR;
3557251881Speter        }
3558251881Speter    }
3559251881Speter
3560251881Speter  /* See if this file even exists, and make sure it really is a file. */
3561251881Speter  SVN_ERR(svn_io_check_path(file, &kind, pool));
3562251881Speter  if (kind != svn_node_file)
3563251881Speter    return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
3564251881Speter                             _("Can't detect MIME type of non-file '%s'"),
3565251881Speter                             svn_dirent_local_style(file, pool));
3566251881Speter
3567251881Speter  SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool));
3568251881Speter
3569251881Speter  /* Read a block of data from FILE. */
3570251881Speter  err = svn_io_file_read(fh, block, &amt_read, pool);
3571251881Speter  if (err && ! APR_STATUS_IS_EOF(err->apr_err))
3572251881Speter    return err;
3573251881Speter  svn_error_clear(err);
3574251881Speter
3575251881Speter  /* Now close the file.  No use keeping it open any more.  */
3576251881Speter  SVN_ERR(svn_io_file_close(fh, pool));
3577251881Speter
3578251881Speter  if (svn_io_is_binary_data(block, amt_read))
3579251881Speter    *mimetype = generic_binary;
3580251881Speter
3581251881Speter  return SVN_NO_ERROR;
3582251881Speter}
3583251881Speter
3584251881Speter
3585251881Spetersvn_boolean_t
3586251881Spetersvn_io_is_binary_data(const void *data, apr_size_t len)
3587251881Speter{
3588251881Speter  const unsigned char *buf = data;
3589251881Speter
3590251881Speter  if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF)
3591251881Speter    {
3592251881Speter      /* This is an empty UTF-8 file which only contains the UTF-8 BOM.
3593251881Speter       * Treat it as plain text. */
3594251881Speter      return FALSE;
3595251881Speter    }
3596251881Speter
3597251881Speter  /* Right now, this function is going to be really stupid.  It's
3598251881Speter     going to examine the block of data, and make sure that 15%
3599251881Speter     of the bytes are such that their value is in the ranges 0x07-0x0D
3600251881Speter     or 0x20-0x7F, and that none of those bytes is 0x00.  If those
3601251881Speter     criteria are not met, we're calling it binary.
3602251881Speter
3603251881Speter     NOTE:  Originally, I intended to target 85% of the bytes being in
3604251881Speter     the specified ranges, but I flubbed the condition.  At any rate,
3605251881Speter     folks aren't complaining, so I'm not sure that it's worth
3606251881Speter     adjusting this retroactively now.  --cmpilato  */
3607251881Speter  if (len > 0)
3608251881Speter    {
3609251881Speter      apr_size_t i;
3610251881Speter      apr_size_t binary_count = 0;
3611251881Speter
3612251881Speter      /* Run through the data we've read, counting the 'binary-ish'
3613251881Speter         bytes.  HINT: If we see a 0x00 byte, we'll set our count to its
3614251881Speter         max and stop reading the file. */
3615251881Speter      for (i = 0; i < len; i++)
3616251881Speter        {
3617251881Speter          if (buf[i] == 0)
3618251881Speter            {
3619251881Speter              binary_count = len;
3620251881Speter              break;
3621251881Speter            }
3622251881Speter          if ((buf[i] < 0x07)
3623251881Speter              || ((buf[i] > 0x0D) && (buf[i] < 0x20))
3624251881Speter              || (buf[i] > 0x7F))
3625251881Speter            {
3626251881Speter              binary_count++;
3627251881Speter            }
3628251881Speter        }
3629251881Speter
3630251881Speter      return (((binary_count * 1000) / len) > 850);
3631251881Speter    }
3632251881Speter
3633251881Speter  return FALSE;
3634251881Speter}
3635251881Speter
3636251881Speter
3637251881Spetersvn_error_t *
3638251881Spetersvn_io_detect_mimetype(const char **mimetype,
3639251881Speter                       const char *file,
3640251881Speter                       apr_pool_t *pool)
3641251881Speter{
3642251881Speter  return svn_io_detect_mimetype2(mimetype, file, NULL, pool);
3643251881Speter}
3644251881Speter
3645251881Speter
3646251881Spetersvn_error_t *
3647251881Spetersvn_io_file_open(apr_file_t **new_file, const char *fname,
3648251881Speter                 apr_int32_t flag, apr_fileperms_t perm,
3649251881Speter                 apr_pool_t *pool)
3650251881Speter{
3651251881Speter  const char *fname_apr;
3652251881Speter  apr_status_t status;
3653251881Speter
3654251881Speter  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
3655251881Speter  status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE,
3656251881Speter                     pool);
3657251881Speter
3658251881Speter  if (status)
3659251881Speter    return svn_error_wrap_apr(status, _("Can't open file '%s'"),
3660251881Speter                              svn_dirent_local_style(fname, pool));
3661251881Speter  else
3662251881Speter    return SVN_NO_ERROR;
3663251881Speter}
3664251881Speter
3665251881Speter
3666251881Speterstatic APR_INLINE svn_error_t *
3667251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
3668251881Speter                           const char *msg, const char *msg_no_name,
3669251881Speter                           apr_pool_t *pool)
3670251881Speter{
3671251881Speter  const char *name;
3672251881Speter  svn_error_t *err;
3673251881Speter
3674251881Speter  if (! status)
3675251881Speter    return SVN_NO_ERROR;
3676251881Speter
3677251881Speter  err = svn_io_file_name_get(&name, file, pool);
3678251881Speter  if (err)
3679251881Speter    name = NULL;
3680251881Speter  svn_error_clear(err);
3681251881Speter
3682251881Speter  /* ### Issue #3014: Return a specific error for broken pipes,
3683251881Speter   * ### with a single element in the error chain. */
3684262253Speter  if (SVN__APR_STATUS_IS_EPIPE(status))
3685251881Speter    return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
3686251881Speter
3687251881Speter  if (name)
3688251881Speter    return svn_error_wrap_apr(status, _(msg),
3689251881Speter                              try_utf8_from_internal_style(name, pool));
3690251881Speter  else
3691251881Speter    return svn_error_wrap_apr(status, "%s", _(msg_no_name));
3692251881Speter}
3693251881Speter
3694251881Speter
3695251881Spetersvn_error_t *
3696251881Spetersvn_io_file_close(apr_file_t *file, apr_pool_t *pool)
3697251881Speter{
3698251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_close(file),
3699251881Speter                                    N_("Can't close file '%s'"),
3700251881Speter                                    N_("Can't close stream"),
3701251881Speter                                    pool);
3702251881Speter}
3703251881Speter
3704251881Speter
3705251881Spetersvn_error_t *
3706251881Spetersvn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool)
3707251881Speter{
3708251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file),
3709251881Speter                                    N_("Can't read file '%s'"),
3710251881Speter                                    N_("Can't read stream"),
3711251881Speter                                    pool);
3712251881Speter}
3713251881Speter
3714251881Speter
3715251881Spetersvn_error_t *
3716251881Spetersvn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool)
3717251881Speter{
3718251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file),
3719251881Speter                                    N_("Can't write file '%s'"),
3720251881Speter                                    N_("Can't write stream"),
3721251881Speter                                    pool);
3722251881Speter}
3723251881Speter
3724251881Speter
3725251881Spetersvn_error_t *
3726251881Spetersvn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted,
3727251881Speter                     apr_file_t *file, apr_pool_t *pool)
3728251881Speter{
3729251881Speter  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
3730251881Speter  wanted &= ~SVN__APR_FINFO_MASK_OUT;
3731251881Speter
3732251881Speter  return do_io_file_wrapper_cleanup(
3733251881Speter             file, apr_file_info_get(finfo, wanted, file),
3734251881Speter             N_("Can't get attribute information from file '%s'"),
3735251881Speter             N_("Can't get attribute information from stream"),
3736251881Speter             pool);
3737251881Speter}
3738251881Speter
3739251881Speter
3740251881Spetersvn_error_t *
3741251881Spetersvn_io_file_read(apr_file_t *file, void *buf,
3742251881Speter                 apr_size_t *nbytes, apr_pool_t *pool)
3743251881Speter{
3744251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes),
3745251881Speter                                    N_("Can't read file '%s'"),
3746251881Speter                                    N_("Can't read stream"),
3747251881Speter                                    pool);
3748251881Speter}
3749251881Speter
3750251881Speter
3751251881Spetersvn_error_t *
3752251881Spetersvn_io_file_read_full2(apr_file_t *file, void *buf,
3753251881Speter                       apr_size_t nbytes, apr_size_t *bytes_read,
3754251881Speter                       svn_boolean_t *hit_eof,
3755251881Speter                       apr_pool_t *pool)
3756251881Speter{
3757251881Speter  apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read);
3758251881Speter  if (hit_eof)
3759251881Speter    {
3760251881Speter      if (APR_STATUS_IS_EOF(status))
3761251881Speter        {
3762251881Speter          *hit_eof = TRUE;
3763251881Speter          return SVN_NO_ERROR;
3764251881Speter        }
3765251881Speter      else
3766251881Speter        *hit_eof = FALSE;
3767251881Speter    }
3768251881Speter
3769251881Speter  return do_io_file_wrapper_cleanup(file, status,
3770251881Speter                                    N_("Can't read file '%s'"),
3771251881Speter                                    N_("Can't read stream"),
3772251881Speter                                    pool);
3773251881Speter}
3774251881Speter
3775251881Speter
3776251881Spetersvn_error_t *
3777251881Spetersvn_io_file_seek(apr_file_t *file, apr_seek_where_t where,
3778251881Speter                 apr_off_t *offset, apr_pool_t *pool)
3779251881Speter{
3780251881Speter  return do_io_file_wrapper_cleanup(
3781251881Speter             file, apr_file_seek(file, where, offset),
3782251881Speter             N_("Can't set position pointer in file '%s'"),
3783251881Speter             N_("Can't set position pointer in stream"),
3784251881Speter             pool);
3785251881Speter}
3786251881Speter
3787299742Sdimsvn_error_t *
3788299742Sdimsvn_io_file_aligned_seek(apr_file_t *file,
3789299742Sdim                         apr_off_t block_size,
3790299742Sdim                         apr_off_t *buffer_start,
3791299742Sdim                         apr_off_t offset,
3792299742Sdim                         apr_pool_t *scratch_pool)
3793299742Sdim{
3794299742Sdim  const apr_size_t apr_default_buffer_size = 4096;
3795299742Sdim  apr_size_t file_buffer_size = apr_default_buffer_size;
3796299742Sdim  apr_off_t desired_offset = 0;
3797299742Sdim  apr_off_t current = 0;
3798299742Sdim  apr_off_t aligned_offset = 0;
3799299742Sdim  svn_boolean_t fill_buffer = FALSE;
3800251881Speter
3801299742Sdim  /* paranoia check: huge blocks on 32 bit machines may cause overflows */
3802299742Sdim  SVN_ERR_ASSERT(block_size == (apr_size_t)block_size);
3803299742Sdim
3804299742Sdim  /* default for invalid block sizes */
3805299742Sdim  if (block_size == 0)
3806299742Sdim    block_size = apr_default_buffer_size;
3807299742Sdim
3808299742Sdim  file_buffer_size = apr_file_buffer_size_get(file);
3809299742Sdim
3810299742Sdim  /* don't try to set a buffer size for non-buffered files! */
3811299742Sdim  if (file_buffer_size == 0)
3812299742Sdim    {
3813299742Sdim      aligned_offset = offset;
3814299742Sdim    }
3815299742Sdim  else if (file_buffer_size != (apr_size_t)block_size)
3816299742Sdim    {
3817299742Sdim      /* FILE has the wrong buffer size. correct it */
3818299742Sdim      char *buffer;
3819299742Sdim      file_buffer_size = (apr_size_t)block_size;
3820299742Sdim      buffer = apr_palloc(apr_file_pool_get(file), file_buffer_size);
3821299742Sdim      apr_file_buffer_set(file, buffer, file_buffer_size);
3822299742Sdim
3823299742Sdim      /* seek to the start of the block and cause APR to read 1 block */
3824299742Sdim      aligned_offset = offset - (offset % block_size);
3825299742Sdim      fill_buffer = TRUE;
3826299742Sdim    }
3827299742Sdim  else
3828299742Sdim    {
3829299742Sdim      aligned_offset = offset - (offset % file_buffer_size);
3830299742Sdim
3831299742Sdim      /* We have no way to determine the block start of an APR file.
3832299742Sdim         Furthermore, we don't want to throw away the current buffer
3833299742Sdim         contents.  Thus, we re-align the buffer only if the CURRENT
3834299742Sdim         offset definitely lies outside the desired, aligned buffer.
3835299742Sdim         This covers the typical case of linear reads getting very
3836299742Sdim         close to OFFSET but reading the previous / following block.
3837299742Sdim
3838299742Sdim         Note that ALIGNED_OFFSET may still be within the current
3839299742Sdim         buffer and no I/O will actually happen in the FILL_BUFFER
3840299742Sdim         section below.
3841299742Sdim       */
3842299742Sdim      SVN_ERR(svn_io_file_seek(file, APR_CUR, &current, scratch_pool));
3843299742Sdim      fill_buffer = aligned_offset + file_buffer_size <= current
3844299742Sdim                 || current <= aligned_offset;
3845299742Sdim    }
3846299742Sdim
3847299742Sdim  if (fill_buffer)
3848299742Sdim    {
3849299742Sdim      char dummy;
3850299742Sdim      apr_status_t status;
3851299742Sdim
3852299742Sdim      /* seek to the start of the block and cause APR to read 1 block */
3853299742Sdim      SVN_ERR(svn_io_file_seek(file, APR_SET, &aligned_offset,
3854299742Sdim                               scratch_pool));
3855299742Sdim      status = apr_file_getc(&dummy, file);
3856299742Sdim
3857299742Sdim      /* read may fail if we seek to or behind EOF.  That's ok then. */
3858299742Sdim      if (status != APR_SUCCESS && !APR_STATUS_IS_EOF(status))
3859299742Sdim        return do_io_file_wrapper_cleanup(file, status,
3860299742Sdim                                          N_("Can't read file '%s'"),
3861299742Sdim                                          N_("Can't read stream"),
3862299742Sdim                                          scratch_pool);
3863299742Sdim    }
3864299742Sdim
3865299742Sdim  /* finally, seek to the OFFSET the caller wants */
3866299742Sdim  desired_offset = offset;
3867299742Sdim  SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
3868299742Sdim  if (desired_offset != offset)
3869299742Sdim    return do_io_file_wrapper_cleanup(file, APR_EOF,
3870299742Sdim                                      N_("Can't seek in file '%s'"),
3871299742Sdim                                      N_("Can't seek in stream"),
3872299742Sdim                                      scratch_pool);
3873299742Sdim
3874299742Sdim  /* return the buffer start that we (probably) enforced */
3875299742Sdim  if (buffer_start)
3876299742Sdim    *buffer_start = aligned_offset;
3877299742Sdim
3878299742Sdim  return SVN_NO_ERROR;
3879299742Sdim}
3880299742Sdim
3881299742Sdim
3882251881Spetersvn_error_t *
3883251881Spetersvn_io_file_write(apr_file_t *file, const void *buf,
3884251881Speter                  apr_size_t *nbytes, apr_pool_t *pool)
3885251881Speter{
3886251881Speter  return svn_error_trace(do_io_file_wrapper_cleanup(
3887251881Speter     file, apr_file_write(file, buf, nbytes),
3888251881Speter     N_("Can't write to file '%s'"),
3889251881Speter     N_("Can't write to stream"),
3890251881Speter     pool));
3891251881Speter}
3892251881Speter
3893299742Sdimsvn_error_t *
3894299742Sdimsvn_io_file_flush(apr_file_t *file,
3895299742Sdim                  apr_pool_t *scratch_pool)
3896299742Sdim{
3897299742Sdim  return svn_error_trace(do_io_file_wrapper_cleanup(
3898299742Sdim     file, apr_file_flush(file),
3899299742Sdim     N_("Can't flush file '%s'"),
3900299742Sdim     N_("Can't flush stream"),
3901299742Sdim     scratch_pool));
3902299742Sdim}
3903251881Speter
3904251881Spetersvn_error_t *
3905251881Spetersvn_io_file_write_full(apr_file_t *file, const void *buf,
3906251881Speter                       apr_size_t nbytes, apr_size_t *bytes_written,
3907251881Speter                       apr_pool_t *pool)
3908251881Speter{
3909251881Speter  /* We cannot simply call apr_file_write_full on Win32 as it may fail
3910251881Speter     for larger values of NBYTES. In that case, we have to emulate the
3911251881Speter     "_full" part here. Thus, always call apr_file_write directly on
3912251881Speter     Win32 as this minimizes overhead for small data buffers. */
3913251881Speter#ifdef WIN32
3914251881Speter#define MAXBUFSIZE 30*1024
3915251881Speter  apr_size_t bw = nbytes;
3916251881Speter  apr_size_t to_write = nbytes;
3917251881Speter
3918251881Speter  /* try a simple "write everything at once" first */
3919251881Speter  apr_status_t rv = apr_file_write(file, buf, &bw);
3920251881Speter  buf = (char *)buf + bw;
3921251881Speter  to_write -= bw;
3922251881Speter
3923251881Speter  /* if the OS cannot handle that, use smaller chunks */
3924251881Speter  if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY)
3925251881Speter      && nbytes > MAXBUFSIZE)
3926251881Speter    {
3927251881Speter      do {
3928251881Speter        bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write;
3929251881Speter        rv = apr_file_write(file, buf, &bw);
3930251881Speter        buf = (char *)buf + bw;
3931251881Speter        to_write -= bw;
3932251881Speter      } while (rv == APR_SUCCESS && to_write > 0);
3933251881Speter    }
3934251881Speter
3935251881Speter  /* bytes_written may actually be NULL */
3936251881Speter  if (bytes_written)
3937251881Speter    *bytes_written = nbytes - to_write;
3938251881Speter#undef MAXBUFSIZE
3939251881Speter#else
3940251881Speter  apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written);
3941251881Speter#endif
3942251881Speter
3943251881Speter  return svn_error_trace(do_io_file_wrapper_cleanup(
3944251881Speter     file, rv,
3945251881Speter     N_("Can't write to file '%s'"),
3946251881Speter     N_("Can't write to stream"),
3947251881Speter     pool));
3948251881Speter}
3949251881Speter
3950251881Speter
3951251881Spetersvn_error_t *
3952251881Spetersvn_io_write_unique(const char **tmp_path,
3953251881Speter                    const char *dirpath,
3954251881Speter                    const void *buf,
3955251881Speter                    apr_size_t nbytes,
3956251881Speter                    svn_io_file_del_t delete_when,
3957251881Speter                    apr_pool_t *pool)
3958251881Speter{
3959251881Speter  apr_file_t *new_file;
3960251881Speter  svn_error_t *err;
3961251881Speter
3962251881Speter  SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath,
3963251881Speter                                   delete_when, pool, pool));
3964251881Speter
3965251881Speter  err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool);
3966251881Speter
3967251881Speter  if (!err)
3968299742Sdim    {
3969299742Sdim      /* svn_io_file_flush_to_disk() can be very expensive, so use the
3970299742Sdim         cheaper standard flush if the file is created as temporary file
3971299742Sdim         anyway */
3972299742Sdim      if (delete_when == svn_io_file_del_none)
3973299742Sdim        err = svn_io_file_flush_to_disk(new_file, pool);
3974299742Sdim      else
3975299742Sdim        err = svn_io_file_flush(new_file, pool);
3976299742Sdim    }
3977251881Speter
3978251881Speter  return svn_error_trace(
3979251881Speter                  svn_error_compose_create(err,
3980251881Speter                                           svn_io_file_close(new_file, pool)));
3981251881Speter}
3982251881Speter
3983299742Sdimsvn_error_t *
3984299742Sdimsvn_io_write_atomic(const char *final_path,
3985299742Sdim                    const void *buf,
3986299742Sdim                    apr_size_t nbytes,
3987299742Sdim                    const char *copy_perms_path,
3988299742Sdim                    apr_pool_t *scratch_pool)
3989299742Sdim{
3990299742Sdim  apr_file_t *tmp_file;
3991299742Sdim  const char *tmp_path;
3992299742Sdim  svn_error_t *err;
3993299742Sdim  const char *dirname = svn_dirent_dirname(final_path, scratch_pool);
3994251881Speter
3995299742Sdim  SVN_ERR(svn_io_open_unique_file3(&tmp_file, &tmp_path, dirname,
3996299742Sdim                                   svn_io_file_del_none,
3997299742Sdim                                   scratch_pool, scratch_pool));
3998299742Sdim
3999299742Sdim  err = svn_io_file_write_full(tmp_file, buf, nbytes, NULL, scratch_pool);
4000299742Sdim
4001299742Sdim  if (!err)
4002299742Sdim    err = svn_io_file_flush_to_disk(tmp_file, scratch_pool);
4003299742Sdim
4004299742Sdim  err = svn_error_compose_create(err,
4005299742Sdim                                 svn_io_file_close(tmp_file, scratch_pool));
4006299742Sdim
4007299742Sdim  if (!err && copy_perms_path)
4008299742Sdim    err = svn_io_copy_perms(copy_perms_path, tmp_path, scratch_pool);
4009299742Sdim
4010299742Sdim  if (!err)
4011299742Sdim    err = svn_io_file_rename(tmp_path, final_path, scratch_pool);
4012299742Sdim
4013299742Sdim  if (err)
4014299742Sdim    {
4015299742Sdim      err = svn_error_compose_create(err,
4016299742Sdim                                     svn_io_remove_file2(tmp_path, TRUE,
4017299742Sdim                                                         scratch_pool));
4018299742Sdim
4019299742Sdim      return svn_error_createf(err->apr_err, err,
4020299742Sdim                               _("Can't write '%s' atomically"),
4021299742Sdim                               svn_dirent_local_style(final_path,
4022299742Sdim                                                      scratch_pool));
4023299742Sdim    }
4024299742Sdim
4025299742Sdim#ifdef __linux__
4026299742Sdim  {
4027299742Sdim    /* Linux has the unusual feature that fsync() on a file is not
4028299742Sdim       enough to ensure that a file's directory entries have been
4029299742Sdim       flushed to disk; you have to fsync the directory as well.
4030299742Sdim       On other operating systems, we'd only be asking for trouble
4031299742Sdim       by trying to open and fsync a directory. */
4032299742Sdim    apr_file_t *file;
4033299742Sdim
4034299742Sdim    SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
4035299742Sdim                             scratch_pool));
4036299742Sdim    SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
4037299742Sdim    SVN_ERR(svn_io_file_close(file, scratch_pool));
4038299742Sdim  }
4039299742Sdim#endif
4040299742Sdim
4041299742Sdim  return SVN_NO_ERROR;
4042299742Sdim}
4043299742Sdim
4044251881Spetersvn_error_t *
4045251881Spetersvn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool)
4046251881Speter{
4047251881Speter  /* This is a work-around. APR would flush the write buffer
4048251881Speter     _after_ truncating the file causing now invalid buffered
4049251881Speter     data to be written behind OFFSET. */
4050251881Speter  SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
4051251881Speter                                     N_("Can't flush file '%s'"),
4052251881Speter                                     N_("Can't flush stream"),
4053251881Speter                                     pool));
4054251881Speter
4055251881Speter  return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset),
4056251881Speter                                    N_("Can't truncate file '%s'"),
4057251881Speter                                    N_("Can't truncate stream"),
4058251881Speter                                    pool);
4059251881Speter}
4060251881Speter
4061251881Speter
4062251881Spetersvn_error_t *
4063251881Spetersvn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit,
4064251881Speter                        apr_pool_t *pool)
4065251881Speter{
4066251881Speter  /* variables */
4067251881Speter  apr_size_t total_read = 0;
4068251881Speter  svn_boolean_t eof = FALSE;
4069251881Speter  const char *name;
4070251881Speter  svn_error_t *err;
4071251881Speter  apr_size_t buf_size = *limit;
4072251881Speter
4073251881Speter  while (buf_size > 0)
4074251881Speter    {
4075251881Speter      /* read a fair chunk of data at once. But don't get too ambitious
4076251881Speter       * as that would result in too much waste. Also make sure we can
4077251881Speter       * put a NUL after the last byte read.
4078251881Speter       */
4079251881Speter      apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128;
4080251881Speter      apr_size_t bytes_read = 0;
4081251881Speter      char *eol;
4082251881Speter
4083253734Speter      if (to_read == 0)
4084253734Speter        break;
4085253734Speter
4086251881Speter      /* read data block (or just a part of it) */
4087251881Speter      SVN_ERR(svn_io_file_read_full2(file, buf, to_read,
4088251881Speter                                     &bytes_read, &eof, pool));
4089251881Speter
4090251881Speter      /* look or a newline char */
4091251881Speter      buf[bytes_read] = 0;
4092251881Speter      eol = strchr(buf, '\n');
4093251881Speter      if (eol)
4094251881Speter        {
4095251881Speter          apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read;
4096251881Speter
4097251881Speter          *eol = 0;
4098251881Speter          *limit = total_read + (eol - buf);
4099251881Speter
4100251881Speter          /* correct the file pointer:
4101251881Speter           * appear as though we just had read the newline char
4102251881Speter           */
4103251881Speter          SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
4104251881Speter
4105251881Speter          return SVN_NO_ERROR;
4106251881Speter        }
4107251881Speter      else if (eof)
4108251881Speter        {
4109251881Speter          /* no EOL found but we hit the end of the file.
4110251881Speter           * Generate a nice EOF error object and return it.
4111251881Speter           */
4112251881Speter          char dummy;
4113251881Speter          SVN_ERR(svn_io_file_getc(&dummy, file, pool));
4114251881Speter        }
4115251881Speter
4116251881Speter      /* next data chunk */
4117251881Speter      buf_size -= bytes_read;
4118251881Speter      buf += bytes_read;
4119251881Speter      total_read += bytes_read;
4120251881Speter    }
4121251881Speter
4122251881Speter  /* buffer limit has been exceeded without finding the EOL */
4123251881Speter  err = svn_io_file_name_get(&name, file, pool);
4124251881Speter  if (err)
4125251881Speter    name = NULL;
4126251881Speter  svn_error_clear(err);
4127251881Speter
4128251881Speter  if (name)
4129251881Speter    return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
4130251881Speter                             _("Can't read length line in file '%s'"),
4131251881Speter                             svn_dirent_local_style(name, pool));
4132251881Speter  else
4133251881Speter    return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
4134251881Speter                            _("Can't read length line in stream"));
4135251881Speter}
4136251881Speter
4137251881Speter
4138251881Spetersvn_error_t *
4139251881Spetersvn_io_stat(apr_finfo_t *finfo, const char *fname,
4140251881Speter            apr_int32_t wanted, apr_pool_t *pool)
4141251881Speter{
4142251881Speter  apr_status_t status;
4143251881Speter  const char *fname_apr;
4144251881Speter
4145251881Speter  /* APR doesn't like "" directories */
4146251881Speter  if (fname[0] == '\0')
4147251881Speter    fname = ".";
4148251881Speter
4149251881Speter  SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
4150251881Speter
4151251881Speter  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
4152251881Speter  wanted &= ~SVN__APR_FINFO_MASK_OUT;
4153251881Speter
4154251881Speter  status = apr_stat(finfo, fname_apr, wanted, pool);
4155251881Speter  if (status)
4156251881Speter    return svn_error_wrap_apr(status, _("Can't stat '%s'"),
4157251881Speter                              svn_dirent_local_style(fname, pool));
4158251881Speter
4159251881Speter  return SVN_NO_ERROR;
4160251881Speter}
4161251881Speter
4162251881Speter
4163251881Spetersvn_error_t *
4164251881Spetersvn_io_file_rename(const char *from_path, const char *to_path,
4165251881Speter                   apr_pool_t *pool)
4166251881Speter{
4167251881Speter  apr_status_t status = APR_SUCCESS;
4168251881Speter  const char *from_path_apr, *to_path_apr;
4169251881Speter
4170251881Speter  SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool));
4171251881Speter  SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool));
4172251881Speter
4173251881Speter  status = apr_file_rename(from_path_apr, to_path_apr, pool);
4174251881Speter
4175251881Speter#if defined(WIN32) || defined(__OS2__)
4176251881Speter  /* If the target file is read only NTFS reports EACCESS and
4177251881Speter     FAT/FAT32 reports EEXIST */
4178251881Speter  if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
4179251881Speter    {
4180251881Speter      /* Set the destination file writable because Windows will not
4181251881Speter         allow us to rename when to_path is read-only, but will
4182251881Speter         allow renaming when from_path is read only. */
4183251881Speter      SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
4184251881Speter
4185251881Speter      status = apr_file_rename(from_path_apr, to_path_apr, pool);
4186251881Speter    }
4187251881Speter  WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool));
4188251881Speter#endif /* WIN32 || __OS2__ */
4189251881Speter
4190251881Speter  if (status)
4191251881Speter    return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"),
4192251881Speter                              svn_dirent_local_style(from_path, pool),
4193251881Speter                              svn_dirent_local_style(to_path, pool));
4194251881Speter
4195251881Speter  return SVN_NO_ERROR;
4196251881Speter}
4197251881Speter
4198251881Speter
4199251881Spetersvn_error_t *
4200251881Spetersvn_io_file_move(const char *from_path, const char *to_path,
4201251881Speter                 apr_pool_t *pool)
4202251881Speter{
4203251881Speter  svn_error_t *err = svn_io_file_rename(from_path, to_path, pool);
4204251881Speter
4205251881Speter  if (err && APR_STATUS_IS_EXDEV(err->apr_err))
4206251881Speter    {
4207251881Speter      const char *tmp_to_path;
4208251881Speter
4209251881Speter      svn_error_clear(err);
4210251881Speter
4211251881Speter      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path,
4212251881Speter                                       svn_dirent_dirname(to_path, pool),
4213251881Speter                                       svn_io_file_del_none,
4214251881Speter                                       pool, pool));
4215251881Speter
4216251881Speter      err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool);
4217251881Speter      if (err)
4218251881Speter        goto failed_tmp;
4219251881Speter
4220251881Speter      err = svn_io_file_rename(tmp_to_path, to_path, pool);
4221251881Speter      if (err)
4222251881Speter        goto failed_tmp;
4223251881Speter
4224251881Speter      err = svn_io_remove_file2(from_path, FALSE, pool);
4225251881Speter      if (! err)
4226251881Speter        return SVN_NO_ERROR;
4227251881Speter
4228251881Speter      svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool));
4229251881Speter
4230251881Speter      return err;
4231251881Speter
4232251881Speter    failed_tmp:
4233251881Speter      svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool));
4234251881Speter    }
4235251881Speter
4236251881Speter  return err;
4237251881Speter}
4238251881Speter
4239251881Speter/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden.
4240251881Speter   HIDDEN determines if the hidden attribute
4241251881Speter   should be set on the newly created directory. */
4242251881Speterstatic svn_error_t *
4243251881Speterdir_make(const char *path, apr_fileperms_t perm,
4244251881Speter         svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool)
4245251881Speter{
4246251881Speter  apr_status_t status;
4247251881Speter  const char *path_apr;
4248251881Speter
4249251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
4250251881Speter
4251251881Speter  /* APR doesn't like "" directories */
4252251881Speter  if (path_apr[0] == '\0')
4253251881Speter    path_apr = ".";
4254251881Speter
4255251881Speter#if (APR_OS_DEFAULT & APR_WSTICKY)
4256251881Speter  /* The APR shipped with httpd 2.0.50 contains a bug where
4257251881Speter     APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits.
4258251881Speter     There is a special case for file creation, but not directory
4259251881Speter     creation, so directories wind up getting created with the sticky
4260251881Speter     bit set.  (There is no such thing as a setuid directory, and the
4261251881Speter     setgid bit is apparently ignored at mkdir() time.)  If we detect
4262251881Speter     this problem, work around it by unsetting those bits if we are
4263251881Speter     passed APR_OS_DEFAULT. */
4264251881Speter  if (perm == APR_OS_DEFAULT)
4265251881Speter    perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY);
4266251881Speter#endif
4267251881Speter
4268251881Speter  status = apr_dir_make(path_apr, perm, pool);
4269251881Speter
4270299742Sdim#ifdef WIN32
4271299742Sdim  /* Don't retry on ERROR_ACCESS_DENIED, as that typically signals a
4272299742Sdim     permanent error */
4273299742Sdim  if (status == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION))
4274299742Sdim    WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
4275299742Sdim#endif
4276299742Sdim
4277251881Speter  if (status)
4278251881Speter    return svn_error_wrap_apr(status, _("Can't create directory '%s'"),
4279251881Speter                              svn_dirent_local_style(path, pool));
4280251881Speter
4281251881Speter#ifdef APR_FILE_ATTR_HIDDEN
4282251881Speter  if (hidden)
4283251881Speter    {
4284251881Speter#ifndef WIN32
4285251881Speter      status = apr_file_attrs_set(path_apr,
4286251881Speter                                  APR_FILE_ATTR_HIDDEN,
4287251881Speter                                  APR_FILE_ATTR_HIDDEN,
4288251881Speter                                  pool);
4289299742Sdim      if (status)
4290299742Sdim        return svn_error_wrap_apr(status, _("Can't hide directory '%s'"),
4291299742Sdim                                  svn_dirent_local_style(path, pool));
4292251881Speter#else
4293251881Speter    /* on Windows, use our wrapper so we can also set the
4294251881Speter       FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */
4295299742Sdim      svn_error_t *err =
4296299742Sdim          io_win_file_attrs_set(path_apr,
4297299742Sdim                                FILE_ATTRIBUTE_HIDDEN |
4298299742Sdim                                FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
4299299742Sdim                                FILE_ATTRIBUTE_HIDDEN |
4300299742Sdim                                FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
4301299742Sdim                                pool);
4302299742Sdim      if (err)
4303299742Sdim        return svn_error_createf(err->apr_err, err,
4304299742Sdim                                 _("Can't hide directory '%s'"),
4305299742Sdim                                 svn_dirent_local_style(path, pool));
4306299742Sdim#endif /* WIN32 */
4307251881Speter    }
4308299742Sdim#endif /* APR_FILE_ATTR_HIDDEN */
4309251881Speter
4310251881Speter/* Windows does not implement sgid. Skip here because retrieving
4311251881Speter   the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented
4312251881Speter   to be 'incredibly expensive'. */
4313251881Speter#ifndef WIN32
4314251881Speter  if (sgid)
4315251881Speter    {
4316251881Speter      apr_finfo_t finfo;
4317251881Speter
4318251881Speter      /* Per our contract, don't do error-checking.  Some filesystems
4319251881Speter       * don't support the sgid bit, and that's okay. */
4320251881Speter      status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool);
4321251881Speter
4322251881Speter      if (!status)
4323251881Speter        apr_file_perms_set(path_apr, finfo.protection | APR_GSETID);
4324251881Speter    }
4325251881Speter#endif
4326251881Speter
4327251881Speter  return SVN_NO_ERROR;
4328251881Speter}
4329251881Speter
4330251881Spetersvn_error_t *
4331251881Spetersvn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool)
4332251881Speter{
4333251881Speter  return dir_make(path, perm, FALSE, FALSE, pool);
4334251881Speter}
4335251881Speter
4336251881Spetersvn_error_t *
4337251881Spetersvn_io_dir_make_hidden(const char *path, apr_fileperms_t perm,
4338251881Speter                       apr_pool_t *pool)
4339251881Speter{
4340251881Speter  return dir_make(path, perm, TRUE, FALSE, pool);
4341251881Speter}
4342251881Speter
4343251881Spetersvn_error_t *
4344251881Spetersvn_io_dir_make_sgid(const char *path, apr_fileperms_t perm,
4345251881Speter                     apr_pool_t *pool)
4346251881Speter{
4347251881Speter  return dir_make(path, perm, FALSE, TRUE, pool);
4348251881Speter}
4349251881Speter
4350251881Speter
4351251881Spetersvn_error_t *
4352251881Spetersvn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool)
4353251881Speter{
4354251881Speter  apr_status_t status;
4355251881Speter  const char *dirname_apr;
4356251881Speter
4357251881Speter  /* APR doesn't like "" directories */
4358251881Speter  if (dirname[0] == '\0')
4359251881Speter    dirname = ".";
4360251881Speter
4361251881Speter  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
4362251881Speter
4363251881Speter  status = apr_dir_open(new_dir, dirname_apr, pool);
4364251881Speter  if (status)
4365251881Speter    return svn_error_wrap_apr(status, _("Can't open directory '%s'"),
4366251881Speter                              svn_dirent_local_style(dirname, pool));
4367251881Speter
4368251881Speter  return SVN_NO_ERROR;
4369251881Speter}
4370251881Speter
4371251881Spetersvn_error_t *
4372251881Spetersvn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool)
4373251881Speter{
4374251881Speter  apr_status_t status;
4375251881Speter  const char *dirname_apr;
4376251881Speter
4377251881Speter  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
4378251881Speter
4379251881Speter  status = apr_dir_remove(dirname_apr, pool);
4380251881Speter
4381251881Speter#ifdef WIN32
4382251881Speter  {
4383251881Speter    svn_boolean_t retry = TRUE;
4384251881Speter
4385251881Speter    if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY)
4386251881Speter      {
4387251881Speter        apr_status_t empty_status = dir_is_empty(dirname_apr, pool);
4388251881Speter
4389251881Speter        if (APR_STATUS_IS_ENOTEMPTY(empty_status))
4390251881Speter          retry = FALSE;
4391251881Speter      }
4392251881Speter
4393251881Speter    if (retry)
4394251881Speter      {
4395251881Speter        WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool));
4396251881Speter      }
4397251881Speter  }
4398251881Speter#endif
4399251881Speter  if (status)
4400251881Speter    return svn_error_wrap_apr(status, _("Can't remove directory '%s'"),
4401251881Speter                              svn_dirent_local_style(dirname, pool));
4402251881Speter
4403251881Speter  return SVN_NO_ERROR;
4404251881Speter}
4405251881Speter
4406251881Speter
4407251881Spetersvn_error_t *
4408251881Spetersvn_io_dir_read(apr_finfo_t *finfo,
4409251881Speter                apr_int32_t wanted,
4410251881Speter                apr_dir_t *thedir,
4411251881Speter                apr_pool_t *pool)
4412251881Speter{
4413251881Speter  apr_status_t status;
4414251881Speter
4415251881Speter  status = apr_dir_read(finfo, wanted, thedir);
4416251881Speter
4417251881Speter  if (status)
4418251881Speter    return svn_error_wrap_apr(status, _("Can't read directory"));
4419251881Speter
4420251881Speter  /* It would be nice to use entry_name_to_utf8() below, but can we
4421251881Speter     get the dir's path out of an apr_dir_t?  I don't see a reliable
4422251881Speter     way to do it. */
4423251881Speter
4424251881Speter  if (finfo->fname)
4425251881Speter    SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool));
4426251881Speter
4427251881Speter  if (finfo->name)
4428251881Speter    SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool));
4429251881Speter
4430251881Speter  return SVN_NO_ERROR;
4431251881Speter}
4432251881Speter
4433251881Spetersvn_error_t *
4434251881Spetersvn_io_dir_close(apr_dir_t *thedir)
4435251881Speter{
4436251881Speter  apr_status_t apr_err = apr_dir_close(thedir);
4437251881Speter  if (apr_err)
4438251881Speter    return svn_error_wrap_apr(apr_err, _("Error closing directory"));
4439251881Speter
4440251881Speter  return SVN_NO_ERROR;
4441251881Speter}
4442251881Speter
4443251881Spetersvn_error_t *
4444251881Spetersvn_io_dir_walk2(const char *dirname,
4445251881Speter                 apr_int32_t wanted,
4446251881Speter                 svn_io_walk_func_t walk_func,
4447251881Speter                 void *walk_baton,
4448251881Speter                 apr_pool_t *pool)
4449251881Speter{
4450251881Speter  apr_status_t apr_err;
4451251881Speter  apr_dir_t *handle;
4452251881Speter  apr_pool_t *subpool;
4453251881Speter  const char *dirname_apr;
4454251881Speter  apr_finfo_t finfo;
4455251881Speter
4456251881Speter  wanted |= APR_FINFO_TYPE | APR_FINFO_NAME;
4457251881Speter
4458251881Speter  /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
4459251881Speter  wanted &= ~SVN__APR_FINFO_MASK_OUT;
4460251881Speter
4461251881Speter  /* The documentation for apr_dir_read used to state that "." and ".."
4462251881Speter     will be returned as the first two files, but it doesn't
4463251881Speter     work that way in practice, in particular ext3 on Linux-2.6 doesn't
4464251881Speter     follow the rules.  For details see
4465251881Speter     http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666
4466251881Speter
4467251881Speter     If APR ever does implement "dot-first" then it would be possible to
4468251881Speter     remove the svn_io_stat and walk_func calls and use the walk_func
4469251881Speter     inside the loop.
4470251881Speter
4471251881Speter     Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is
4472251881Speter     documented to provide it, so we have to do a bit extra. */
4473251881Speter  SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool));
4474251881Speter  SVN_ERR(cstring_from_utf8(&finfo.name,
4475251881Speter                            svn_dirent_basename(dirname, pool),
4476251881Speter                            pool));
4477251881Speter  finfo.valid |= APR_FINFO_NAME;
4478251881Speter  SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool));
4479251881Speter
4480251881Speter  SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
4481251881Speter
4482251881Speter  /* APR doesn't like "" directories */
4483251881Speter  if (dirname_apr[0] == '\0')
4484251881Speter    dirname_apr = ".";
4485251881Speter
4486251881Speter  apr_err = apr_dir_open(&handle, dirname_apr, pool);
4487251881Speter  if (apr_err)
4488251881Speter    return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"),
4489251881Speter                              svn_dirent_local_style(dirname, pool));
4490251881Speter
4491251881Speter  /* iteration subpool */
4492251881Speter  subpool = svn_pool_create(pool);
4493251881Speter
4494251881Speter  while (1)
4495251881Speter    {
4496251881Speter      const char *name_utf8;
4497251881Speter      const char *full_path;
4498251881Speter
4499251881Speter      svn_pool_clear(subpool);
4500251881Speter
4501251881Speter      apr_err = apr_dir_read(&finfo, wanted, handle);
4502251881Speter      if (APR_STATUS_IS_ENOENT(apr_err))
4503251881Speter        break;
4504251881Speter      else if (apr_err)
4505251881Speter        {
4506251881Speter          return svn_error_wrap_apr(apr_err,
4507251881Speter                                    _("Can't read directory entry in '%s'"),
4508251881Speter                                    svn_dirent_local_style(dirname, pool));
4509251881Speter        }
4510251881Speter
4511251881Speter      if (finfo.filetype == APR_DIR)
4512251881Speter        {
4513251881Speter          if (finfo.name[0] == '.'
4514251881Speter              && (finfo.name[1] == '\0'
4515251881Speter                  || (finfo.name[1] == '.' && finfo.name[2] == '\0')))
4516251881Speter            /* skip "." and ".." */
4517251881Speter            continue;
4518251881Speter
4519251881Speter          /* some other directory. recurse. it will be passed to the
4520251881Speter             callback inside the recursion. */
4521251881Speter          SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
4522251881Speter                                     subpool));
4523251881Speter          full_path = svn_dirent_join(dirname, name_utf8, subpool);
4524251881Speter          SVN_ERR(svn_io_dir_walk2(full_path,
4525251881Speter                                   wanted,
4526251881Speter                                   walk_func,
4527251881Speter                                   walk_baton,
4528251881Speter                                   subpool));
4529251881Speter        }
4530251881Speter      else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
4531251881Speter        {
4532251881Speter          /* some other directory. pass it to the callback. */
4533251881Speter          SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
4534251881Speter                                     subpool));
4535251881Speter          full_path = svn_dirent_join(dirname, name_utf8, subpool);
4536251881Speter          SVN_ERR((*walk_func)(walk_baton,
4537251881Speter                               full_path,
4538251881Speter                               &finfo,
4539251881Speter                               subpool));
4540251881Speter        }
4541251881Speter      /* else:
4542251881Speter         Some other type of file; skip it for now.  We've reserved the
4543251881Speter         right to expand our coverage here in the future, though,
4544251881Speter         without revving this API.
4545251881Speter      */
4546251881Speter    }
4547251881Speter
4548251881Speter  svn_pool_destroy(subpool);
4549251881Speter
4550251881Speter  apr_err = apr_dir_close(handle);
4551251881Speter  if (apr_err)
4552251881Speter    return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"),
4553251881Speter                              svn_dirent_local_style(dirname, pool));
4554251881Speter
4555251881Speter  return SVN_NO_ERROR;
4556251881Speter}
4557251881Speter
4558251881Speter
4559251881Speter
4560251881Speter/**
4561251881Speter * Determine if a directory is empty or not.
4562251881Speter * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not.
4563251881Speter * @param path The directory.
4564251881Speter * @param pool Used for temporary allocation.
4565251881Speter * @remark If path is not a directory, or some other error occurs,
4566251881Speter * then return the appropriate apr status code.
4567251881Speter *
4568251881Speter * (This function is written in APR style, in anticipation of
4569251881Speter * perhaps someday being moved to APR as 'apr_dir_is_empty'.)
4570251881Speter */
4571251881Speterstatic apr_status_t
4572251881Speterdir_is_empty(const char *dir, apr_pool_t *pool)
4573251881Speter{
4574251881Speter  apr_status_t apr_err;
4575251881Speter  apr_dir_t *dir_handle;
4576251881Speter  apr_finfo_t finfo;
4577251881Speter  apr_status_t retval = APR_SUCCESS;
4578251881Speter
4579251881Speter  /* APR doesn't like "" directories */
4580251881Speter  if (dir[0] == '\0')
4581251881Speter    dir = ".";
4582251881Speter
4583251881Speter  apr_err = apr_dir_open(&dir_handle, dir, pool);
4584251881Speter  if (apr_err != APR_SUCCESS)
4585251881Speter    return apr_err;
4586251881Speter
4587251881Speter  for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle);
4588251881Speter       apr_err == APR_SUCCESS;
4589251881Speter       apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle))
4590251881Speter    {
4591251881Speter      /* Ignore entries for this dir and its parent, robustly.
4592251881Speter         (APR promises that they'll come first, so technically
4593251881Speter         this guard could be moved outside the loop.  But Ryan Bloom
4594251881Speter         says he doesn't believe it, and I believe him. */
4595251881Speter      if (! (finfo.name[0] == '.'
4596251881Speter             && (finfo.name[1] == '\0'
4597251881Speter                 || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
4598251881Speter        {
4599251881Speter          retval = APR_ENOTEMPTY;
4600251881Speter          break;
4601251881Speter        }
4602251881Speter    }
4603251881Speter
4604251881Speter  /* Make sure we broke out of the loop for the right reason. */
4605251881Speter  if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err))
4606251881Speter    return apr_err;
4607251881Speter
4608251881Speter  apr_err = apr_dir_close(dir_handle);
4609251881Speter  if (apr_err != APR_SUCCESS)
4610251881Speter    return apr_err;
4611251881Speter
4612251881Speter  return retval;
4613251881Speter}
4614251881Speter
4615251881Speter
4616251881Spetersvn_error_t *
4617251881Spetersvn_io_dir_empty(svn_boolean_t *is_empty_p,
4618251881Speter                 const char *path,
4619251881Speter                 apr_pool_t *pool)
4620251881Speter{
4621251881Speter  apr_status_t status;
4622251881Speter  const char *path_apr;
4623251881Speter
4624251881Speter  SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
4625251881Speter
4626251881Speter  status = dir_is_empty(path_apr, pool);
4627251881Speter
4628251881Speter  if (!status)
4629251881Speter    *is_empty_p = TRUE;
4630251881Speter  else if (APR_STATUS_IS_ENOTEMPTY(status))
4631251881Speter    *is_empty_p = FALSE;
4632251881Speter  else
4633251881Speter    return svn_error_wrap_apr(status, _("Can't check directory '%s'"),
4634251881Speter                              svn_dirent_local_style(path, pool));
4635251881Speter
4636251881Speter  return SVN_NO_ERROR;
4637251881Speter}
4638251881Speter
4639251881Speter
4640251881Speter
4641251881Speter/*** Version/format files ***/
4642251881Speter
4643251881Spetersvn_error_t *
4644251881Spetersvn_io_write_version_file(const char *path,
4645251881Speter                          int version,
4646251881Speter                          apr_pool_t *pool)
4647251881Speter{
4648251881Speter  const char *path_tmp;
4649251881Speter  const char *format_contents = apr_psprintf(pool, "%d\n", version);
4650251881Speter
4651251881Speter  SVN_ERR_ASSERT(version >= 0);
4652251881Speter
4653251881Speter  SVN_ERR(svn_io_write_unique(&path_tmp,
4654251881Speter                              svn_dirent_dirname(path, pool),
4655251881Speter                              format_contents, strlen(format_contents),
4656251881Speter                              svn_io_file_del_none, pool));
4657251881Speter
4658251881Speter#if defined(WIN32) || defined(__OS2__)
4659251881Speter  /* make the destination writable, but only on Windows, because
4660251881Speter     Windows does not let us replace read-only files. */
4661251881Speter  SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
4662251881Speter#endif /* WIN32 || __OS2__ */
4663251881Speter
4664251881Speter  /* rename the temp file as the real destination */
4665251881Speter  SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
4666251881Speter
4667251881Speter  /* And finally remove the perms to make it read only */
4668251881Speter  return svn_io_set_file_read_only(path, FALSE, pool);
4669251881Speter}
4670251881Speter
4671251881Speter
4672251881Spetersvn_error_t *
4673251881Spetersvn_io_read_version_file(int *version,
4674251881Speter                         const char *path,
4675251881Speter                         apr_pool_t *pool)
4676251881Speter{
4677251881Speter  apr_file_t *format_file;
4678251881Speter  char buf[80];
4679251881Speter  apr_size_t len;
4680251881Speter  svn_error_t *err;
4681251881Speter
4682251881Speter  /* Read a chunk of data from PATH */
4683251881Speter  SVN_ERR(svn_io_file_open(&format_file, path, APR_READ,
4684251881Speter                           APR_OS_DEFAULT, pool));
4685251881Speter  len = sizeof(buf);
4686251881Speter  err = svn_io_file_read(format_file, buf, &len, pool);
4687251881Speter
4688251881Speter  /* Close the file. */
4689251881Speter  SVN_ERR(svn_error_compose_create(err,
4690251881Speter                                   svn_io_file_close(format_file, pool)));
4691251881Speter
4692251881Speter  /* If there was no data in PATH, return an error. */
4693251881Speter  if (len == 0)
4694251881Speter    return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
4695251881Speter                             _("Reading '%s'"),
4696251881Speter                             svn_dirent_local_style(path, pool));
4697251881Speter
4698251881Speter  /* Check that the first line contains only digits. */
4699251881Speter  {
4700251881Speter    apr_size_t i;
4701251881Speter
4702251881Speter    for (i = 0; i < len; ++i)
4703251881Speter      {
4704251881Speter        char c = buf[i];
4705251881Speter
4706251881Speter        if (i > 0 && (c == '\r' || c == '\n'))
4707251881Speter          {
4708251881Speter            buf[i] = '\0';
4709251881Speter            break;
4710251881Speter          }
4711251881Speter        if (! svn_ctype_isdigit(c))
4712251881Speter          return svn_error_createf
4713251881Speter            (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
4714251881Speter             _("First line of '%s' contains non-digit"),
4715251881Speter             svn_dirent_local_style(path, pool));
4716251881Speter      }
4717251881Speter  }
4718251881Speter
4719251881Speter  /* Convert to integer. */
4720251881Speter  SVN_ERR(svn_cstring_atoi(version, buf));
4721251881Speter
4722251881Speter  return SVN_NO_ERROR;
4723251881Speter}
4724251881Speter
4725251881Speter
4726251881Speter/* Do a byte-for-byte comparison of FILE1 and FILE2. */
4727251881Speterstatic svn_error_t *
4728251881Spetercontents_identical_p(svn_boolean_t *identical_p,
4729251881Speter                     const char *file1,
4730251881Speter                     const char *file2,
4731251881Speter                     apr_pool_t *pool)
4732251881Speter{
4733251881Speter  svn_error_t *err;
4734251881Speter  apr_size_t bytes_read1, bytes_read2;
4735251881Speter  char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
4736251881Speter  char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
4737251881Speter  apr_file_t *file1_h;
4738251881Speter  apr_file_t *file2_h;
4739251881Speter  svn_boolean_t eof1 = FALSE;
4740251881Speter  svn_boolean_t eof2 = FALSE;
4741251881Speter
4742251881Speter  SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
4743251881Speter                           pool));
4744251881Speter
4745251881Speter  err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
4746251881Speter                         pool);
4747251881Speter
4748251881Speter  if (err)
4749251881Speter    return svn_error_trace(
4750251881Speter               svn_error_compose_create(err,
4751251881Speter                                        svn_io_file_close(file1_h, pool)));
4752251881Speter
4753251881Speter  *identical_p = TRUE;  /* assume TRUE, until disproved below */
4754251881Speter  while (!err && !eof1 && !eof2)
4755251881Speter    {
4756251881Speter      err = svn_io_file_read_full2(file1_h, buf1,
4757251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read1,
4758251881Speter                                   &eof1, pool);
4759251881Speter      if (err)
4760251881Speter          break;
4761251881Speter
4762251881Speter      err = svn_io_file_read_full2(file2_h, buf2,
4763251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read2,
4764251881Speter                                   &eof2, pool);
4765251881Speter      if (err)
4766251881Speter          break;
4767251881Speter
4768251881Speter      if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1))
4769251881Speter        {
4770251881Speter          *identical_p = FALSE;
4771251881Speter          break;
4772251881Speter        }
4773251881Speter    }
4774251881Speter
4775251881Speter  /* Special case: one file being a prefix of the other and the shorter
4776251881Speter   * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */
4777251881Speter  if (!err && (eof1 != eof2))
4778251881Speter    *identical_p = FALSE;
4779251881Speter
4780251881Speter  return svn_error_trace(
4781251881Speter           svn_error_compose_create(
4782251881Speter                err,
4783251881Speter                svn_error_compose_create(svn_io_file_close(file1_h, pool),
4784251881Speter                                         svn_io_file_close(file2_h, pool))));
4785251881Speter}
4786251881Speter
4787251881Speter
4788251881Speter
4789251881Speter/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */
4790251881Speterstatic svn_error_t *
4791251881Spetercontents_three_identical_p(svn_boolean_t *identical_p12,
4792251881Speter                           svn_boolean_t *identical_p23,
4793251881Speter                           svn_boolean_t *identical_p13,
4794251881Speter                           const char *file1,
4795251881Speter                           const char *file2,
4796251881Speter                           const char *file3,
4797251881Speter                           apr_pool_t *scratch_pool)
4798251881Speter{
4799251881Speter  svn_error_t *err;
4800251881Speter  char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4801251881Speter  char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4802251881Speter  char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
4803251881Speter  apr_file_t *file1_h;
4804251881Speter  apr_file_t *file2_h;
4805251881Speter  apr_file_t *file3_h;
4806251881Speter  svn_boolean_t eof1 = FALSE;
4807251881Speter  svn_boolean_t eof2 = FALSE;
4808251881Speter  svn_boolean_t eof3 = FALSE;
4809251881Speter
4810251881Speter  SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
4811251881Speter                           scratch_pool));
4812251881Speter
4813251881Speter  err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
4814251881Speter                         scratch_pool);
4815251881Speter
4816251881Speter  if (err)
4817251881Speter    return svn_error_trace(
4818251881Speter               svn_error_compose_create(err,
4819251881Speter                                        svn_io_file_close(file1_h, scratch_pool)));
4820251881Speter
4821251881Speter  err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT,
4822251881Speter                         scratch_pool);
4823251881Speter
4824251881Speter  if (err)
4825251881Speter      return svn_error_trace(
4826251881Speter               svn_error_compose_create(
4827251881Speter                    err,
4828251881Speter                    svn_error_compose_create(svn_io_file_close(file1_h,
4829251881Speter                                                          scratch_pool),
4830251881Speter                                             svn_io_file_close(file2_h,
4831251881Speter                                                          scratch_pool))));
4832251881Speter
4833251881Speter  /* assume TRUE, until disproved below */
4834251881Speter  *identical_p12 = *identical_p23 = *identical_p13 = TRUE;
4835251881Speter  /* We need to read as long as no error occurs, and as long as one of the
4836251881Speter   * flags could still change due to a read operation */
4837251881Speter  while (!err
4838251881Speter        && ((*identical_p12 && !eof1 && !eof2)
4839251881Speter            || (*identical_p23 && !eof2 && !eof3)
4840251881Speter            || (*identical_p13 && !eof1 && !eof3)))
4841251881Speter    {
4842299742Sdim      apr_size_t bytes_read1, bytes_read2, bytes_read3;
4843299742Sdim      svn_boolean_t read_1, read_2, read_3;
4844299742Sdim
4845251881Speter      read_1 = read_2 = read_3 = FALSE;
4846251881Speter
4847251881Speter      /* As long as a file is not at the end yet, and it is still
4848251881Speter       * potentially identical to another file, we read the next chunk.*/
4849262253Speter      if (!eof1 && (*identical_p12 || *identical_p13))
4850251881Speter        {
4851251881Speter          err = svn_io_file_read_full2(file1_h, buf1,
4852251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read1,
4853251881Speter                                   &eof1, scratch_pool);
4854251881Speter          if (err)
4855251881Speter              break;
4856251881Speter          read_1 = TRUE;
4857251881Speter        }
4858251881Speter
4859262253Speter      if (!eof2 && (*identical_p12 || *identical_p23))
4860251881Speter        {
4861251881Speter          err = svn_io_file_read_full2(file2_h, buf2,
4862251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read2,
4863251881Speter                                   &eof2, scratch_pool);
4864251881Speter          if (err)
4865251881Speter              break;
4866251881Speter          read_2 = TRUE;
4867251881Speter        }
4868251881Speter
4869262253Speter      if (!eof3 && (*identical_p13 || *identical_p23))
4870251881Speter        {
4871251881Speter          err = svn_io_file_read_full2(file3_h, buf3,
4872251881Speter                                   SVN__STREAM_CHUNK_SIZE, &bytes_read3,
4873251881Speter                                   &eof3, scratch_pool);
4874251881Speter          if (err)
4875251881Speter              break;
4876251881Speter          read_3 = TRUE;
4877251881Speter        }
4878251881Speter
4879251881Speter      /* If the files are still marked identical, and at least one of them
4880251881Speter       * is not at the end of file, we check whether they differ, and set
4881251881Speter       * their flag to false then. */
4882251881Speter      if (*identical_p12
4883251881Speter          && (read_1 || read_2)
4884251881Speter          && ((eof1 != eof2)
4885251881Speter              || (bytes_read1 != bytes_read2)
4886251881Speter              || memcmp(buf1, buf2, bytes_read1)))
4887251881Speter        {
4888251881Speter          *identical_p12 = FALSE;
4889251881Speter        }
4890251881Speter
4891251881Speter      if (*identical_p23
4892251881Speter          && (read_2 || read_3)
4893251881Speter          && ((eof2 != eof3)
4894251881Speter              || (bytes_read2 != bytes_read3)
4895251881Speter              || memcmp(buf2, buf3, bytes_read2)))
4896251881Speter        {
4897251881Speter          *identical_p23 = FALSE;
4898251881Speter        }
4899251881Speter
4900251881Speter      if (*identical_p13
4901251881Speter          && (read_1 || read_3)
4902251881Speter          && ((eof1 != eof3)
4903251881Speter              || (bytes_read1 != bytes_read3)
4904251881Speter              || memcmp(buf1, buf3, bytes_read3)))
4905251881Speter        {
4906251881Speter          *identical_p13 = FALSE;
4907251881Speter        }
4908251881Speter    }
4909251881Speter
4910251881Speter  return svn_error_trace(
4911251881Speter           svn_error_compose_create(
4912251881Speter                err,
4913251881Speter                svn_error_compose_create(
4914251881Speter                    svn_io_file_close(file1_h, scratch_pool),
4915251881Speter                    svn_error_compose_create(
4916251881Speter                        svn_io_file_close(file2_h, scratch_pool),
4917251881Speter                        svn_io_file_close(file3_h, scratch_pool)))));
4918251881Speter}
4919251881Speter
4920251881Speter
4921251881Speter
4922251881Spetersvn_error_t *
4923251881Spetersvn_io_files_contents_same_p(svn_boolean_t *same,
4924251881Speter                             const char *file1,
4925251881Speter                             const char *file2,
4926251881Speter                             apr_pool_t *pool)
4927251881Speter{
4928251881Speter  svn_boolean_t q;
4929251881Speter
4930251881Speter  SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool));
4931251881Speter
4932251881Speter  if (q)
4933251881Speter    {
4934251881Speter      *same = FALSE;
4935251881Speter      return SVN_NO_ERROR;
4936251881Speter    }
4937251881Speter
4938251881Speter  SVN_ERR(contents_identical_p(&q, file1, file2, pool));
4939251881Speter
4940251881Speter  if (q)
4941251881Speter    *same = TRUE;
4942251881Speter  else
4943251881Speter    *same = FALSE;
4944251881Speter
4945251881Speter  return SVN_NO_ERROR;
4946251881Speter}
4947251881Speter
4948251881Spetersvn_error_t *
4949251881Spetersvn_io_files_contents_three_same_p(svn_boolean_t *same12,
4950251881Speter                                   svn_boolean_t *same23,
4951251881Speter                                   svn_boolean_t *same13,
4952251881Speter                                   const char *file1,
4953251881Speter                                   const char *file2,
4954251881Speter                                   const char *file3,
4955251881Speter                                   apr_pool_t *scratch_pool)
4956251881Speter{
4957251881Speter  svn_boolean_t diff_size12, diff_size23, diff_size13;
4958251881Speter
4959251881Speter  SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12,
4960251881Speter                                             &diff_size23,
4961251881Speter                                             &diff_size13,
4962251881Speter                                             file1,
4963251881Speter                                             file2,
4964251881Speter                                             file3,
4965251881Speter                                             scratch_pool));
4966251881Speter
4967251881Speter  if (diff_size12 && diff_size23 && diff_size13)
4968251881Speter    {
4969251881Speter      *same12 = *same23 = *same13 = FALSE;
4970251881Speter    }
4971251881Speter  else if (diff_size12 && diff_size23)
4972251881Speter    {
4973251881Speter      *same12 = *same23 = FALSE;
4974251881Speter      SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool));
4975251881Speter    }
4976251881Speter  else if (diff_size23 && diff_size13)
4977251881Speter    {
4978251881Speter      *same23 = *same13 = FALSE;
4979251881Speter      SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool));
4980251881Speter    }
4981251881Speter  else if (diff_size12 && diff_size13)
4982251881Speter    {
4983251881Speter      *same12 = *same13 = FALSE;
4984251881Speter      SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool));
4985251881Speter    }
4986251881Speter  else
4987251881Speter    {
4988251881Speter      SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13);
4989251881Speter      SVN_ERR(contents_three_identical_p(same12, same23, same13,
4990251881Speter                                         file1, file2, file3,
4991251881Speter                                         scratch_pool));
4992251881Speter    }
4993251881Speter
4994251881Speter  return SVN_NO_ERROR;
4995251881Speter}
4996251881Speter
4997251881Speter#ifdef WIN32
4998251881Speter/* Counter value of file_mktemp request (used in a threadsafe way), to make
4999251881Speter   sure that a single process normally never generates the same tempname
5000251881Speter   twice */
5001251881Speterstatic volatile apr_uint32_t tempname_counter = 0;
5002251881Speter#endif
5003251881Speter
5004251881Speter/* Creates a new temporary file in DIRECTORY with apr flags FLAGS.
5005251881Speter   Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name.
5006251881Speter   Perform temporary allocations in SCRATCH_POOL and the result in
5007251881Speter   RESULT_POOL. */
5008251881Speterstatic svn_error_t *
5009251881Spetertemp_file_create(apr_file_t **new_file,
5010251881Speter                 const char **new_file_name,
5011251881Speter                 const char *directory,
5012251881Speter                 apr_int32_t flags,
5013251881Speter                 apr_pool_t *result_pool,
5014251881Speter                 apr_pool_t *scratch_pool)
5015251881Speter{
5016251881Speter#ifndef WIN32
5017251881Speter  const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool);
5018251881Speter  const char *templ_apr;
5019251881Speter  apr_status_t status;
5020251881Speter
5021251881Speter  SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool));
5022251881Speter
5023251881Speter  /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the
5024251881Speter         data available in POOL and we need a non-const pointer here,
5025251881Speter         as apr changes the template to return the new filename. */
5026251881Speter  status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool);
5027251881Speter
5028251881Speter  if (status)
5029251881Speter    return svn_error_wrap_apr(status, _("Can't create temporary file from "
5030251881Speter                              "template '%s'"), templ);
5031251881Speter
5032251881Speter  /* Translate the returned path back to utf-8 before returning it */
5033251881Speter  return svn_error_trace(svn_path_cstring_to_utf8(new_file_name,
5034251881Speter                                                  templ_apr,
5035251881Speter                                                  result_pool));
5036251881Speter#else
5037251881Speter  /* The Windows implementation of apr_file_mktemp doesn't handle access
5038251881Speter     denied errors correctly. Therefore we implement our own temp file
5039251881Speter     creation function here. */
5040251881Speter
5041251881Speter  /* ### Most of this is borrowed from the svn_io_open_uniquely_named(),
5042251881Speter     ### the function we used before. But we try to guess a more unique
5043251881Speter     ### name before trying if it exists. */
5044251881Speter
5045251881Speter  /* Offset by some time value and a unique request nr to make the number
5046251881Speter     +- unique for both this process and on the computer */
5047251881Speter  int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter)
5048251881Speter               + GetCurrentProcessId();
5049251881Speter  int i;
5050251881Speter
5051251881Speter  /* ### Maybe use an iterpool? */
5052251881Speter  for (i = 0; i <= 99999; i++)
5053251881Speter    {
5054251881Speter      apr_uint32_t unique_nr;
5055251881Speter      const char *unique_name;
5056251881Speter      const char *unique_name_apr;
5057251881Speter      apr_file_t *try_file;
5058251881Speter      apr_status_t apr_err;
5059251881Speter
5060251881Speter      /* Generate a number that should be unique for this application and
5061251881Speter         usually for the entire computer to reduce the number of cycles
5062251881Speter         through this loop. (A bit of calculation is much cheaper then
5063251881Speter         disk io) */
5064251881Speter      unique_nr = baseNr + 3 * i;
5065251881Speter
5066251881Speter      unique_name = svn_dirent_join(directory,
5067251881Speter                                    apr_psprintf(scratch_pool, "svn-%X",
5068251881Speter                                                 unique_nr),
5069251881Speter                                    scratch_pool);
5070251881Speter
5071251881Speter      SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool));
5072251881Speter
5073251881Speter      apr_err = file_open(&try_file, unique_name_apr, flags,
5074251881Speter                          APR_OS_DEFAULT, FALSE, scratch_pool);
5075251881Speter
5076251881Speter      if (APR_STATUS_IS_EEXIST(apr_err))
5077251881Speter          continue;
5078251881Speter      else if (apr_err)
5079251881Speter        {
5080251881Speter          /* On Win32, CreateFile fails with an "Access Denied" error
5081251881Speter             code, rather than "File Already Exists", if the colliding
5082251881Speter             name belongs to a directory. */
5083251881Speter
5084251881Speter          if (APR_STATUS_IS_EACCES(apr_err))
5085251881Speter            {
5086251881Speter              apr_finfo_t finfo;
5087251881Speter              apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
5088251881Speter                                                APR_FINFO_TYPE, scratch_pool);
5089251881Speter
5090251881Speter              if (!apr_err_2 && finfo.filetype == APR_DIR)
5091251881Speter                continue;
5092251881Speter
5093251881Speter              apr_err_2 = APR_TO_OS_ERROR(apr_err);
5094251881Speter
5095251881Speter              if (apr_err_2 == ERROR_ACCESS_DENIED ||
5096251881Speter                  apr_err_2 == ERROR_SHARING_VIOLATION)
5097251881Speter                {
5098251881Speter                  /* The file is in use by another process or is hidden;
5099251881Speter                     create a new name, but don't do this 99999 times in
5100251881Speter                     case the folder is not writable */
5101251881Speter                  i += 797;
5102251881Speter                  continue;
5103251881Speter                }
5104251881Speter
5105251881Speter              /* Else fall through and return the original error. */
5106251881Speter            }
5107251881Speter
5108251881Speter          return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
5109251881Speter                                    svn_dirent_local_style(unique_name,
5110251881Speter                                                           scratch_pool));
5111251881Speter        }
5112251881Speter      else
5113251881Speter        {
5114251881Speter          /* Move file to the right pool */
5115251881Speter          apr_err = apr_file_setaside(new_file, try_file, result_pool);
5116251881Speter
5117251881Speter          if (apr_err)
5118251881Speter            return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"),
5119251881Speter                                      svn_dirent_local_style(unique_name,
5120251881Speter                                                             scratch_pool));
5121251881Speter
5122251881Speter          *new_file_name = apr_pstrdup(result_pool, unique_name);
5123251881Speter
5124251881Speter          return SVN_NO_ERROR;
5125251881Speter        }
5126251881Speter    }
5127251881Speter
5128251881Speter  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
5129251881Speter                           NULL,
5130251881Speter                           _("Unable to make name in '%s'"),
5131251881Speter                           svn_dirent_local_style(directory, scratch_pool));
5132251881Speter#endif
5133251881Speter}
5134251881Speter
5135251881Speter/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */
5136251881Spetersvn_error_t *
5137251881Spetersvn_io_file_name_get(const char **filename,
5138251881Speter                     apr_file_t *file,
5139251881Speter                     apr_pool_t *pool)
5140251881Speter{
5141251881Speter  const char *fname_apr;
5142251881Speter  apr_status_t status;
5143251881Speter
5144251881Speter  status = apr_file_name_get(&fname_apr, file);
5145251881Speter  if (status)
5146251881Speter    return svn_error_wrap_apr(status, _("Can't get file name"));
5147251881Speter
5148251881Speter  if (fname_apr)
5149251881Speter    SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool));
5150251881Speter  else
5151251881Speter    *filename = NULL;
5152251881Speter
5153251881Speter  return SVN_NO_ERROR;
5154251881Speter}
5155251881Speter
5156251881Speter
5157251881Spetersvn_error_t *
5158251881Spetersvn_io_open_unique_file3(apr_file_t **file,
5159251881Speter                         const char **unique_path,
5160251881Speter                         const char *dirpath,
5161251881Speter                         svn_io_file_del_t delete_when,
5162251881Speter                         apr_pool_t *result_pool,
5163251881Speter                         apr_pool_t *scratch_pool)
5164251881Speter{
5165251881Speter  apr_file_t *tempfile;
5166251881Speter  const char *tempname;
5167251881Speter  struct temp_file_cleanup_s *baton = NULL;
5168251881Speter  apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL |
5169251881Speter                       APR_BUFFERED | APR_BINARY);
5170251881Speter#if !defined(WIN32) && !defined(__OS2__)
5171251881Speter  apr_fileperms_t perms;
5172251881Speter  svn_boolean_t using_system_temp_dir = FALSE;
5173251881Speter#endif
5174251881Speter
5175251881Speter  SVN_ERR_ASSERT(file || unique_path);
5176251881Speter  if (file)
5177251881Speter    *file = NULL;
5178251881Speter  if (unique_path)
5179251881Speter    *unique_path = NULL;
5180251881Speter
5181251881Speter  if (dirpath == NULL)
5182251881Speter    {
5183251881Speter#if !defined(WIN32) && !defined(__OS2__)
5184251881Speter      using_system_temp_dir = TRUE;
5185251881Speter#endif
5186251881Speter      SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
5187251881Speter    }
5188251881Speter
5189251881Speter  switch (delete_when)
5190251881Speter    {
5191251881Speter      case svn_io_file_del_on_pool_cleanup:
5192251881Speter        baton = apr_palloc(result_pool, sizeof(*baton));
5193251881Speter        baton->pool = result_pool;
5194251881Speter        baton->fname_apr = NULL;
5195251881Speter
5196251881Speter        /* Because cleanups are run LIFO, we need to make sure to register
5197251881Speter           our cleanup before the apr_file_close cleanup:
5198251881Speter
5199251881Speter           On Windows, you can't remove an open file.
5200251881Speter        */
5201251881Speter        apr_pool_cleanup_register(result_pool, baton,
5202251881Speter                                  temp_file_plain_cleanup_handler,
5203251881Speter                                  temp_file_child_cleanup_handler);
5204251881Speter
5205251881Speter        break;
5206251881Speter      case svn_io_file_del_on_close:
5207251881Speter        flags |= APR_DELONCLOSE;
5208251881Speter        break;
5209251881Speter      default:
5210251881Speter        break;
5211251881Speter    }
5212251881Speter
5213251881Speter  SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags,
5214251881Speter                           result_pool, scratch_pool));
5215251881Speter
5216251881Speter#if !defined(WIN32) && !defined(__OS2__)
5217251881Speter  /* apr_file_mktemp() creates files with mode 0600.
5218251881Speter   * This is appropriate if we're using a system temp dir since we don't
5219251881Speter   * want to leak sensitive data into temp files other users can read.
5220251881Speter   * If we're not using a system temp dir we're probably using the
5221251881Speter   * .svn/tmp area and it's likely that the tempfile will end up being
5222251881Speter   * copied or renamed into the working copy.
5223251881Speter   * This would cause working files having mode 0600 while users might
5224251881Speter   * expect to see 0644 or 0664. So we tweak perms of the tempfile in this
5225251881Speter   * case, but only if the umask allows it. */
5226251881Speter  if (!using_system_temp_dir)
5227251881Speter    {
5228289166Speter      svn_error_t *err;
5229289166Speter
5230251881Speter      SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool));
5231289166Speter      err = file_perms_set2(tempfile, perms, scratch_pool);
5232289166Speter      if (err)
5233289166Speter        {
5234289166Speter          if (APR_STATUS_IS_INCOMPLETE(err->apr_err) ||
5235289166Speter              APR_STATUS_IS_ENOTIMPL(err->apr_err))
5236289166Speter            svn_error_clear(err);
5237289166Speter          else
5238289166Speter            {
5239299742Sdim              return svn_error_quick_wrapf(
5240299742Sdim                       err, _("Can't set permissions on '%s'"),
5241299742Sdim                       svn_dirent_local_style(tempname, scratch_pool));
5242289166Speter            }
5243289166Speter        }
5244251881Speter    }
5245251881Speter#endif
5246251881Speter
5247251881Speter  if (file)
5248251881Speter    *file = tempfile;
5249251881Speter  else
5250251881Speter    SVN_ERR(svn_io_file_close(tempfile, scratch_pool));
5251251881Speter
5252251881Speter  if (unique_path)
5253251881Speter    *unique_path = tempname; /* Was allocated in result_pool */
5254251881Speter
5255251881Speter  if (baton)
5256251881Speter    SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool));
5257251881Speter
5258251881Speter  return SVN_NO_ERROR;
5259251881Speter}
5260251881Speter
5261251881Spetersvn_error_t *
5262251881Spetersvn_io_file_readline(apr_file_t *file,
5263251881Speter                     svn_stringbuf_t **stringbuf,
5264251881Speter                     const char **eol,
5265251881Speter                     svn_boolean_t *eof,
5266251881Speter                     apr_size_t max_len,
5267251881Speter                     apr_pool_t *result_pool,
5268251881Speter                     apr_pool_t *scratch_pool)
5269251881Speter{
5270251881Speter  svn_stringbuf_t *str;
5271251881Speter  const char *eol_str;
5272251881Speter  apr_size_t numbytes;
5273251881Speter  char c;
5274251881Speter  apr_size_t len;
5275251881Speter  svn_boolean_t found_eof;
5276251881Speter
5277251881Speter  str = svn_stringbuf_create_ensure(80, result_pool);
5278251881Speter
5279251881Speter  /* Read bytes into STR up to and including, but not storing,
5280251881Speter   * the next EOL sequence. */
5281251881Speter  eol_str = NULL;
5282251881Speter  numbytes = 1;
5283251881Speter  len = 0;
5284251881Speter  found_eof = FALSE;
5285251881Speter  while (!found_eof)
5286251881Speter    {
5287251881Speter      if (len < max_len)
5288251881Speter        SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
5289251881Speter                                       &found_eof, scratch_pool));
5290251881Speter      len++;
5291251881Speter      if (numbytes != 1 || len > max_len)
5292251881Speter        {
5293251881Speter          found_eof = TRUE;
5294251881Speter          break;
5295251881Speter        }
5296251881Speter
5297251881Speter      if (c == '\n')
5298251881Speter        {
5299251881Speter          eol_str = "\n";
5300251881Speter        }
5301251881Speter      else if (c == '\r')
5302251881Speter        {
5303251881Speter          eol_str = "\r";
5304251881Speter
5305251881Speter          if (!found_eof && len < max_len)
5306251881Speter            {
5307251881Speter              apr_off_t pos;
5308251881Speter
5309251881Speter              /* Check for "\r\n" by peeking at the next byte. */
5310251881Speter              pos = 0;
5311251881Speter              SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
5312251881Speter              SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
5313251881Speter                                             &found_eof, scratch_pool));
5314251881Speter              if (numbytes == 1 && c == '\n')
5315251881Speter                {
5316251881Speter                  eol_str = "\r\n";
5317251881Speter                  len++;
5318251881Speter                }
5319251881Speter              else
5320251881Speter                {
5321251881Speter                  /* Pretend we never peeked. */
5322251881Speter                  SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
5323251881Speter                  found_eof = FALSE;
5324251881Speter                  numbytes = 1;
5325251881Speter                }
5326251881Speter            }
5327251881Speter        }
5328251881Speter      else
5329251881Speter        svn_stringbuf_appendbyte(str, c);
5330251881Speter
5331251881Speter      if (eol_str)
5332251881Speter        break;
5333251881Speter    }
5334251881Speter
5335251881Speter  if (eol)
5336251881Speter    *eol = eol_str;
5337251881Speter  if (eof)
5338251881Speter    *eof = found_eof;
5339251881Speter  *stringbuf = str;
5340251881Speter
5341251881Speter  return SVN_NO_ERROR;
5342251881Speter}
5343