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