1 /*
2  * io.c:   shared file reading, writing, and probing code.
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 
25 
26 #include <stdio.h>
27 
28 #ifndef WIN32
29 #include <unistd.h>
30 #endif
31 
32 #ifndef APR_STATUS_IS_EPERM
33 #include <errno.h>
34 #ifdef EPERM
35 #define APR_STATUS_IS_EPERM(s)   ((s) == EPERM)
36 #else
37 #define APR_STATUS_IS_EPERM(s)   (0)
38 #endif
39 #endif
40 
41 #include <apr_lib.h>
42 #include <apr_pools.h>
43 #include <apr_file_io.h>
44 #include <apr_file_info.h>
45 #include <apr_general.h>
46 #include <apr_strings.h>
47 #include <apr_portable.h>
48 #include <apr_md5.h>
49 
50 #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 
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 *
cstring_to_utf8(const char ** path_utf8,const char * path_apr,apr_pool_t * pool)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 *
cstring_from_utf8(const char ** path_apr,const char * path_utf8,apr_pool_t * pool)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 *
try_utf8_from_internal_style(const char * path,apr_pool_t * pool)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 *
entry_name_to_utf8(const char ** name_p,const char * name,const char * parent,apr_pool_t * pool)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
map_apr_finfo_to_node_kind(svn_node_kind_t * kind,svn_boolean_t * is_special,apr_finfo_t * finfo)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 *
io_check_path(const char * path,svn_boolean_t resolve_symlinks,svn_boolean_t * is_special_p,svn_node_kind_t * kind,apr_pool_t * pool)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
file_open(apr_file_t ** f,const char * fname_apr,apr_int32_t flag,apr_fileperms_t perm,svn_boolean_t retry_on_failure,apr_pool_t * pool)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 *
svn_io_check_resolved_path(const char * path,svn_node_kind_t * kind,apr_pool_t * pool)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 *
svn_io_check_path(const char * path,svn_node_kind_t * kind,apr_pool_t * pool)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 *
svn_io_check_special_path(const char * path,svn_node_kind_t * kind,svn_boolean_t * is_special,apr_pool_t * pool)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
temp_file_plain_cleanup_handler(void * baton)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
temp_file_child_cleanup_handler(void * baton)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 *
svn_io_open_uniquely_named(apr_file_t ** file,const char ** unique_path,const char * dirpath,const char * filename,const char * suffix,svn_io_file_del_t delete_when,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_io_create_unique_link(const char ** unique_name_p,const char * path,const char * dest,const char * suffix,apr_pool_t * pool)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 *
svn_io_read_link(svn_string_t ** dest,const char * path,apr_pool_t * pool)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 *
svn_io_copy_link(const char * src,const char * dst,apr_pool_t * pool)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 *
init_temp_dir(void * baton,apr_pool_t * scratch_pool)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 *
svn_io_temp_dir(const char ** dir,apr_pool_t * pool)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 
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
copy_contents(apr_file_t * from_file,apr_file_t * to_file,apr_pool_t * pool)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 *
svn_io_copy_file(const char * src,const char * dst,svn_boolean_t copy_perms,apr_pool_t * pool)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 *
file_perms_set(const char * fname,apr_fileperms_t perms,apr_pool_t * pool)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 *
file_perms_set2(apr_file_t * file,apr_fileperms_t perms,apr_pool_t * pool)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 *
svn_io_copy_perms(const char * src,const char * dst,apr_pool_t * pool)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 *
svn_io_append_file(const char * src,const char * dst,apr_pool_t * pool)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 
svn_io_copy_dir_recursively(const char * src,const char * dst_parent,const char * dst_basename,svn_boolean_t copy_perms,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)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 *
svn_io_make_dir_recursively(const char * path,apr_pool_t * pool)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 *
svn_io_file_create_bytes(const char * file,const void * contents,apr_size_t length,apr_pool_t * scratch_pool)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 *
svn_io_file_create(const char * file,const char * contents,apr_pool_t * pool)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 *
svn_io_file_create_empty(const char * file,apr_pool_t * scratch_pool)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 *
svn_io_dir_file_copy(const char * src_path,const char * dest_path,const char * file,apr_pool_t * pool)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 
1280 /*** Modtime checking. ***/
1281 
1282 svn_error_t *
svn_io_file_affected_time(apr_time_t * apr_time,const char * path,apr_pool_t * pool)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 *
svn_io_set_file_affected_time(apr_time_t apr_time,const char * path,apr_pool_t * pool)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
svn_io_sleep_for_timestamps(const char * path,apr_pool_t * pool)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 *
svn_io_filesizes_different_p(svn_boolean_t * different_p,const char * file1,const char * file2,apr_pool_t * pool)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 *
svn_io_filesizes_three_different_p(svn_boolean_t * different_p12,svn_boolean_t * different_p23,svn_boolean_t * different_p13,const char * file1,const char * file2,const char * file3,apr_pool_t * scratch_pool)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 *
svn_io_file_checksum2(svn_checksum_t ** checksum,const char * file,svn_checksum_kind_t kind,apr_pool_t * pool)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 *
svn_io_file_checksum(unsigned char digest[],const char * file,apr_pool_t * pool)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 
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 *
reown_file(const char * path,apr_pool_t * pool)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 *
get_default_file_perms(apr_fileperms_t * perms,const char * directory,apr_pool_t * scratch_pool)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 *
merge_default_file_perms(apr_file_t * fd,apr_fileperms_t * perms,const char * directory,apr_pool_t * scratch_pool)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 *
io_set_file_perms(const char * path,svn_boolean_t change_readwrite,svn_boolean_t enable_write,svn_boolean_t change_executable,svn_boolean_t executable,svn_boolean_t ignore_enoent,apr_pool_t * pool)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*
svn_io__utf8_to_unicode_longpath(const WCHAR ** result,const char * source,apr_pool_t * result_pool)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 *
io_unicode_to_utf8_path(const char ** result,const WCHAR * source,apr_pool_t * result_pool)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 *
io_win_file_attrs_set(const char * fname,DWORD attributes,DWORD attr_mask,apr_pool_t * pool)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 
win_init_dynamic_imports(void * baton,apr_pool_t * pool)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 
io_win_read_link(svn_string_t ** dest,const char * path,apr_pool_t * pool)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
win32_set_file_information_by_handle(HANDLE hFile,int FileInformationClass,LPVOID lpFileInformation,DWORD dwBufferSize)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 *
svn_io__win_delete_file_on_close(apr_file_t * file,const char * path,apr_pool_t * pool)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 *
svn_io__win_rename_open_file(apr_file_t * file,const char * from_path,const char * to_path,apr_pool_t * pool)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 [email protected]:
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 *
svn_io_set_file_read_write_carefully(const char * path,svn_boolean_t enable_write,svn_boolean_t ignore_enoent,apr_pool_t * pool)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 *
svn_io_set_file_read_only(const char * path,svn_boolean_t ignore_enoent,apr_pool_t * pool)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 *
svn_io_set_file_read_write(const char * path,svn_boolean_t ignore_enoent,apr_pool_t * pool)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 *
svn_io_set_file_executable(const char * path,svn_boolean_t executable,svn_boolean_t ignore_enoent,apr_pool_t * pool)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 *
svn_io__is_finfo_read_only(svn_boolean_t * read_only,apr_finfo_t * file_info,apr_pool_t * pool)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 *
svn_io__is_finfo_executable(svn_boolean_t * executable,apr_finfo_t * file_info,apr_pool_t * pool)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 *
svn_io_is_file_executable(svn_boolean_t * executable,const char * path,apr_pool_t * pool)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 
2282 /*** File locking. ***/
2283 /* Clear all outstanding locks on ARG, an open apr_file_t *. */
2284 static apr_status_t
file_clear_locks(void * arg)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 *
svn_io_lock_open_file(apr_file_t * lockfile_handle,svn_boolean_t exclusive,svn_boolean_t nonblocking,apr_pool_t * pool)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 *
svn_io_unlock_open_file(apr_file_t * lockfile_handle,apr_pool_t * pool)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 *
svn_io_file_lock2(const char * lock_file,svn_boolean_t exclusive,svn_boolean_t nonblocking,apr_pool_t * pool)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 *
svn_io__file_lock_autocreate(const char * lock_file,apr_pool_t * pool)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 
2453 /* Data consistency/coherency operations. */
2454 
svn_io_file_flush_to_disk(apr_file_t * file,apr_pool_t * pool)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 
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 *
stringbuf_from_aprfile(svn_stringbuf_t ** result,const char * filename,apr_file_t * file,svn_boolean_t check_size,apr_pool_t * pool)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 *
svn_stringbuf_from_file2(svn_stringbuf_t ** result,const char * filename,apr_pool_t * pool)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 *
svn_stringbuf_from_file(svn_stringbuf_t ** result,const char * filename,apr_pool_t * pool)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 *
svn_stringbuf_from_aprfile(svn_stringbuf_t ** result,apr_file_t * file,apr_pool_t * pool)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 
2631 /* Deletion. */
2632 
2633 svn_error_t *
svn_io_remove_file2(const char * path,svn_boolean_t ignore_enoent,apr_pool_t * scratch_pool)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 *
svn_io_remove_dir(const char * path,apr_pool_t * pool)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 *
svn_io_remove_dir2(const char * path,svn_boolean_t ignore_enoent,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)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 *
svn_io_get_dir_filenames(apr_hash_t ** dirents,const char * path,apr_pool_t * pool)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 *
svn_io_dirent2_create(apr_pool_t * result_pool)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 *
svn_io_dirent2_dup(const svn_io_dirent2_t * item,apr_pool_t * result_pool)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 *
svn_io_get_dirents3(apr_hash_t ** dirents,const char * path,svn_boolean_t only_check_type,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_io_stat_dirent2(const svn_io_dirent2_t ** dirent_p,const char * path,svn_boolean_t verify_truename,svn_boolean_t ignore_enoent,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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
handle_child_process_error(apr_pool_t * pool,apr_status_t status,const char * desc)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 *
svn_io_start_cmd3(apr_proc_t * cmd_proc,const char * path,const char * cmd,const char * const * args,const char * const * env,svn_boolean_t inherit,svn_boolean_t infile_pipe,apr_file_t * infile,svn_boolean_t outfile_pipe,apr_file_t * outfile,svn_boolean_t errfile_pipe,apr_file_t * errfile,apr_pool_t * pool)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 *
svn_io_wait_for_cmd(apr_proc_t * cmd_proc,const char * cmd,int * exitcode,apr_exit_why_e * exitwhy,apr_pool_t * pool)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 *
svn_io_run_cmd(const char * path,const char * cmd,const char * const * args,int * exitcode,apr_exit_why_e * exitwhy,svn_boolean_t inherit,apr_file_t * infile,apr_file_t * outfile,apr_file_t * errfile,apr_pool_t * pool)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 *
svn_io_run_diff2(const char * dir,const char * const * user_args,int num_user_args,const char * label1,const char * label2,const char * from,const char * to,int * pexitcode,apr_file_t * outfile,apr_file_t * errfile,const char * diff_cmd,apr_pool_t * pool)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 *
svn_io_run_diff3_3(int * exitcode,const char * dir,const char * mine,const char * older,const char * yours,const char * mine_label,const char * older_label,const char * yours_label,apr_file_t * merged,const char * diff3_cmd,const apr_array_header_t * user_args,apr_pool_t * pool)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 *
fileext_tolower(char * key)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 *
svn_io_parse_mimetypes_file(apr_hash_t ** type_map,const char * mimetypes_file,apr_pool_t * pool)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 *
svn_io_detect_mimetype2(const char ** mimetype,const char * file,apr_hash_t * mimetype_map,apr_pool_t * pool)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
svn_io_is_binary_data(const void * data,apr_size_t len)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 *
svn_io_detect_mimetype(const char ** mimetype,const char * file,apr_pool_t * pool)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 *
svn_io_file_open(apr_file_t ** new_file,const char * fname,apr_int32_t flag,apr_fileperms_t perm,apr_pool_t * pool)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 *
do_io_file_wrapper_cleanup(apr_file_t * file,apr_status_t status,const char * msg,const char * msg_no_name,apr_pool_t * pool)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 *
svn_io_file_close(apr_file_t * file,apr_pool_t * pool)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 *
svn_io_file_getc(char * ch,apr_file_t * file,apr_pool_t * pool)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 *
svn_io_file_putc(char ch,apr_file_t * file,apr_pool_t * pool)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 *
svn_io_file_info_get(apr_finfo_t * finfo,apr_int32_t wanted,apr_file_t * file,apr_pool_t * pool)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 *
svn_io_file_size_get(svn_filesize_t * filesize_p,apr_file_t * file,apr_pool_t * pool)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 *
svn_io_file_get_offset(apr_off_t * offset_p,apr_file_t * file,apr_pool_t * pool)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 *
svn_io_file_read(apr_file_t * file,void * buf,apr_size_t * nbytes,apr_pool_t * pool)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 *
svn_io_file_read_full2(apr_file_t * file,void * buf,apr_size_t nbytes,apr_size_t * bytes_read,svn_boolean_t * hit_eof,apr_pool_t * pool)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 *
svn_io_file_seek(apr_file_t * file,apr_seek_where_t where,apr_off_t * offset,apr_pool_t * pool)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 *
svn_io_file_aligned_seek(apr_file_t * file,apr_off_t block_size,apr_off_t * buffer_start,apr_off_t offset,apr_pool_t * scratch_pool)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 *
svn_io_file_write(apr_file_t * file,const void * buf,apr_size_t * nbytes,apr_pool_t * pool)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 *
svn_io_file_flush(apr_file_t * file,apr_pool_t * scratch_pool)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 *
svn_io_file_write_full(apr_file_t * file,const void * buf,apr_size_t nbytes,apr_size_t * bytes_written,apr_pool_t * pool)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 *
svn_io_write_unique(const char ** tmp_path,const char * dirpath,const void * buf,apr_size_t nbytes,svn_io_file_del_t delete_when,apr_pool_t * pool)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 *
svn_io_write_atomic2(const char * final_path,const void * buf,apr_size_t nbytes,const char * copy_perms_path,svn_boolean_t flush_to_disk,apr_pool_t * scratch_pool)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 *
svn_io_file_trunc(apr_file_t * file,apr_off_t offset,apr_pool_t * pool)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 *
svn_io_read_length_line(apr_file_t * file,char * buf,apr_size_t * limit,apr_pool_t * pool)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 *
svn_io_stat(apr_finfo_t * finfo,const char * fname,apr_int32_t wanted,apr_pool_t * pool)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
win32_file_rename(const WCHAR * from_path_w,const WCHAR * to_path_w,svn_boolean_t flush_to_disk)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 *
svn_io_file_rename2(const char * from_path,const char * to_path,svn_boolean_t flush_to_disk,apr_pool_t * pool)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 *
svn_io_file_move(const char * from_path,const char * to_path,apr_pool_t * pool)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 *
dir_make(const char * path,apr_fileperms_t perm,svn_boolean_t hidden,svn_boolean_t sgid,apr_pool_t * pool)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 *
svn_io_dir_make(const char * path,apr_fileperms_t perm,apr_pool_t * pool)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 *
svn_io_dir_make_hidden(const char * path,apr_fileperms_t perm,apr_pool_t * pool)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 *
svn_io_dir_make_sgid(const char * path,apr_fileperms_t perm,apr_pool_t * pool)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 *
svn_io_dir_open(apr_dir_t ** new_dir,const char * dirname,apr_pool_t * pool)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 *
svn_io_dir_remove_nonrecursive(const char * dirname,apr_pool_t * pool)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 *
svn_io_dir_read(apr_finfo_t * finfo,apr_int32_t wanted,apr_dir_t * thedir,apr_pool_t * pool)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 *
svn_io_dir_close(apr_dir_t * thedir)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 *
svn_io_dir_walk2(const char * dirname,apr_int32_t wanted,svn_io_walk_func_t walk_func,void * walk_baton,apr_pool_t * pool)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 
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
dir_is_empty(const char * dir,apr_pool_t * pool)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 *
svn_io_dir_empty(svn_boolean_t * is_empty_p,const char * path,apr_pool_t * pool)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 
4743 /*** Version/format files ***/
4744 
4745 svn_error_t *
svn_io_write_version_file(const char * path,int version,apr_pool_t * pool)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 *
svn_io_read_version_file(int * version,const char * path,apr_pool_t * pool)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 *
contents_identical_p(svn_boolean_t * identical_p,const char * file1,const char * file2,apr_pool_t * pool)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 *
contents_three_identical_p(svn_boolean_t * identical_p12,svn_boolean_t * identical_p23,svn_boolean_t * identical_p13,const char * file1,const char * file2,const char * file3,apr_pool_t * scratch_pool)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 *
svn_io_files_contents_same_p(svn_boolean_t * same,const char * file1,const char * file2,apr_pool_t * pool)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 *
svn_io_files_contents_three_same_p(svn_boolean_t * same12,svn_boolean_t * same23,svn_boolean_t * same13,const char * file1,const char * file2,const char * file3,apr_pool_t * scratch_pool)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 *
temp_file_create(apr_file_t ** new_file,const char ** new_file_name,const char * directory,apr_int32_t flags,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_io_file_name_get(const char ** filename,apr_file_t * file,apr_pool_t * pool)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 *
svn_io_open_unique_file3(apr_file_t ** file,const char ** unique_path,const char * dirpath,svn_io_file_del_t delete_when,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_io_file_readline(apr_file_t * file,svn_stringbuf_t ** stringbuf,const char ** eol,svn_boolean_t * eof,apr_size_t max_len,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 }
5443