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