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, ¤t, 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