Success build TortoiseMerge.
[TortoiseGit.git] / src / TortoiseMerge / libsvn_diff / io.c
blob2fb3936fc52205872d5552dce64938fe0f43a2fb
1 /*
2 * io.c: shared file reading, writing, and probing code.
4 * ====================================================================
5 * Copyright (c) 2000-2008 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <stdio.h>
23 #ifndef WIN32
24 #include <unistd.h>
25 #endif
27 #ifndef APR_STATUS_IS_EPERM
28 #include <errno.h>
29 #ifdef EPERM
30 #define APR_STATUS_IS_EPERM(s) ((s) == EPERM)
31 #else
32 #define APR_STATUS_IS_EPERM(s) (0)
33 #endif
34 #endif
36 #include <apr_lib.h>
37 #include <apr_pools.h>
38 #include <apr_file_io.h>
39 #include <apr_file_info.h>
40 #include <apr_general.h>
41 #include <apr_strings.h>
42 #include <apr_portable.h>
43 #include <apr_md5.h>
45 #include "svn_types.h"
46 #include "svn_path.h"
47 #include "svn_string.h"
48 #include "svn_error.h"
49 #include "svn_io.h"
50 #include "svn_pools.h"
51 #include "svn_utf.h"
52 #include "svn_config.h"
53 //#include "svn_private_config.h"
55 #define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
58 Windows is 'aided' by a number of types of applications that
59 follow other applications around and open up files they have
60 changed for various reasons (the most intrusive are virus
61 scanners). So, if one of these other apps has glommed onto
62 our file we may get an 'access denied' error.
64 This retry loop does not completely solve the problem (who
65 knows how long the other app is going to hold onto it for), but
66 goes a long way towards minimizing it. It is not an infinite
67 loop because there might really be an error.
69 Another reason for retrying delete operations on Windows
70 is that they are asynchronous -- the file or directory is not
71 actually deleted until the last handle to it is closed. The
72 retry loop cannot completely solve this problem either, but can
73 help mitigate it.
75 #ifdef WIN32
76 #define WIN32_RETRY_LOOP(err, expr) \
77 do \
78 { \
79 apr_status_t os_err = APR_TO_OS_ERROR(err); \
80 int sleep_count = 1000; \
81 int retries; \
82 for (retries = 0; \
83 retries < 100 && (os_err == ERROR_ACCESS_DENIED \
84 || os_err == ERROR_SHARING_VIOLATION \
85 || os_err == ERROR_DIR_NOT_EMPTY); \
86 ++retries, os_err = APR_TO_OS_ERROR(err)) \
87 { \
88 apr_sleep(sleep_count); \
89 if (sleep_count < 128000) \
90 sleep_count *= 2; \
91 (err) = (expr); \
92 } \
93 } \
94 while (0)
95 #else
96 #define WIN32_RETRY_LOOP(err, expr) ((void)0)
97 #endif
99 /* Local wrapper of svn_path_cstring_to_utf8() that does no copying on
100 * operating systems where APR always uses utf-8 as native path format */
101 static svn_error_t *
102 cstring_to_utf8(const char **path_utf8,
103 const char *path_apr,
104 apr_pool_t *pool)
106 #if defined(WIN32) || defined(DARWIN)
107 pool; /* Unused */
108 *path_utf8 = path_apr;
109 return SVN_NO_ERROR;
110 #else
111 return svn_path_cstring_to_utf8(path_utf8, path_apr, pool);
112 #endif
115 /* Local wrapper of svn_path_cstring_from_utf8() that does no copying on
116 * operating systems where APR always uses utf-8 as native path format */
117 static svn_error_t *
118 cstring_from_utf8(const char **path_apr,
119 const char *path_utf8,
120 apr_pool_t *pool)
122 #if defined(WIN32) || defined(DARWIN)
123 pool; /* Unused */
124 *path_apr = path_utf8;
125 return SVN_NO_ERROR;
126 #else
127 return svn_path_cstring_from_utf8(path_apr, path_utf8, pool);
128 #endif
132 /* Set *NAME_P to the UTF-8 representation of directory entry NAME.
133 * NAME is in the the internal encoding used by APR; PARENT is in
134 * UTF-8 and in internal (not local) style.
136 * Use PARENT only for generating an error string if the conversion
137 * fails because NAME could not be represented in UTF-8. In that
138 * case, return a two-level error in which the outer error's message
139 * mentions PARENT, but the inner error's message does not mention
140 * NAME (except possibly in hex) since NAME may not be printable.
141 * Such a compound error at least allows the user to go looking in the
142 * right directory for the problem.
144 * If there is any other error, just return that error directly.
146 * If there is any error, the effect on *NAME_P is undefined.
148 * *NAME_P and NAME may refer to the same storage.
150 static svn_error_t *
151 entry_name_to_utf8(const char **name_p,
152 const char *name,
153 const char *parent,
154 apr_pool_t *pool)
156 svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
157 if (err && err->apr_err == APR_EINVAL)
159 return svn_error_createf(err->apr_err, err,
160 _("Error converting entry "
161 "in directory '%s' to UTF-8"),
162 svn_path_local_style(parent, pool));
164 return err;
169 static void
170 map_apr_finfo_to_node_kind(svn_node_kind_t *kind,
171 svn_boolean_t *is_special,
172 apr_finfo_t *finfo)
174 *is_special = FALSE;
176 if (finfo->filetype == APR_REG)
177 *kind = svn_node_file;
178 else if (finfo->filetype == APR_DIR)
179 *kind = svn_node_dir;
180 else if (finfo->filetype == APR_LNK)
182 *is_special = TRUE;
183 *kind = svn_node_file;
185 else
186 *kind = svn_node_unknown;
189 /* Helper for svn_io_check_path() and svn_io_check_resolved_path();
190 essentially the same semantics as those two, with the obvious
191 interpretation for RESOLVE_SYMLINKS. */
192 static svn_error_t *
193 io_check_path(const char *path,
194 svn_boolean_t resolve_symlinks,
195 svn_boolean_t *is_special_p,
196 svn_node_kind_t *kind,
197 apr_pool_t *pool)
199 apr_int32_t flags;
200 apr_finfo_t finfo;
201 apr_status_t apr_err;
202 const char *path_apr;
203 svn_boolean_t is_special = FALSE;
205 if (path[0] == '\0')
206 path = ".";
208 /* Not using svn_io_stat() here because we want to check the
209 apr_err return explicitly. */
210 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
212 flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
213 apr_err = apr_stat(&finfo, path_apr, flags, pool);
215 if (APR_STATUS_IS_ENOENT(apr_err))
216 *kind = svn_node_none;
217 else if (APR_STATUS_IS_ENOTDIR(apr_err))
218 *kind = svn_node_none;
219 else if (apr_err)
220 return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"),
221 svn_path_local_style(path, pool));
222 else
223 map_apr_finfo_to_node_kind(kind, &is_special, &finfo);
225 *is_special_p = is_special;
227 return SVN_NO_ERROR;
231 /* Wrapper for apr_file_open(). */
232 static apr_status_t
233 file_open(apr_file_t **f,
234 const char *fname,
235 apr_int32_t flag,
236 apr_fileperms_t perm,
237 svn_boolean_t retry_on_failure,
238 apr_pool_t *pool)
240 apr_status_t status = apr_file_open(f, fname, flag, perm, pool);
242 if (retry_on_failure)
244 WIN32_RETRY_LOOP(status, apr_file_open(f, fname, flag, perm, pool));
246 return status;
250 svn_error_t *
251 svn_io_check_resolved_path(const char *path,
252 svn_node_kind_t *kind,
253 apr_pool_t *pool)
255 svn_boolean_t ignored;
256 return io_check_path(path, TRUE, &ignored, kind, pool);
259 svn_error_t *
260 svn_io_check_path(const char *path,
261 svn_node_kind_t *kind,
262 apr_pool_t *pool)
264 svn_boolean_t ignored;
265 return io_check_path(path, FALSE, &ignored, kind, pool);
268 svn_error_t *
269 svn_io_check_special_path(const char *path,
270 svn_node_kind_t *kind,
271 svn_boolean_t *is_special,
272 apr_pool_t *pool)
274 return io_check_path(path, FALSE, is_special, kind, pool);
277 struct temp_file_cleanup_s
279 apr_pool_t *pool;
280 const char *name;
284 static apr_status_t
285 temp_file_plain_cleanup_handler(void *baton)
287 struct temp_file_cleanup_s *b = baton;
288 apr_status_t apr_err = APR_SUCCESS;
290 if (b->name)
292 apr_err = apr_file_remove(b->name, b->pool);
293 WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->name, b->pool));
296 return apr_err;
300 static apr_status_t
301 temp_file_child_cleanup_handler(void *baton)
303 struct temp_file_cleanup_s *b = baton;
305 apr_pool_cleanup_kill(b->pool, b,
306 temp_file_plain_cleanup_handler);
308 return APR_SUCCESS;
312 svn_error_t *
313 svn_io_open_uniquely_named(apr_file_t **file,
314 const char **unique_path,
315 const char *dirpath,
316 const char *filename,
317 const char *suffix,
318 svn_io_file_del_t delete_when,
319 apr_pool_t *result_pool,
320 apr_pool_t *scratch_pool)
322 const char *path;
323 unsigned int i;
324 struct temp_file_cleanup_s *baton = NULL;
326 SVN_ERR_ASSERT(file || unique_path);
328 if (dirpath == NULL)
329 SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
330 if (filename == NULL)
331 filename = "tempfile";
332 if (suffix == NULL)
333 suffix = ".tmp";
335 path = svn_path_join(dirpath, filename, scratch_pool);
337 if (delete_when == svn_io_file_del_on_pool_cleanup)
339 baton = apr_palloc(result_pool, sizeof(*baton));
341 baton->pool = result_pool;
342 baton->name = NULL;
344 /* Because cleanups are run LIFO, we need to make sure to register
345 our cleanup before the apr_file_close cleanup:
347 On Windows, you can't remove an open file.
349 apr_pool_cleanup_register(result_pool, baton,
350 temp_file_plain_cleanup_handler,
351 temp_file_child_cleanup_handler);
354 for (i = 1; i <= 99999; i++)
356 const char *unique_name;
357 const char *unique_name_apr;
358 apr_file_t *try_file;
359 apr_status_t apr_err;
360 apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL
361 | APR_BUFFERED | APR_BINARY);
363 if (delete_when == svn_io_file_del_on_close)
364 flag |= APR_DELONCLOSE;
366 /* Special case the first attempt -- if we can avoid having a
367 generated numeric portion at all, that's best. So first we
368 try with just the suffix; then future tries add a number
369 before the suffix. (A do-while loop could avoid the repeated
370 conditional, but it's not worth the clarity loss.)
372 If the first attempt fails, the first number will be "2".
373 This is good, since "1" would misleadingly imply that
374 the second attempt was actually the first... and if someone's
375 got conflicts on their conflicts, we probably don't want to
376 add to their confusion :-). */
377 /* ### could alloc in scratch, then copy to result.. but "meh" */
378 if (i == 1)
379 unique_name = apr_psprintf(result_pool, "%s%s", path, suffix);
380 else
381 unique_name = apr_psprintf(result_pool, "%s.%u%s", path, i, suffix);
383 /* Hmmm. Ideally, we would append to a native-encoding buf
384 before starting iteration, then convert back to UTF-8 for
385 return. But I suppose that would make the appending code
386 sensitive to i18n in a way it shouldn't be... Oh well. */
387 /* ### could alloc in scratch, then copy to result in baton... */
388 SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, result_pool));
390 apr_err = file_open(&try_file, unique_name_apr, flag,
391 APR_OS_DEFAULT, FALSE, result_pool);
393 if (APR_STATUS_IS_EEXIST(apr_err))
394 continue;
395 else if (apr_err)
397 /* On Win32, CreateFile fails with an "Access Denied" error
398 code, rather than "File Already Exists", if the colliding
399 name belongs to a directory. */
400 if (APR_STATUS_IS_EACCES(apr_err))
402 apr_finfo_t finfo;
403 apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
404 APR_FINFO_TYPE, scratch_pool);
406 if (!apr_err_2 && finfo.filetype == APR_DIR)
407 continue;
409 #if WIN32
410 apr_err_2 = APR_TO_OS_ERROR(apr_err);
412 if (apr_err_2 == ERROR_ACCESS_DENIED ||
413 apr_err_2 == ERROR_SHARING_VIOLATION)
415 /* The file is in use by another process or is hidden;
416 create a new name, but don't do this 99999 times in
417 case the folder is not writable */
418 i += 797;
419 continue;
421 #endif
423 /* Else fall through and return the original error. */
426 if (file) *file = NULL;
427 if (unique_path) *unique_path = NULL;
428 return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
429 svn_path_local_style(unique_name,
430 scratch_pool));
432 else
434 if (delete_when == svn_io_file_del_on_pool_cleanup)
435 baton->name = unique_name_apr;
437 if (file)
438 *file = try_file;
439 else
440 apr_file_close(try_file);
441 if (unique_path) *unique_path = unique_name;
443 return SVN_NO_ERROR;
447 if (file) *file = NULL;
448 if (unique_path) *unique_path = NULL;
449 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
450 NULL,
451 _("Unable to make name for '%s'"),
452 svn_path_local_style(path, scratch_pool));
455 svn_error_t *
456 svn_io_open_unique_file3(apr_file_t **file,
457 const char **temp_path,
458 const char *dirpath,
459 svn_io_file_del_t delete_when,
460 apr_pool_t *result_pool,
461 apr_pool_t *scratch_pool)
463 return svn_io_open_uniquely_named(file, temp_path,
464 dirpath, "tempfile", ".tmp",
465 delete_when, result_pool, scratch_pool);
468 svn_error_t *
469 svn_io_create_unique_link(const char **unique_name_p,
470 const char *path,
471 const char *dest,
472 const char *suffix,
473 apr_pool_t *pool)
475 #ifdef HAVE_SYMLINK
476 unsigned int i;
477 const char *unique_name;
478 const char *unique_name_apr;
479 const char *dest_apr;
480 int rv;
482 SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool));
483 for (i = 1; i <= 99999; i++)
485 apr_status_t apr_err;
487 /* Special case the first attempt -- if we can avoid having a
488 generated numeric portion at all, that's best. So first we
489 try with just the suffix; then future tries add a number
490 before the suffix. (A do-while loop could avoid the repeated
491 conditional, but it's not worth the clarity loss.)
493 If the first attempt fails, the first number will be "2".
494 This is good, since "1" would misleadingly imply that
495 the second attempt was actually the first... and if someone's
496 got conflicts on their conflicts, we probably don't want to
497 add to their confusion :-). */
498 if (i == 1)
499 unique_name = apr_psprintf(pool, "%s%s", path, suffix);
500 else
501 unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
503 /* Hmmm. Ideally, we would append to a native-encoding buf
504 before starting iteration, then convert back to UTF-8 for
505 return. But I suppose that would make the appending code
506 sensitive to i18n in a way it shouldn't be... Oh well. */
507 SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool));
508 do {
509 rv = symlink(dest_apr, unique_name_apr);
510 } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
512 apr_err = apr_get_os_error();
514 if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err))
515 continue;
516 else if (rv == -1 && apr_err)
518 /* On Win32, CreateFile fails with an "Access Denied" error
519 code, rather than "File Already Exists", if the colliding
520 name belongs to a directory. */
521 if (APR_STATUS_IS_EACCES(apr_err))
523 apr_finfo_t finfo;
524 apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
525 APR_FINFO_TYPE, pool);
527 if (!apr_err_2
528 && (finfo.filetype == APR_DIR))
529 continue;
531 /* Else ignore apr_err_2; better to fall through and
532 return the original error. */
535 *unique_name_p = NULL;
536 return svn_error_wrap_apr(apr_err,
537 _("Can't create symbolic link '%s'"),
538 svn_path_local_style(unique_name, pool));
540 else
542 *unique_name_p = unique_name;
543 return SVN_NO_ERROR;
547 *unique_name_p = NULL;
548 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
549 NULL,
550 _("Unable to make name for '%s'"),
551 svn_path_local_style(path, pool));
552 #else
553 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
554 _("Symbolic links are not supported on this "
555 "platform"));
556 #endif
559 svn_error_t *
560 svn_io_read_link(svn_string_t **dest,
561 const char *path,
562 apr_pool_t *pool)
564 #ifdef HAVE_READLINK
565 svn_string_t dest_apr;
566 const char *path_apr;
567 char buf[1025];
568 int rv;
570 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
571 do {
572 rv = readlink(path_apr, buf, sizeof(buf) - 1);
573 } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
575 if (rv == -1)
576 return svn_error_wrap_apr
577 (apr_get_os_error(), _("Can't read contents of link"));
579 buf[rv] = '\0';
580 dest_apr.data = buf;
581 dest_apr.len = rv;
583 /* ### Cast needed, one of these interfaces is wrong */
584 return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool);
585 #else
586 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
587 _("Symbolic links are not supported on this "
588 "platform"));
589 #endif
593 svn_error_t *
594 svn_io_copy_link(const char *src,
595 const char *dst,
596 apr_pool_t *pool)
599 #ifdef HAVE_READLINK
600 svn_string_t *link_dest;
601 const char *dst_tmp;
603 /* Notice what the link is pointing at... */
604 SVN_ERR(svn_io_read_link(&link_dest, src, pool));
606 /* Make a tmp-link pointing at the same thing. */
607 SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data,
608 ".tmp", pool));
610 /* Move the tmp-link to link. */
611 return svn_io_file_rename(dst_tmp, dst, pool);
613 #else
614 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
615 _("Symbolic links are not supported on this "
616 "platform"));
617 #endif
621 svn_error_t *
622 svn_io_temp_dir(const char **dir,
623 apr_pool_t *pool)
625 apr_status_t apr_err = apr_temp_dir_get(dir, pool);
627 if (apr_err)
628 return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory"));
630 SVN_ERR(cstring_to_utf8(dir, *dir, pool));
632 *dir = svn_path_canonicalize(*dir, pool);
634 return SVN_NO_ERROR;
640 /*** Creating, copying and appending files. ***/
642 /* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary
643 * allocations.
645 * NOTE: We don't use apr_copy_file() for this, since it takes filenames
646 * as parameters. Since we want to copy to a temporary file
647 * and rename for atomicity (see below), this would require an extra
648 * close/open pair, which can be expensive, especially on
649 * remote file systems.
651 static apr_status_t
652 copy_contents(apr_file_t *from_file,
653 apr_file_t *to_file,
654 apr_pool_t *pool)
656 /* Copy bytes till the cows come home. */
657 while (1)
659 char buf[SVN__STREAM_CHUNK_SIZE];
660 apr_size_t bytes_this_time = sizeof(buf);
661 apr_status_t read_err;
662 apr_status_t write_err;
664 /* Read 'em. */
665 read_err = apr_file_read(from_file, buf, &bytes_this_time);
666 if (read_err && !APR_STATUS_IS_EOF(read_err))
668 return read_err;
671 /* Write 'em. */
672 write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL);
673 if (write_err)
675 return write_err;
678 if (read_err && APR_STATUS_IS_EOF(read_err))
680 /* Return the results of this close: an error, or success. */
681 return APR_SUCCESS;
684 /* NOTREACHED */
688 svn_error_t *
689 svn_io_copy_file(const char *src,
690 const char *dst,
691 svn_boolean_t copy_perms,
692 apr_pool_t *pool)
694 apr_file_t *from_file, *to_file;
695 apr_status_t apr_err;
696 const char *src_apr, *dst_tmp_apr;
697 const char *dst_tmp;
698 svn_error_t *err, *err2;
700 /* ### NOTE: sometimes src == dst. In this case, because we copy to a
701 ### temporary file, and then rename over the top of the destination,
702 ### the net result is resetting the permissions on src/dst.
704 ### Note: specifically, this can happen during a switch when the desired
705 ### permissions for a file change from one branch to another. See
706 ### switch_tests 17.
708 ### ... yes, we should avoid copying to the same file, and we should
709 ### make the "reset perms" explicit. The switch *happens* to work
710 ### because of this copy-to-temp-then-rename implementation. If it
711 ### weren't for that, the switch would break.
713 #ifdef CHECK_FOR_SAME_FILE
714 if (strcmp(src, dst) == 0)
715 return SVN_NO_ERROR;
716 #endif
718 SVN_ERR(cstring_from_utf8(&src_apr, src, pool));
720 SVN_ERR(svn_io_file_open(&from_file, src, APR_READ | APR_BINARY,
721 APR_OS_DEFAULT, pool));
723 /* For atomicity, we copy to a tmp file and then rename the tmp
724 file over the real destination. */
726 SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp,
727 svn_path_dirname(dst, pool),
728 svn_io_file_del_none, pool, pool));
729 SVN_ERR(cstring_from_utf8(&dst_tmp_apr, dst_tmp, pool));
731 apr_err = copy_contents(from_file, to_file, pool);
733 if (apr_err)
735 err = svn_error_wrap_apr
736 (apr_err, _("Can't copy '%s' to '%s'"),
737 svn_path_local_style(src, pool),
738 svn_path_local_style(dst_tmp, pool));
740 else
741 err = NULL;
743 err2 = svn_io_file_close(from_file, pool);
744 if (! err)
745 err = err2;
746 else
747 svn_error_clear(err2);
748 err2 = svn_io_file_close(to_file, pool);
749 if (! err)
750 err = err2;
751 else
752 svn_error_clear(err2);
753 if (err)
755 apr_err = apr_file_remove(dst_tmp_apr, pool);
756 WIN32_RETRY_LOOP(apr_err, apr_file_remove(dst_tmp_apr, pool));
757 return err;
760 /* If copying perms, set the perms on dst_tmp now, so they will be
761 atomically inherited in the upcoming rename. But note that we
762 had to wait until now to set perms, because if they say
763 read-only, then we'd have failed filling dst_tmp's contents. */
764 if (copy_perms)
765 SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool));
767 return svn_io_file_rename(dst_tmp, dst, pool);
771 svn_error_t *
772 svn_io_copy_perms(const char *src,
773 const char *dst,
774 apr_pool_t *pool)
776 /* ### FIXME: apr_file_copy with perms may fail on Win32. We need a
777 platform-specific implementation to get the permissions right. */
779 #ifndef WIN32
781 apr_file_t *src_file;
782 apr_finfo_t finfo;
783 const char *dst_apr;
784 apr_status_t apr_err;
786 SVN_ERR(svn_io_file_open(&src_file, src, APR_READ, APR_OS_DEFAULT, pool));
787 SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, src_file, pool));
788 SVN_ERR(svn_io_file_close(src_file, pool));
790 SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool));
791 apr_err = apr_file_perms_set(dst_apr, finfo.protection);
793 /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL
794 here under normal circumstances, because the perms themselves
795 came from a call to apr_file_info_get(), and we already know
796 this is the non-Win32 case. But if it does happen, it's not
797 an error. */
798 if (apr_err != APR_SUCCESS
799 && apr_err != APR_INCOMPLETE
800 && apr_err != APR_ENOTIMPL)
802 return svn_error_wrap_apr(apr_err, _("Can't set permissions on '%s'"),
803 svn_path_local_style(dst, pool));
806 #endif /* ! WIN32 */
808 return SVN_NO_ERROR;
812 svn_error_t *
813 svn_io_append_file(const char *src, const char *dst, apr_pool_t *pool)
815 apr_status_t apr_err;
816 const char *src_apr, *dst_apr;
818 SVN_ERR(cstring_from_utf8(&src_apr, src, pool));
819 SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool));
821 apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool);
823 if (apr_err)
824 return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"),
825 svn_path_local_style(src, pool),
826 svn_path_local_style(dst, pool));
828 return SVN_NO_ERROR;
832 svn_error_t *svn_io_copy_dir_recursively(const char *src,
833 const char *dst_parent,
834 const char *dst_basename,
835 svn_boolean_t copy_perms,
836 svn_cancel_func_t cancel_func,
837 void *cancel_baton,
838 apr_pool_t *pool)
840 svn_node_kind_t kind;
841 apr_status_t status;
842 const char *dst_path;
843 apr_dir_t *this_dir;
844 apr_finfo_t this_entry;
845 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
847 /* Make a subpool for recursion */
848 apr_pool_t *subpool = svn_pool_create(pool);
850 /* The 'dst_path' is simply dst_parent/dst_basename */
851 dst_path = svn_path_join(dst_parent, dst_basename, pool);
853 /* Sanity checks: SRC and DST_PARENT are directories, and
854 DST_BASENAME doesn't already exist in DST_PARENT. */
855 SVN_ERR(svn_io_check_path(src, &kind, subpool));
856 if (kind != svn_node_dir)
857 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
858 _("Source '%s' is not a directory"),
859 svn_path_local_style(src, pool));
861 SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
862 if (kind != svn_node_dir)
863 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
864 _("Destination '%s' is not a directory"),
865 svn_path_local_style(dst_parent, pool));
867 SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
868 if (kind != svn_node_none)
869 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
870 _("Destination '%s' already exists"),
871 svn_path_local_style(dst_path, pool));
873 /* Create the new directory. */
874 /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
875 SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool));
877 /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
878 SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
880 for (status = apr_dir_read(&this_entry, flags, this_dir);
881 status == APR_SUCCESS;
882 status = apr_dir_read(&this_entry, flags, this_dir))
884 if ((this_entry.name[0] == '.')
885 && ((this_entry.name[1] == '\0')
886 || ((this_entry.name[1] == '.')
887 && (this_entry.name[2] == '\0'))))
889 continue;
891 else
893 const char *src_target, *entryname_utf8;
895 if (cancel_func)
896 SVN_ERR(cancel_func(cancel_baton));
898 SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
899 src, subpool));
900 src_target = svn_path_join(src, entryname_utf8, subpool);
902 if (this_entry.filetype == APR_REG) /* regular file */
904 const char *dst_target = svn_path_join(dst_path, entryname_utf8,
905 subpool);
906 SVN_ERR(svn_io_copy_file(src_target, dst_target,
907 copy_perms, subpool));
909 else if (this_entry.filetype == APR_LNK) /* symlink */
911 const char *dst_target = svn_path_join(dst_path, entryname_utf8,
912 subpool);
913 SVN_ERR(svn_io_copy_link(src_target, dst_target,
914 subpool));
916 else if (this_entry.filetype == APR_DIR) /* recurse */
918 /* Prevent infinite recursion by filtering off our
919 newly created destination path. */
920 if (strcmp(src, dst_parent) == 0
921 && strcmp(entryname_utf8, dst_basename) == 0)
922 continue;
924 SVN_ERR(svn_io_copy_dir_recursively
925 (src_target,
926 dst_path,
927 entryname_utf8,
928 copy_perms,
929 cancel_func,
930 cancel_baton,
931 subpool));
933 /* ### support other APR node types someday?? */
938 if (! (APR_STATUS_IS_ENOENT(status)))
939 return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
940 svn_path_local_style(src, pool));
942 status = apr_dir_close(this_dir);
943 if (status)
944 return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
945 svn_path_local_style(src, pool));
947 /* Free any memory used by recursion */
948 svn_pool_destroy(subpool);
950 return SVN_NO_ERROR;
954 svn_error_t *
955 svn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
957 const char *path_apr;
958 apr_status_t apr_err;
960 if (svn_path_is_empty(path))
961 /* Empty path (current dir) is assumed to always exist,
962 so we do nothing, per docs. */
963 return SVN_NO_ERROR;
965 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
967 apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool);
968 WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr,
969 APR_OS_DEFAULT, pool));
971 if (apr_err)
972 return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"),
973 svn_path_local_style(path, pool));
975 return SVN_NO_ERROR;
978 svn_error_t *svn_io_file_create(const char *file,
979 const char *contents,
980 apr_pool_t *pool)
982 apr_file_t *f;
983 apr_size_t written;
985 SVN_ERR(svn_io_file_open(&f, file,
986 (APR_WRITE | APR_CREATE | APR_EXCL),
987 APR_OS_DEFAULT,
988 pool));
989 SVN_ERR(svn_io_file_write_full(f, contents, strlen(contents),
990 &written, pool));
991 return svn_io_file_close(f, pool);
994 svn_error_t *svn_io_dir_file_copy(const char *src_path,
995 const char *dest_path,
996 const char *file,
997 apr_pool_t *pool)
999 const char *file_dest_path = svn_path_join(dest_path, file, pool);
1000 const char *file_src_path = svn_path_join(src_path, file, pool);
1002 return svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool);
1006 /*** Modtime checking. ***/
1008 svn_error_t *
1009 svn_io_file_affected_time(apr_time_t *apr_time,
1010 const char *path,
1011 apr_pool_t *pool)
1013 apr_finfo_t finfo;
1015 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1017 *apr_time = finfo.mtime;
1019 return SVN_NO_ERROR;
1023 svn_error_t *
1024 svn_io_set_file_affected_time(apr_time_t apr_time,
1025 const char *path,
1026 apr_pool_t *pool)
1028 apr_status_t status;
1029 const char *native_path;
1031 SVN_ERR(cstring_from_utf8(&native_path, path, pool));
1032 status = apr_file_mtime_set(native_path, apr_time, pool);
1034 if (status)
1035 return svn_error_wrap_apr
1036 (status, _("Can't set access time of '%s'"),
1037 svn_path_local_style(path, pool));
1039 return SVN_NO_ERROR;
1043 void
1044 svn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool)
1046 apr_time_t now, then;
1047 svn_error_t *err;
1048 char *sleep_env_var;
1050 sleep_env_var = getenv(SVN_SLEEP_ENV_VAR);
1052 if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0)
1053 return; /* Allow skipping for testing */
1055 now = apr_time_now();
1057 /* Calculate 0.02 seconds after the next second wallclock tick. */
1058 then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50);
1060 /* Worst case is waiting one second, so we can use that time to determine
1061 if we can sleep shorter than that */
1062 if (path)
1064 apr_finfo_t finfo;
1066 err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool);
1068 if (err)
1070 svn_error_clear(err); /* Fall back on original behavior */
1072 else if (finfo.mtime % APR_USEC_PER_SEC)
1074 /* Very simplistic but safe approach:
1075 If the filesystem has < sec mtime we can be reasonably sure
1076 that the filesystem has <= millisecond precision.
1078 ## Perhaps find a better algorithm here. This will fail once
1079 in every 1000 cases on a millisecond precision filesystem.
1081 But better to fail once in every thousand cases than every
1082 time, like we did before.
1083 (All tested filesystems I know have at least microsecond precision.)
1085 Note for further research on algorithm:
1086 FAT32 has < 1 sec precision on ctime, but 2 sec on mtime */
1088 /* Sleep for at least 1 millisecond.
1089 (t < 1000 will be round to 0 in apr) */
1090 apr_sleep(1000);
1092 return;
1095 now = apr_time_now(); /* Extract the time used for the path stat */
1097 if (now >= then)
1098 return; /* Passing negative values may suspend indefinately (Windows) */
1101 apr_sleep(then - now);
1105 svn_error_t *
1106 svn_io_filesizes_different_p(svn_boolean_t *different_p,
1107 const char *file1,
1108 const char *file2,
1109 apr_pool_t *pool)
1111 apr_finfo_t finfo1;
1112 apr_finfo_t finfo2;
1113 apr_status_t status;
1114 const char *file1_apr, *file2_apr;
1116 /* Not using svn_io_stat() because don't want to generate
1117 svn_error_t objects for non-error conditions. */
1119 SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool));
1120 SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool));
1122 /* Stat both files */
1123 status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool);
1124 if (status)
1126 /* If we got an error stat'ing a file, it could be because the
1127 file was removed... or who knows. Whatever the case, we
1128 don't know if the filesizes are definitely different, so
1129 assume that they're not. */
1130 *different_p = FALSE;
1131 return SVN_NO_ERROR;
1134 status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool);
1135 if (status)
1137 /* See previous comment. */
1138 *different_p = FALSE;
1139 return SVN_NO_ERROR;
1142 /* Examine file sizes */
1143 if (finfo1.size == finfo2.size)
1144 *different_p = FALSE;
1145 else
1146 *different_p = TRUE;
1148 return SVN_NO_ERROR;
1152 svn_error_t *
1153 svn_io_file_checksum2(svn_checksum_t **checksum,
1154 const char *file,
1155 svn_checksum_kind_t kind,
1156 apr_pool_t *pool)
1158 svn_stream_t *file_stream;
1159 svn_stream_t *checksum_stream;
1160 apr_file_t* f;
1162 SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool));
1163 file_stream = svn_stream_from_aprfile2(f, FALSE, pool);
1164 checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind,
1165 TRUE, pool);
1167 /* Because the checksummed stream will force the reading (and
1168 checksumming) of all the file's bytes, we can just close the stream
1169 and let its magic work. */
1170 return svn_stream_close(checksum_stream);
1174 svn_error_t *
1175 svn_io_file_checksum(unsigned char digest[],
1176 const char *file,
1177 apr_pool_t *pool)
1179 svn_checksum_t *checksum;
1181 SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool));
1182 memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE);
1184 return SVN_NO_ERROR;
1189 /*** Permissions and modes. ***/
1191 #ifndef WIN32
1192 /* Given the file specified by PATH, attempt to create an
1193 identical version of it owned by the current user. This is done by
1194 moving it to a temporary location, copying the file back to its old
1195 path, then deleting the temporarily moved version. All temporary
1196 allocations are done in POOL. */
1197 static svn_error_t *
1198 reown_file(const char *path,
1199 apr_pool_t *pool)
1201 const char *unique_name;
1203 SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name,
1204 svn_path_dirname(path, pool),
1205 svn_io_file_del_none, pool, pool));
1206 SVN_ERR(svn_io_file_rename(path, unique_name, pool));
1207 SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool));
1208 return svn_io_remove_file(unique_name, pool);
1211 /* Determine what the read-write PERMS for PATH should be by ORing
1212 together the permissions of PATH and the permissions of a temporary
1213 file that we create. Unfortunately, this is the only way to
1214 determine which combination of write bits (User/Group/World) should
1215 be set to restore a file from read-only to read-write. Make
1216 temporary allocations in POOL. */
1217 static svn_error_t *
1218 get_default_file_perms(const char *path, apr_fileperms_t *perms,
1219 apr_pool_t *pool)
1221 apr_status_t status;
1222 apr_finfo_t tmp_finfo, finfo;
1223 apr_file_t *fd;
1224 const char *tmp_path;
1225 const char *apr_path;
1227 /* Get the perms for a newly created file to find out what write
1228 bits should be set.
1230 NOTE: normally del_on_close can be problematic because APR might
1231 delete the file if we spawned any child processes. In this case,
1232 the lifetime of this file handle is about 3 lines of code, so
1233 we can safely use del_on_close here.
1235 NOTE: not so fast, shorty. if some other thread forks off a child,
1236 then the APR cleanups run, and the file will disappear. sigh.
1238 SVN_ERR(svn_io_open_unique_file3(&fd, &tmp_path,
1239 svn_path_dirname(path, pool),
1240 svn_io_file_del_on_pool_cleanup,
1241 pool, pool));
1242 status = apr_stat(&tmp_finfo, tmp_path, APR_FINFO_PROT, pool);
1243 if (status)
1244 return svn_error_wrap_apr(status, _("Can't get default file perms "
1245 "for file at '%s' (file stat error)"),
1246 path);
1247 apr_file_close(fd);
1249 /* Get the perms for the original file so we'll have any other bits
1250 * that were already set (like the execute bits, for example). */
1251 SVN_ERR(cstring_from_utf8(&apr_path, path, pool));
1252 status = apr_file_open(&fd, apr_path, APR_READ | APR_BINARY,
1253 APR_OS_DEFAULT, pool);
1254 if (status)
1255 return svn_error_wrap_apr(status, _("Can't open file at '%s'"), path);
1257 status = apr_stat(&finfo, apr_path, APR_FINFO_PROT, pool);
1258 if (status)
1259 return svn_error_wrap_apr(status, _("Can't get file perms for file at "
1260 "'%s' (file stat error)"), path);
1261 apr_file_close(fd);
1263 /* Glom the perms together. */
1264 *perms = tmp_finfo.protection | finfo.protection;
1265 return SVN_NO_ERROR;
1268 /* This is a helper function for the svn_io_set_file_read* functions
1269 that attempts to honor the users umask when dealing with
1270 permission changes. It is a no-op when invoked on a symlink. */
1271 static svn_error_t *
1272 io_set_file_perms(const char *path,
1273 svn_boolean_t change_readwrite,
1274 svn_boolean_t enable_write,
1275 svn_boolean_t change_executable,
1276 svn_boolean_t executable,
1277 svn_boolean_t ignore_enoent,
1278 apr_pool_t *pool)
1280 apr_status_t status;
1281 const char *path_apr;
1282 apr_finfo_t finfo;
1283 apr_fileperms_t perms_to_set;
1285 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1287 /* Try to change only a minimal amount of the perms first
1288 by getting the current perms and adding bits
1289 only on where read perms are granted. If this fails
1290 fall through to just setting file attributes. */
1291 status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool);
1292 if (status)
1294 if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1295 return SVN_NO_ERROR;
1296 else if (status != APR_ENOTIMPL)
1297 return svn_error_wrap_apr(status,
1298 _("Can't change perms of file '%s'"),
1299 svn_path_local_style(path, pool));
1300 return SVN_NO_ERROR;
1303 if (finfo.filetype == APR_LNK)
1304 return SVN_NO_ERROR;
1306 perms_to_set = finfo.protection;
1307 if (change_readwrite)
1309 if (enable_write) /* Make read-write. */
1310 SVN_ERR(get_default_file_perms(path, &perms_to_set, pool));
1311 else
1313 if (finfo.protection & APR_UREAD)
1314 perms_to_set &= ~APR_UWRITE;
1315 if (finfo.protection & APR_GREAD)
1316 perms_to_set &= ~APR_GWRITE;
1317 if (finfo.protection & APR_WREAD)
1318 perms_to_set &= ~APR_WWRITE;
1322 if (change_executable)
1324 if (executable)
1326 if (finfo.protection & APR_UREAD)
1327 perms_to_set |= APR_UEXECUTE;
1328 if (finfo.protection & APR_GREAD)
1329 perms_to_set |= APR_GEXECUTE;
1330 if (finfo.protection & APR_WREAD)
1331 perms_to_set |= APR_WEXECUTE;
1333 else
1335 if (finfo.protection & APR_UREAD)
1336 perms_to_set &= ~APR_UEXECUTE;
1337 if (finfo.protection & APR_GREAD)
1338 perms_to_set &= ~APR_GEXECUTE;
1339 if (finfo.protection & APR_WREAD)
1340 perms_to_set &= ~APR_WEXECUTE;
1344 /* If we aren't changing anything then just return, this saves
1345 some system calls and helps with shared working copies */
1346 if (perms_to_set == finfo.protection)
1347 return SVN_NO_ERROR;
1349 status = apr_file_perms_set(path_apr, perms_to_set);
1350 if (!status)
1351 return SVN_NO_ERROR;
1353 if (APR_STATUS_IS_EPERM(status))
1355 /* We don't have permissions to change the
1356 permissions! Try a move, copy, and delete
1357 workaround to see if we can get the file owned by
1358 us. If these succeed, try the permissions set
1359 again.
1361 Note that we only attempt this in the
1362 stat-available path. This assumes that the
1363 move-copy workaround will only be helpful on
1364 platforms that implement apr_stat. */
1365 SVN_ERR(reown_file(path, pool));
1366 status = apr_file_perms_set(path_apr, perms_to_set);
1369 if (!status)
1370 return SVN_NO_ERROR;
1372 if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1373 return SVN_NO_ERROR;
1374 else if (status == APR_ENOTIMPL)
1376 /* At least try to set the attributes. */
1377 apr_fileattrs_t attrs = 0;
1378 apr_fileattrs_t attrs_values = 0;
1380 if (change_readwrite)
1382 attrs = APR_FILE_ATTR_READONLY;
1383 if (!enable_write)
1384 attrs_values = APR_FILE_ATTR_READONLY;
1386 if (change_executable)
1388 attrs = APR_FILE_ATTR_EXECUTABLE;
1389 if (executable)
1390 attrs_values = APR_FILE_ATTR_EXECUTABLE;
1392 status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
1395 return svn_error_wrap_apr(status,
1396 _("Can't change perms of file '%s'"),
1397 svn_path_local_style(path, pool));
1399 #endif /* !WIN32 */
1402 svn_error_t *
1403 svn_io_set_file_read_write_carefully(const char *path,
1404 svn_boolean_t enable_write,
1405 svn_boolean_t ignore_enoent,
1406 apr_pool_t *pool)
1408 if (enable_write)
1409 return svn_io_set_file_read_write(path, ignore_enoent, pool);
1410 return svn_io_set_file_read_only(path, ignore_enoent, pool);
1413 svn_error_t *
1414 svn_io_set_file_read_only(const char *path,
1415 svn_boolean_t ignore_enoent,
1416 apr_pool_t *pool)
1418 /* On Windows, just set the file attributes -- on unix call
1419 our internal function which attempts to honor the umask. */
1420 #ifndef WIN32
1421 return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
1422 ignore_enoent, pool);
1423 #else
1424 apr_status_t status;
1425 const char *path_apr;
1427 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1429 status = apr_file_attrs_set(path_apr,
1430 APR_FILE_ATTR_READONLY,
1431 APR_FILE_ATTR_READONLY,
1432 pool);
1434 if (status && status != APR_ENOTIMPL)
1435 if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
1436 return svn_error_wrap_apr(status,
1437 _("Can't set file '%s' read-only"),
1438 svn_path_local_style(path, pool));
1440 return SVN_NO_ERROR;
1441 #endif
1445 svn_error_t *
1446 svn_io_set_file_read_write(const char *path,
1447 svn_boolean_t ignore_enoent,
1448 apr_pool_t *pool)
1450 /* On Windows, just set the file attributes -- on unix call
1451 our internal function which attempts to honor the umask. */
1452 #ifndef WIN32
1453 return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
1454 ignore_enoent, pool);
1455 #else
1456 apr_status_t status;
1457 const char *path_apr;
1459 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1461 status = apr_file_attrs_set(path_apr,
1463 APR_FILE_ATTR_READONLY,
1464 pool);
1466 if (status && status != APR_ENOTIMPL)
1467 if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
1468 return svn_error_wrap_apr(status,
1469 _("Can't set file '%s' read-write"),
1470 svn_path_local_style(path, pool));
1472 return SVN_NO_ERROR;
1473 #endif
1476 svn_error_t *
1477 svn_io_set_file_executable(const char *path,
1478 svn_boolean_t executable,
1479 svn_boolean_t ignore_enoent,
1480 apr_pool_t *pool)
1482 /* On Windows, just exit -- on unix call our internal function
1483 which attempts to honor the umask. */
1484 #ifndef WIN32
1485 return io_set_file_perms(path, FALSE, FALSE, TRUE, executable,
1486 ignore_enoent, pool);
1487 #else
1488 return SVN_NO_ERROR;
1489 #endif
1493 svn_error_t *
1494 svn_io_is_file_executable(svn_boolean_t *executable,
1495 const char *path,
1496 apr_pool_t *pool)
1498 #if defined(APR_HAS_USER) && !defined(WIN32)
1499 apr_finfo_t file_info;
1500 apr_status_t apr_err;
1501 apr_uid_t uid;
1502 apr_gid_t gid;
1504 *executable = FALSE;
1506 /* Get file and user info. */
1507 SVN_ERR(svn_io_stat(&file_info, path,
1508 (APR_FINFO_PROT | APR_FINFO_OWNER),
1509 pool));
1510 apr_err = apr_uid_current(&uid, &gid, pool);
1512 if (apr_err)
1513 return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
1515 /* Check executable bit for current user. */
1516 if (apr_uid_compare(uid, file_info.user) == APR_SUCCESS)
1517 *executable = (file_info.protection & APR_UEXECUTE);
1519 else if (apr_gid_compare(gid, file_info.group) == APR_SUCCESS)
1520 *executable = (file_info.protection & APR_GEXECUTE);
1522 else
1523 *executable = (file_info.protection & APR_WEXECUTE);
1525 #else /* defined(WIN32) || !defined(APR_HAS_USER) */
1526 *executable = FALSE;
1527 #endif
1529 return SVN_NO_ERROR;
1533 /*** File locking. ***/
1534 /* Clear all outstanding locks on ARG, an open apr_file_t *. */
1535 static apr_status_t
1536 svn_io__file_clear_and_close(void *arg)
1538 apr_status_t apr_err;
1539 apr_file_t *f = arg;
1541 /* Remove locks. */
1542 apr_err = apr_file_unlock(f);
1543 if (apr_err)
1544 return apr_err;
1546 /* Close the file. */
1547 apr_err = apr_file_close(f);
1548 if (apr_err)
1549 return apr_err;
1551 return 0;
1555 svn_error_t *svn_io_file_lock(const char *lock_file,
1556 svn_boolean_t exclusive,
1557 apr_pool_t *pool)
1559 return svn_io_file_lock2(lock_file, exclusive, FALSE, pool);
1562 svn_error_t *svn_io_file_lock2(const char *lock_file,
1563 svn_boolean_t exclusive,
1564 svn_boolean_t nonblocking,
1565 apr_pool_t *pool)
1567 int locktype = APR_FLOCK_SHARED;
1568 apr_file_t *lockfile_handle;
1569 apr_int32_t flags;
1570 apr_status_t apr_err;
1572 if (exclusive == TRUE)
1573 locktype = APR_FLOCK_EXCLUSIVE;
1575 flags = APR_READ;
1576 if (locktype == APR_FLOCK_EXCLUSIVE)
1577 flags |= APR_WRITE;
1579 if (nonblocking == TRUE)
1580 locktype |= APR_FLOCK_NONBLOCK;
1582 SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags,
1583 APR_OS_DEFAULT,
1584 pool));
1586 /* Get lock on the filehandle. */
1587 apr_err = apr_file_lock(lockfile_handle, locktype);
1588 if (apr_err)
1590 switch (locktype & APR_FLOCK_TYPEMASK)
1592 case APR_FLOCK_SHARED:
1593 return svn_error_wrap_apr
1594 (apr_err, _("Can't get shared lock on file '%s'"),
1595 svn_path_local_style(lock_file, pool));
1596 case APR_FLOCK_EXCLUSIVE:
1597 return svn_error_wrap_apr
1598 (apr_err, _("Can't get exclusive lock on file '%s'"),
1599 svn_path_local_style(lock_file, pool));
1600 default:
1601 SVN_ERR_MALFUNCTION();
1605 apr_pool_cleanup_register(pool, lockfile_handle,
1606 svn_io__file_clear_and_close,
1607 apr_pool_cleanup_null);
1609 return SVN_NO_ERROR;
1614 /* Data consistency/coherency operations. */
1616 static svn_error_t *
1617 do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
1618 const char *msg, const char *msg_no_name,
1619 apr_pool_t *pool);
1621 svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
1622 apr_pool_t *pool)
1624 apr_os_file_t filehand;
1626 /* First make sure that any user-space buffered data is flushed. */
1627 SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
1628 N_("Can't flush file '%s'"),
1629 N_("Can't flush stream"),
1630 pool));
1632 apr_os_file_get(&filehand, file);
1634 /* Call the operating system specific function to actually force the
1635 data to disk. */
1637 #ifdef WIN32
1639 if (! FlushFileBuffers(filehand))
1640 return svn_error_wrap_apr
1641 (apr_get_os_error(), _("Can't flush file to disk"));
1643 #else
1644 int rv;
1646 do {
1647 rv = fsync(filehand);
1648 } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
1650 /* If the file is in a memory filesystem, fsync() may return
1651 EINVAL. Presumably the user knows the risks, and we can just
1652 ignore the error. */
1653 if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error()))
1654 return SVN_NO_ERROR;
1656 if (rv == -1)
1657 return svn_error_wrap_apr
1658 (apr_get_os_error(), _("Can't flush file to disk"));
1660 #endif
1662 return SVN_NO_ERROR;
1667 /* TODO write test for these two functions, then refactor. */
1669 svn_error_t *
1670 svn_stringbuf_from_file2(svn_stringbuf_t **result,
1671 const char *filename,
1672 apr_pool_t *pool)
1674 apr_file_t *f;
1676 if (filename[0] == '-' && filename[1] == '\0')
1678 apr_status_t apr_err;
1679 if ((apr_err = apr_file_open_stdin(&f, pool)))
1680 return svn_error_wrap_apr(apr_err, _("Can't open stdin"));
1682 else
1684 SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool));
1687 /* ### ugh. we should stat() the file, get its length, and read that
1688 ### much data into memory. the _from_aprfile() function uses a
1689 ### realloc-style that chews up memory needlessly. */
1691 SVN_ERR(svn_stringbuf_from_aprfile(result, f, pool));
1692 return svn_io_file_close(f, pool);
1696 svn_error_t *
1697 svn_stringbuf_from_file(svn_stringbuf_t **result,
1698 const char *filename,
1699 apr_pool_t *pool)
1701 if (filename[0] == '-' && filename[1] == '\0')
1702 return svn_error_create
1703 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1704 _("Reading from stdin is disallowed"));
1705 return svn_stringbuf_from_file2(result, filename, pool);
1709 /* Get the name of FILE, or NULL if FILE is an unnamed stream. */
1710 static svn_error_t *
1711 file_name_get(const char **fname_utf8, apr_file_t *file, apr_pool_t *pool)
1713 apr_status_t apr_err;
1714 const char *fname;
1716 apr_err = apr_file_name_get(&fname, file);
1717 if (apr_err)
1718 return svn_error_wrap_apr(apr_err, _("Can't get file name"));
1720 if (fname)
1721 SVN_ERR(svn_path_cstring_to_utf8(fname_utf8, fname, pool));
1722 else
1723 *fname_utf8 = NULL;
1725 return SVN_NO_ERROR;
1729 svn_error_t *
1730 svn_stringbuf_from_aprfile(svn_stringbuf_t **result,
1731 apr_file_t *file,
1732 apr_pool_t *pool)
1734 apr_size_t len;
1735 svn_error_t *err;
1736 svn_stringbuf_t *res = svn_stringbuf_create_ensure(SVN__STREAM_CHUNK_SIZE,
1737 pool);
1738 char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
1740 /* XXX: We should check the incoming data for being of type binary. */
1742 /* apr_file_read will not return data and eof in the same call. So this loop
1743 * is safe from missing read data. */
1744 len = SVN__STREAM_CHUNK_SIZE;
1745 err = svn_io_file_read(file, buf, &len, pool);
1746 while (! err)
1748 svn_stringbuf_appendbytes(res, buf, len);
1749 len = SVN__STREAM_CHUNK_SIZE;
1750 err = svn_io_file_read(file, buf, &len, pool);
1753 /* Having read all the data we *expect* EOF */
1754 if (err && !APR_STATUS_IS_EOF(err->apr_err))
1755 return err;
1756 svn_error_clear(err);
1758 *result = res;
1759 return SVN_NO_ERROR;
1764 /* Deletion. */
1766 svn_error_t *
1767 svn_io_remove_file(const char *path, apr_pool_t *pool)
1769 apr_status_t apr_err;
1770 const char *path_apr;
1772 #ifdef WIN32
1773 /* Set the file writable but only on Windows, because Windows
1774 will not allow us to remove files that are read-only. */
1775 SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
1776 #endif /* WIN32 */
1778 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1780 apr_err = apr_file_remove(path_apr, pool);
1781 #ifdef WIN32
1782 if (apr_err)
1784 /* Check to make sure we aren't trying to delete a directory */
1785 if (APR_TO_OS_ERROR(apr_err) == ERROR_ACCESS_DENIED)
1787 apr_finfo_t finfo;
1789 if (apr_stat(&finfo, path_apr, APR_FINFO_TYPE, pool) == APR_SUCCESS
1790 && finfo.filetype == APR_REG)
1792 WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr, pool));
1796 /* Just return the delete error */
1798 #endif
1799 if (apr_err)
1800 return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"),
1801 svn_path_local_style(path, pool));
1803 return SVN_NO_ERROR;
1807 svn_error_t *
1808 svn_io_remove_dir(const char *path, apr_pool_t *pool)
1810 return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
1814 Mac OS X has a bug where if you're readding the contents of a
1815 directory via readdir in a loop, and you remove one of the entries in
1816 the directory and the directory has 338 or more files in it you will
1817 skip over some of the entries in the directory. Needless to say,
1818 this causes problems if you are using this kind of loop inside a
1819 function that is recursively deleting a directory, because when you
1820 get around to removing the directory it will still have something in
1823 Similar problem has been observed on FreeBSD.
1825 See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 for more
1826 discussion and an initial solution.
1828 To work around the problem, we do a rewinddir after we delete all files
1829 and see if there's anything left. We repeat the steps untill there's
1830 nothing left to delete.
1832 This workaround causes issues on Windows where delete's are asynchronous,
1833 however, so we never rewind if we're on Windows (the delete says it is
1834 complete, we rewind, we see the same file and try to delete it again,
1835 we fail.
1838 /* Neither windows nor unix allows us to delete a non-empty
1839 directory.
1841 This is a function to perform the equivalent of 'rm -rf'. */
1842 svn_error_t *
1843 svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
1844 svn_cancel_func_t cancel_func, void *cancel_baton,
1845 apr_pool_t *pool)
1847 apr_status_t status;
1848 apr_dir_t *this_dir;
1849 apr_finfo_t this_entry;
1850 apr_pool_t *subpool;
1851 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
1852 const char *path_apr;
1853 int need_rewind;
1855 /* Check for pending cancellation request.
1856 If we need to bail out, do so early. */
1858 if (cancel_func)
1859 SVN_ERR((*cancel_func)(cancel_baton));
1861 /* Convert path to native here and call apr_dir_open directly,
1862 instead of just using svn_io_dir_open, because we're going to
1863 need path_apr later anyway when we remove the dir itself. */
1865 if (path[0] == '\0')
1866 /* APR doesn't like "" directories; use "." instead. */
1867 SVN_ERR(cstring_from_utf8(&path_apr, ".", pool));
1868 else
1869 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
1871 status = apr_dir_open(&this_dir, path_apr, pool);
1872 if (status)
1874 /* if the directory doesn't exist, our mission is accomplished */
1875 if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1876 return SVN_NO_ERROR;
1877 else
1878 return svn_error_wrap_apr(status,
1879 _("Can't open directory '%s'"),
1880 svn_path_local_style(path, pool));
1883 subpool = svn_pool_create(pool);
1887 need_rewind = FALSE;
1889 for (status = apr_dir_read(&this_entry, flags, this_dir);
1890 status == APR_SUCCESS;
1891 status = apr_dir_read(&this_entry, flags, this_dir))
1893 svn_pool_clear(subpool);
1894 if ((this_entry.filetype == APR_DIR)
1895 && ((this_entry.name[0] == '.')
1896 && ((this_entry.name[1] == '\0')
1897 || ((this_entry.name[1] == '.')
1898 && (this_entry.name[2] == '\0')))))
1900 continue;
1902 else /* something other than "." or "..", so proceed */
1904 const char *fullpath, *entry_utf8;
1906 #ifndef WIN32
1907 need_rewind = TRUE;
1908 #endif
1910 SVN_ERR(entry_name_to_utf8(&entry_utf8, this_entry.name,
1911 path_apr, subpool));
1913 fullpath = svn_path_join(path, entry_utf8, subpool);
1915 if (this_entry.filetype == APR_DIR)
1917 /* Don't check for cancellation, the callee
1918 will immediately do so */
1919 SVN_ERR(svn_io_remove_dir2(fullpath, FALSE,
1920 cancel_func, cancel_baton,
1921 subpool));
1923 else
1925 svn_error_t *err;
1927 if (cancel_func)
1928 SVN_ERR((*cancel_func)(cancel_baton));
1930 err = svn_io_remove_file(fullpath, subpool);
1931 if (err)
1932 return svn_error_createf
1933 (err->apr_err, err, _("Can't remove '%s'"),
1934 svn_path_local_style(fullpath, subpool));
1939 if (need_rewind)
1941 status = apr_dir_rewind(this_dir);
1942 if (status)
1943 return svn_error_wrap_apr(status, _("Can't rewind directory '%s'"),
1944 svn_path_local_style (path, pool));
1947 while (need_rewind);
1949 svn_pool_destroy(subpool);
1951 if (!APR_STATUS_IS_ENOENT(status))
1952 return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
1953 svn_path_local_style(path, pool));
1955 status = apr_dir_close(this_dir);
1956 if (status)
1957 return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
1958 svn_path_local_style(path, pool));
1960 status = apr_dir_remove(path_apr, pool);
1961 WIN32_RETRY_LOOP(status, apr_dir_remove(path_apr, pool));
1962 if (status)
1963 return svn_error_wrap_apr(status, _("Can't remove '%s'"),
1964 svn_path_local_style(path, pool));
1966 return APR_SUCCESS;
1969 svn_error_t *
1970 svn_io_get_dir_filenames(apr_hash_t **dirents,
1971 const char *path,
1972 apr_pool_t *pool)
1974 apr_status_t status;
1975 apr_dir_t *this_dir;
1976 apr_finfo_t this_entry;
1977 apr_int32_t flags = APR_FINFO_NAME;
1979 *dirents = apr_hash_make(pool);
1981 SVN_ERR(svn_io_dir_open(&this_dir, path, pool));
1983 for (status = apr_dir_read(&this_entry, flags, this_dir);
1984 status == APR_SUCCESS;
1985 status = apr_dir_read(&this_entry, flags, this_dir))
1987 if ((this_entry.name[0] == '.')
1988 && ((this_entry.name[1] == '\0')
1989 || ((this_entry.name[1] == '.')
1990 && (this_entry.name[2] == '\0'))))
1992 continue;
1994 else
1996 const char *name;
1997 SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, pool));
1998 apr_hash_set(*dirents, name, APR_HASH_KEY_STRING, name);
2002 if (! (APR_STATUS_IS_ENOENT(status)))
2003 return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
2004 svn_path_local_style(path, pool));
2006 status = apr_dir_close(this_dir);
2007 if (status)
2008 return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
2009 svn_path_local_style(path, pool));
2011 return SVN_NO_ERROR;
2014 svn_error_t *
2015 svn_io_get_dirents2(apr_hash_t **dirents,
2016 const char *path,
2017 apr_pool_t *pool)
2019 apr_status_t status;
2020 apr_dir_t *this_dir;
2021 apr_finfo_t this_entry;
2022 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
2024 *dirents = apr_hash_make(pool);
2026 SVN_ERR(svn_io_dir_open(&this_dir, path, pool));
2028 for (status = apr_dir_read(&this_entry, flags, this_dir);
2029 status == APR_SUCCESS;
2030 status = apr_dir_read(&this_entry, flags, this_dir))
2032 if ((this_entry.name[0] == '.')
2033 && ((this_entry.name[1] == '\0')
2034 || ((this_entry.name[1] == '.')
2035 && (this_entry.name[2] == '\0'))))
2037 continue;
2039 else
2041 const char *name;
2042 svn_io_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
2044 SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, pool));
2046 map_apr_finfo_to_node_kind(&(dirent->kind),
2047 &(dirent->special),
2048 &this_entry);
2050 apr_hash_set(*dirents, name, APR_HASH_KEY_STRING, dirent);
2054 if (! (APR_STATUS_IS_ENOENT(status)))
2055 return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
2056 svn_path_local_style(path, pool));
2058 status = apr_dir_close(this_dir);
2059 if (status)
2060 return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
2061 svn_path_local_style(path, pool));
2063 return SVN_NO_ERROR;
2066 svn_error_t *
2067 svn_io_get_dirents(apr_hash_t **dirents,
2068 const char *path,
2069 apr_pool_t *pool)
2071 /* Note that in C, padding is not allowed at the beginning of structs,
2072 so this is actually portable, since the kind field of svn_io_dirent_t
2073 is first in that struct. */
2074 return svn_io_get_dirents2(dirents, path, pool);
2077 /* Pool userdata key for the error file passed to svn_io_start_cmd(). */
2078 #define ERRFILE_KEY "svn-io-start-cmd-errfile"
2080 /* Handle an error from the child process (before command execution) by
2081 printing DESC and the error string corresponding to STATUS to stderr. */
2082 static void
2083 handle_child_process_error(apr_pool_t *pool, apr_status_t status,
2084 const char *desc)
2086 char errbuf[256];
2087 apr_file_t *errfile;
2088 void *p;
2090 /* We can't do anything if we get an error here, so just return. */
2091 if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool))
2092 return;
2093 errfile = p;
2095 if (errfile)
2096 /* What we get from APR is in native encoding. */
2097 apr_file_printf(errfile, "%s: %s",
2098 desc, apr_strerror(status, errbuf,
2099 sizeof(errbuf)));
2103 svn_error_t *
2104 svn_io_start_cmd(apr_proc_t *cmd_proc,
2105 const char *path,
2106 const char *cmd,
2107 const char *const *args,
2108 svn_boolean_t inherit,
2109 apr_file_t *infile,
2110 apr_file_t *outfile,
2111 apr_file_t *errfile,
2112 apr_pool_t *pool)
2114 apr_status_t apr_err;
2115 apr_procattr_t *cmdproc_attr;
2116 int num_args;
2117 const char **args_native;
2118 const char *cmd_apr;
2120 /* Create the process attributes. */
2121 apr_err = apr_procattr_create(&cmdproc_attr, pool);
2122 if (apr_err)
2123 return svn_error_wrap_apr
2124 (apr_err, _("Can't create process '%s' attributes"), cmd);
2126 /* Make sure we invoke cmd directly, not through a shell. */
2127 apr_err = apr_procattr_cmdtype_set(cmdproc_attr,
2128 inherit?APR_PROGRAM_PATH:APR_PROGRAM);
2129 if (apr_err)
2130 return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"),
2131 cmd);
2133 /* Set the process's working directory. */
2134 if (path)
2136 const char *path_apr;
2138 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
2139 apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr);
2140 if (apr_err)
2141 return svn_error_wrap_apr
2142 (apr_err, _("Can't set process '%s' directory"), cmd);
2145 /* Use requested inputs and outputs.
2147 ### Unfortunately each of these apr functions creates a pipe and then
2148 overwrites the pipe file descriptor with the descriptor we pass
2149 in. The pipes can then never be closed. This is an APR bug. */
2150 if (infile)
2152 apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL);
2153 if (apr_err)
2154 return svn_error_wrap_apr
2155 (apr_err, _("Can't set process '%s' child input"), cmd);
2157 if (outfile)
2159 apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL);
2160 if (apr_err)
2161 return svn_error_wrap_apr
2162 (apr_err, _("Can't set process '%s' child outfile"), cmd);
2164 if (errfile)
2166 apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL);
2167 if (apr_err)
2168 return svn_error_wrap_apr
2169 (apr_err, _("Can't set process '%s' child errfile"), cmd);
2172 /* Have the child print any problems executing its program to errfile. */
2173 apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool);
2174 if (apr_err)
2175 return svn_error_wrap_apr
2176 (apr_err, _("Can't set process '%s' child errfile for error handler"),
2177 cmd);
2178 apr_err = apr_procattr_child_errfn_set(cmdproc_attr,
2179 handle_child_process_error);
2180 if (apr_err)
2181 return svn_error_wrap_apr
2182 (apr_err, _("Can't set process '%s' error handler"), cmd);
2184 /* Convert cmd and args from UTF-8 */
2185 SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool));
2186 for (num_args = 0; args[num_args]; num_args++)
2188 args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *));
2189 args_native[num_args] = NULL;
2190 while (num_args--)
2192 /* ### Well, it turns out that on APR on Windows expects all
2193 program args to be in UTF-8. Callers of svn_io_run_cmd
2194 should be aware of that. */
2195 SVN_ERR(cstring_from_utf8(&args_native[num_args],
2196 args[num_args], pool));
2200 /* Start the cmd command. */
2201 apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native, NULL,
2202 cmdproc_attr, pool);
2203 if (apr_err)
2204 return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd);
2206 return SVN_NO_ERROR;
2209 #undef ERRFILE_KEY
2211 svn_error_t *
2212 svn_io_wait_for_cmd(apr_proc_t *cmd_proc,
2213 const char *cmd,
2214 int *exitcode,
2215 apr_exit_why_e *exitwhy,
2216 apr_pool_t *pool)
2218 apr_status_t apr_err;
2219 apr_exit_why_e exitwhy_val;
2220 int exitcode_val;
2222 /* The Win32 apr_proc_wait doesn't set this... */
2223 exitwhy_val = APR_PROC_EXIT;
2225 /* Wait for the cmd command to finish. */
2226 apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT);
2227 if (!APR_STATUS_IS_CHILD_DONE(apr_err))
2228 return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"),
2229 cmd);
2231 if (exitwhy)
2232 *exitwhy = exitwhy_val;
2233 else if (! APR_PROC_CHECK_EXIT(exitwhy_val))
2234 return svn_error_createf
2235 (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2236 _("Process '%s' failed (exitwhy %d)"), cmd, exitwhy_val);
2238 if (exitcode)
2239 *exitcode = exitcode_val;
2240 else if (exitcode_val != 0)
2241 return svn_error_createf
2242 (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2243 _("Process '%s' returned error exitcode %d"), cmd, exitcode_val);
2245 return SVN_NO_ERROR;
2249 svn_error_t *
2250 svn_io_run_cmd(const char *path,
2251 const char *cmd,
2252 const char *const *args,
2253 int *exitcode,
2254 apr_exit_why_e *exitwhy,
2255 svn_boolean_t inherit,
2256 apr_file_t *infile,
2257 apr_file_t *outfile,
2258 apr_file_t *errfile,
2259 apr_pool_t *pool)
2261 apr_proc_t cmd_proc;
2263 SVN_ERR(svn_io_start_cmd(&cmd_proc, path, cmd, args, inherit,
2264 infile, outfile, errfile, pool));
2266 return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool);
2270 svn_error_t *
2271 svn_io_run_diff2(const char *dir,
2272 const char *const *user_args,
2273 int num_user_args,
2274 const char *label1,
2275 const char *label2,
2276 const char *from,
2277 const char *to,
2278 int *pexitcode,
2279 apr_file_t *outfile,
2280 apr_file_t *errfile,
2281 const char *diff_cmd,
2282 apr_pool_t *pool)
2284 const char **args;
2285 int i;
2286 int exitcode;
2287 int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */
2288 apr_pool_t *subpool = svn_pool_create(pool);
2290 if (pexitcode == NULL)
2291 pexitcode = &exitcode;
2293 if (user_args != NULL)
2294 nargs += num_user_args;
2295 else
2296 nargs += 1; /* -u */
2298 if (label1 != NULL)
2299 nargs += 2; /* the -L and the label itself */
2300 if (label2 != NULL)
2301 nargs += 2; /* the -L and the label itself */
2303 args = apr_palloc(subpool, nargs * sizeof(char *));
2305 i = 0;
2306 args[i++] = diff_cmd;
2308 if (user_args != NULL)
2310 int j;
2311 for (j = 0; j < num_user_args; ++j)
2312 args[i++] = user_args[j];
2314 else
2315 args[i++] = "-u"; /* assume -u if the user didn't give us any args */
2317 if (label1 != NULL)
2319 args[i++] = "-L";
2320 args[i++] = label1;
2322 if (label2 != NULL)
2324 args[i++] = "-L";
2325 args[i++] = label2;
2328 args[i++] = svn_path_local_style(from, subpool);
2329 args[i++] = svn_path_local_style(to, subpool);
2330 args[i++] = NULL;
2332 SVN_ERR_ASSERT(i == nargs);
2334 SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE,
2335 NULL, outfile, errfile, subpool));
2337 /* The man page for (GNU) diff describes the return value as:
2339 "An exit status of 0 means no differences were found, 1 means
2340 some differences were found, and 2 means trouble."
2342 A return value of 2 typically occurs when diff cannot read its input
2343 or write to its output, but in any case we probably ought to return an
2344 error for anything other than 0 or 1 as the output is likely to be
2345 corrupt.
2347 if (*pexitcode != 0 && *pexitcode != 1)
2348 return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
2349 _("'%s' returned %d"),
2350 svn_path_local_style(diff_cmd, pool),
2351 *pexitcode);
2353 svn_pool_destroy(subpool);
2355 return SVN_NO_ERROR;
2359 svn_error_t *
2360 svn_io_run_diff3_3(int *exitcode,
2361 const char *dir,
2362 const char *mine,
2363 const char *older,
2364 const char *yours,
2365 const char *mine_label,
2366 const char *older_label,
2367 const char *yours_label,
2368 apr_file_t *merged,
2369 const char *diff3_cmd,
2370 const apr_array_header_t *user_args,
2371 apr_pool_t *pool)
2373 const char **args = apr_palloc(pool,
2374 sizeof(char*) * (13
2375 + (user_args
2376 ? user_args->nelts
2377 : 1)));
2378 #ifndef NDEBUG
2379 int nargs = 12;
2380 #endif
2381 int i = 0;
2383 /* Labels fall back to sensible defaults if not specified. */
2384 if (mine_label == NULL)
2385 mine_label = ".working";
2386 if (older_label == NULL)
2387 older_label = ".old";
2388 if (yours_label == NULL)
2389 yours_label = ".new";
2391 /* Set up diff3 command line. */
2392 args[i++] = diff3_cmd;
2393 if (user_args)
2395 int j;
2396 for (j = 0; j < user_args->nelts; ++j)
2397 args[i++] = APR_ARRAY_IDX(user_args, j, const char *);
2398 #ifndef NDEBUG
2399 nargs += user_args->nelts;
2400 #endif
2402 else
2404 args[i++] = "-E"; /* We tried "-A" here, but that caused
2405 overlapping identical changes to
2406 conflict. See issue #682. */
2407 #ifndef NDEBUG
2408 ++nargs;
2409 #endif
2411 args[i++] = "-m";
2412 args[i++] = "-L";
2413 args[i++] = mine_label;
2414 args[i++] = "-L";
2415 args[i++] = older_label; /* note: this label is ignored if
2416 using 2-part markers, which is the
2417 case with "-E". */
2418 args[i++] = "-L";
2419 args[i++] = yours_label;
2420 #ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG
2422 svn_boolean_t has_arg;
2424 /* ### FIXME: we really shouldn't be reading the config here;
2425 instead, the necessary bits should be passed in by the caller.
2426 But should we add another parameter to this function, when the
2427 whole external diff3 thing might eventually go away? */
2428 apr_hash_t *config;
2429 svn_config_t *cfg;
2431 SVN_ERR(svn_config_get_config(&config, pool));
2432 cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
2433 APR_HASH_KEY_STRING) : NULL;
2434 SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS,
2435 SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG,
2436 TRUE));
2437 if (has_arg)
2439 const char *diff_cmd, *diff_utf8;
2440 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
2441 SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF);
2442 SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool));
2443 args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL);
2444 #ifndef NDEBUG
2445 ++nargs;
2446 #endif
2449 #endif
2450 args[i++] = svn_path_local_style(mine, pool);
2451 args[i++] = svn_path_local_style(older, pool);
2452 args[i++] = svn_path_local_style(yours, pool);
2453 args[i++] = NULL;
2454 #ifndef NDEBUG
2455 SVN_ERR_ASSERT(i == nargs);
2456 #endif
2458 /* Run diff3, output the merged text into the scratch file. */
2459 SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args,
2460 exitcode, NULL,
2461 TRUE, /* keep environment */
2462 NULL, merged, NULL,
2463 pool));
2465 /* According to the diff3 docs, a '0' means the merge was clean, and
2466 '1' means conflict markers were found. Anything else is real
2467 error. */
2468 if ((*exitcode != 0) && (*exitcode != 1))
2469 return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
2470 _("Error running '%s': exitcode was %d, "
2471 "args were:"
2472 "\nin directory '%s', basenames:\n%s\n%s\n%s"),
2473 svn_path_local_style(diff3_cmd, pool),
2474 *exitcode,
2475 svn_path_local_style(dir, pool),
2476 /* Don't call svn_path_local_style() on
2477 the basenames. We don't want them to
2478 be absolute, and we don't need the
2479 separator conversion. */
2480 mine, older, yours);
2482 return SVN_NO_ERROR;
2485 svn_error_t *
2486 svn_io_parse_mimetypes_file(apr_hash_t **type_map,
2487 const char *mimetypes_file,
2488 apr_pool_t *pool)
2490 svn_error_t *err = SVN_NO_ERROR;
2491 apr_hash_t *types = apr_hash_make(pool);
2492 svn_boolean_t eof = FALSE;
2493 svn_stringbuf_t *buf;
2494 apr_pool_t *subpool = svn_pool_create(pool);
2495 apr_file_t *types_file;
2496 svn_stream_t *mimetypes_stream;
2498 SVN_ERR(svn_io_file_open(&types_file, mimetypes_file,
2499 APR_READ, APR_OS_DEFAULT, pool));
2500 mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool);
2502 while (1)
2504 apr_array_header_t *tokens;
2505 const char *type;
2506 int i;
2508 svn_pool_clear(subpool);
2510 /* Read a line. */
2511 if ((err = svn_stream_readline(mimetypes_stream, &buf,
2512 APR_EOL_STR, &eof, subpool)))
2513 break;
2515 /* Only pay attention to non-empty, non-comment lines. */
2516 if (buf->len)
2518 if (buf->data[0] == '#')
2519 continue;
2521 /* Tokenize (into our return pool). */
2522 tokens = svn_cstring_split(buf->data, " \t", TRUE, pool);
2523 if (tokens->nelts < 2)
2524 continue;
2526 /* The first token in a multi-token line is the media type.
2527 Subsequent tokens are filename extensions associated with
2528 that media type. */
2529 type = APR_ARRAY_IDX(tokens, 0, const char *);
2530 for (i = 1; i < tokens->nelts; i++)
2532 const char *ext = APR_ARRAY_IDX(tokens, i, const char *);
2533 apr_hash_set(types, ext, APR_HASH_KEY_STRING, type);
2536 if (eof)
2537 break;
2539 svn_pool_destroy(subpool);
2541 /* If there was an error above, close the file (ignoring any error
2542 from *that*) and return the originally error. */
2543 if (err)
2545 svn_error_clear(svn_stream_close(mimetypes_stream));
2546 return err;
2549 /* Close the stream (which closes the underlying file, too). */
2550 SVN_ERR(svn_stream_close(mimetypes_stream));
2552 *type_map = types;
2553 return SVN_NO_ERROR;
2557 svn_error_t *
2558 svn_io_detect_mimetype2(const char **mimetype,
2559 const char *file,
2560 apr_hash_t *mimetype_map,
2561 apr_pool_t *pool)
2563 static const char * const generic_binary = "application/octet-stream";
2565 svn_node_kind_t kind;
2566 apr_file_t *fh;
2567 svn_error_t *err;
2568 unsigned char block[1024];
2569 apr_size_t amt_read = sizeof(block);
2571 /* Default return value is NULL. */
2572 *mimetype = NULL;
2574 /* See if this file even exists, and make sure it really is a file. */
2575 SVN_ERR(svn_io_check_path(file, &kind, pool));
2576 if (kind != svn_node_file)
2577 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
2578 _("Can't detect MIME type of non-file '%s'"),
2579 svn_path_local_style(file, pool));
2581 /* If there is a mimetype_map provided, we'll first try to look up
2582 our file's extension in the map. Failing that, we'll run the
2583 heuristic. */
2584 if (mimetype_map)
2586 const char *type_from_map, *path_ext;
2587 svn_path_splitext(NULL, &path_ext, file, pool);
2588 if ((type_from_map = apr_hash_get(mimetype_map, path_ext,
2589 APR_HASH_KEY_STRING)))
2591 *mimetype = type_from_map;
2592 return SVN_NO_ERROR;
2596 SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool));
2598 /* Read a block of data from FILE. */
2599 err = svn_io_file_read(fh, block, &amt_read, pool);
2600 if (err && ! APR_STATUS_IS_EOF(err->apr_err))
2601 return err;
2602 svn_error_clear(err);
2604 /* Now close the file. No use keeping it open any more. */
2605 SVN_ERR(svn_io_file_close(fh, pool));
2608 /* Right now, this function is going to be really stupid. It's
2609 going to examine the first block of data, and make sure that 85%
2610 of the bytes are such that their value is in the ranges 0x07-0x0D
2611 or 0x20-0x7F, and that 100% of those bytes is not 0x00.
2613 If those criteria are not met, we're calling it binary. */
2614 if (amt_read > 0)
2616 apr_size_t i;
2617 int binary_count = 0;
2619 /* Run through the data we've read, counting the 'binary-ish'
2620 bytes. HINT: If we see a 0x00 byte, we'll set our count to its
2621 max and stop reading the file. */
2622 for (i = 0; i < amt_read; i++)
2624 if (block[i] == 0)
2626 binary_count = amt_read;
2627 break;
2629 if ((block[i] < 0x07)
2630 || ((block[i] > 0x0D) && (block[i] < 0x20))
2631 || (block[i] > 0x7F))
2633 binary_count++;
2637 if (((binary_count * 1000) / amt_read) > 850)
2639 *mimetype = generic_binary;
2640 return SVN_NO_ERROR;
2644 return SVN_NO_ERROR;
2648 svn_error_t *
2649 svn_io_detect_mimetype(const char **mimetype,
2650 const char *file,
2651 apr_pool_t *pool)
2653 return svn_io_detect_mimetype2(mimetype, file, NULL, pool);
2657 svn_error_t *
2658 svn_io_file_open(apr_file_t **new_file, const char *fname,
2659 apr_int32_t flag, apr_fileperms_t perm,
2660 apr_pool_t *pool)
2662 const char *fname_apr;
2663 apr_status_t status;
2665 SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
2666 status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE,
2667 pool);
2669 if (status)
2670 return svn_error_wrap_apr(status, _("Can't open file '%s'"),
2671 svn_path_local_style(fname, pool));
2672 else
2673 return SVN_NO_ERROR;
2677 static svn_error_t *
2678 do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
2679 const char *msg, const char *msg_no_name,
2680 apr_pool_t *pool)
2682 const char *name;
2683 svn_error_t *err;
2685 if (! status)
2686 return SVN_NO_ERROR;
2688 err = file_name_get(&name, file, pool);
2689 if (err)
2690 name = NULL;
2691 svn_error_clear(err);
2693 if (name)
2694 return svn_error_wrap_apr(status, _(msg),
2695 svn_path_local_style(name, pool));
2696 else
2697 return svn_error_wrap_apr(status, _(msg_no_name));
2701 svn_error_t *
2702 svn_io_file_close(apr_file_t *file, apr_pool_t *pool)
2704 return do_io_file_wrapper_cleanup
2705 (file, apr_file_close(file),
2706 N_("Can't close file '%s'"),
2707 N_("Can't close stream"),
2708 pool);
2712 svn_error_t *
2713 svn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool)
2715 return do_io_file_wrapper_cleanup
2716 (file, apr_file_getc(ch, file),
2717 N_("Can't read file '%s'"),
2718 N_("Can't read stream"),
2719 pool);
2723 svn_error_t *
2724 svn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted,
2725 apr_file_t *file, apr_pool_t *pool)
2727 return do_io_file_wrapper_cleanup
2728 (file, apr_file_info_get(finfo, wanted, file),
2729 N_("Can't get attribute information from file '%s'"),
2730 N_("Can't get attribute information from stream"),
2731 pool);
2735 svn_error_t *
2736 svn_io_file_read(apr_file_t *file, void *buf,
2737 apr_size_t *nbytes, apr_pool_t *pool)
2739 return do_io_file_wrapper_cleanup
2740 (file, apr_file_read(file, buf, nbytes),
2741 N_("Can't read file '%s'"),
2742 N_("Can't read stream"),
2743 pool);
2747 svn_error_t *
2748 svn_io_file_read_full(apr_file_t *file, void *buf,
2749 apr_size_t nbytes, apr_size_t *bytes_read,
2750 apr_pool_t *pool)
2752 return do_io_file_wrapper_cleanup
2753 (file, apr_file_read_full(file, buf, nbytes, bytes_read),
2754 N_("Can't read file '%s'"),
2755 N_("Can't read stream"),
2756 pool);
2760 svn_error_t *
2761 svn_io_file_seek(apr_file_t *file, apr_seek_where_t where,
2762 apr_off_t *offset, apr_pool_t *pool)
2764 return do_io_file_wrapper_cleanup
2765 (file, apr_file_seek(file, where, offset),
2766 N_("Can't set position pointer in file '%s'"),
2767 N_("Can't set position pointer in stream"),
2768 pool);
2772 svn_error_t *
2773 svn_io_file_write(apr_file_t *file, const void *buf,
2774 apr_size_t *nbytes, apr_pool_t *pool)
2776 return do_io_file_wrapper_cleanup
2777 (file, apr_file_write(file, buf, nbytes),
2778 N_("Can't write to file '%s'"),
2779 N_("Can't write to stream"),
2780 pool);
2784 svn_error_t *
2785 svn_io_file_write_full(apr_file_t *file, const void *buf,
2786 apr_size_t nbytes, apr_size_t *bytes_written,
2787 apr_pool_t *pool)
2789 apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written);
2791 #ifdef WIN32
2792 #define MAXBUFSIZE 30*1024
2793 if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY)
2794 && nbytes > MAXBUFSIZE)
2796 apr_size_t bw = 0;
2797 *bytes_written = 0;
2799 do {
2800 rv = apr_file_write_full(file, buf,
2801 nbytes > MAXBUFSIZE ? MAXBUFSIZE : nbytes, &bw);
2802 *bytes_written += bw;
2803 buf = (char *)buf + bw;
2804 nbytes -= bw;
2805 } while (rv == APR_SUCCESS && nbytes > 0);
2807 #undef MAXBUFSIZE
2808 #endif
2810 return do_io_file_wrapper_cleanup
2811 (file, rv,
2812 N_("Can't write to file '%s'"),
2813 N_("Can't write to stream"),
2814 pool);
2818 svn_error_t *
2819 svn_io_write_unique(const char **tmp_path,
2820 const char *dirpath,
2821 const void *buf,
2822 apr_size_t nbytes,
2823 svn_io_file_del_t delete_when,
2824 apr_pool_t *pool)
2826 apr_file_t *new_file;
2828 SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath,
2829 delete_when, pool, pool));
2830 SVN_ERR(svn_io_file_write_full(new_file, buf, nbytes, NULL, pool));
2831 SVN_ERR(svn_io_file_flush_to_disk(new_file, pool));
2832 return svn_io_file_close(new_file, pool);
2836 svn_error_t *
2837 svn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool)
2839 return do_io_file_wrapper_cleanup
2840 (file, apr_file_trunc(file, offset),
2841 N_("Can't truncate file '%s'"),
2842 N_("Can't truncate stream"),
2843 pool);
2847 svn_error_t *
2848 svn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit,
2849 apr_pool_t *pool)
2851 const char *name;
2852 svn_error_t *err;
2853 apr_size_t i;
2854 char c;
2856 for (i = 0; i < *limit; i++)
2858 SVN_ERR(svn_io_file_getc(&c, file, pool));
2859 /* Note: this error could be APR_EOF, which
2860 is totally fine. The caller should be aware of
2861 this. */
2863 if (c == '\n')
2865 buf[i] = '\0';
2866 *limit = i;
2867 return SVN_NO_ERROR;
2869 else
2871 buf[i] = c;
2875 err = file_name_get(&name, file, pool);
2876 if (err)
2877 name = NULL;
2878 svn_error_clear(err);
2880 if (name)
2881 return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
2882 _("Can't read length line in file '%s'"),
2883 svn_path_local_style(name, pool));
2884 else
2885 return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
2886 _("Can't read length line in stream"));
2890 svn_error_t *
2891 svn_io_stat(apr_finfo_t *finfo, const char *fname,
2892 apr_int32_t wanted, apr_pool_t *pool)
2894 apr_status_t status;
2895 const char *fname_apr;
2897 /* APR doesn't like "" directories */
2898 if (fname[0] == '\0')
2899 fname = ".";
2901 SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
2903 status = apr_stat(finfo, fname_apr, wanted, pool);
2904 if (status)
2905 return svn_error_wrap_apr(status, _("Can't stat '%s'"),
2906 svn_path_local_style(fname, pool));
2908 return SVN_NO_ERROR;
2912 svn_error_t *
2913 svn_io_file_rename(const char *from_path, const char *to_path,
2914 apr_pool_t *pool)
2916 apr_status_t status = APR_SUCCESS;
2917 const char *from_path_apr, *to_path_apr;
2919 SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool));
2920 SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool));
2922 status = apr_file_rename(from_path_apr, to_path_apr, pool);
2924 #ifdef WIN32
2925 if (status)
2927 /* Set the destination file writable because Windows will not
2928 allow us to rename over files that are read-only. */
2929 SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
2931 status = apr_file_rename(from_path_apr, to_path_apr, pool);
2933 WIN32_RETRY_LOOP(status,
2934 apr_file_rename(from_path_apr, to_path_apr, pool));
2936 #endif /* WIN32 */
2938 if (status)
2939 return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"),
2940 svn_path_local_style(from_path, pool),
2941 svn_path_local_style(to_path, pool));
2943 return SVN_NO_ERROR;
2947 svn_error_t *
2948 svn_io_file_move(const char *from_path, const char *to_path,
2949 apr_pool_t *pool)
2951 svn_error_t *err = svn_io_file_rename(from_path, to_path, pool);
2953 if (err && APR_STATUS_IS_EXDEV(err->apr_err))
2955 const char *tmp_to_path;
2957 svn_error_clear(err);
2959 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path,
2960 svn_path_dirname(to_path, pool),
2961 svn_io_file_del_none,
2962 pool, pool));
2964 err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool);
2965 if (err)
2966 goto failed_tmp;
2968 err = svn_io_file_rename(tmp_to_path, to_path, pool);
2969 if (err)
2970 goto failed_tmp;
2972 err = svn_io_remove_file(from_path, pool);
2973 if (! err)
2974 return SVN_NO_ERROR;
2976 svn_error_clear(svn_io_remove_file(to_path, pool));
2978 return err;
2980 failed_tmp:
2981 svn_error_clear(svn_io_remove_file(tmp_to_path, pool));
2984 return err;
2987 /* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden.
2988 HIDDEN determines if the hidden attribute
2989 should be set on the newly created directory. */
2990 static svn_error_t *
2991 dir_make(const char *path, apr_fileperms_t perm,
2992 svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool)
2994 apr_status_t status;
2995 const char *path_apr;
2997 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
2999 /* APR doesn't like "" directories */
3000 if (path_apr[0] == '\0')
3001 path_apr = ".";
3003 #if (APR_OS_DEFAULT & APR_WSTICKY)
3004 /* The APR shipped with httpd 2.0.50 contains a bug where
3005 APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits.
3006 There is a special case for file creation, but not directory
3007 creation, so directories wind up getting created with the sticky
3008 bit set. (There is no such thing as a setuid directory, and the
3009 setgid bit is apparently ignored at mkdir() time.) If we detect
3010 this problem, work around it by unsetting those bits if we are
3011 passed APR_OS_DEFAULT. */
3012 if (perm == APR_OS_DEFAULT)
3013 perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY);
3014 #endif
3016 status = apr_dir_make(path_apr, perm, pool);
3017 WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
3019 if (status)
3020 return svn_error_wrap_apr(status, _("Can't create directory '%s'"),
3021 svn_path_local_style(path, pool));
3023 #ifdef APR_FILE_ATTR_HIDDEN
3024 if (hidden)
3026 status = apr_file_attrs_set(path_apr,
3027 APR_FILE_ATTR_HIDDEN,
3028 APR_FILE_ATTR_HIDDEN,
3029 pool);
3030 if (status)
3031 return svn_error_wrap_apr(status, _("Can't hide directory '%s'"),
3032 svn_path_local_style(path, pool));
3034 #endif
3036 /* Windows does not implement sgid. Skip here because retrieving
3037 the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented
3038 to be 'incredibly expensive'. */
3039 #ifndef WIN32
3040 if (sgid)
3042 apr_finfo_t finfo;
3044 /* Per our contract, don't do error-checking. Some filesystems
3045 * don't support the sgid bit, and that's okay. */
3046 status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool);
3048 if (!status)
3049 apr_file_perms_set(path_apr, finfo.protection | APR_GSETID);
3051 #endif
3053 return SVN_NO_ERROR;
3056 svn_error_t *
3057 svn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool)
3059 return dir_make(path, perm, FALSE, FALSE, pool);
3062 svn_error_t *
3063 svn_io_dir_make_hidden(const char *path, apr_fileperms_t perm,
3064 apr_pool_t *pool)
3066 return dir_make(path, perm, TRUE, FALSE, pool);
3069 svn_error_t *
3070 svn_io_dir_make_sgid(const char *path, apr_fileperms_t perm,
3071 apr_pool_t *pool)
3073 return dir_make(path, perm, FALSE, TRUE, pool);
3077 svn_error_t *
3078 svn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool)
3080 apr_status_t status;
3081 const char *dirname_apr;
3083 /* APR doesn't like "" directories */
3084 if (dirname[0] == '\0')
3085 dirname = ".";
3087 SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3089 status = apr_dir_open(new_dir, dirname_apr, pool);
3090 if (status)
3091 return svn_error_wrap_apr(status, _("Can't open directory '%s'"),
3092 svn_path_local_style(dirname, pool));
3094 return SVN_NO_ERROR;
3098 svn_error_t *
3099 svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool)
3101 apr_status_t status;
3102 const char *dirname_apr;
3104 SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3106 status = apr_dir_remove(dirname_apr, pool);
3107 WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool));
3108 if (status)
3109 return svn_error_wrap_apr(status, _("Can't remove directory '%s'"),
3110 svn_path_local_style(dirname, pool));
3112 return SVN_NO_ERROR;
3116 svn_error_t *
3117 svn_io_dir_read(apr_finfo_t *finfo,
3118 apr_int32_t wanted,
3119 apr_dir_t *thedir,
3120 apr_pool_t *pool)
3122 apr_status_t status;
3124 status = apr_dir_read(finfo, wanted, thedir);
3126 if (status)
3127 return svn_error_wrap_apr(status, _("Can't read directory"));
3129 /* It would be nice to use entry_name_to_utf8() below, but can we
3130 get the dir's path out of an apr_dir_t? I don't see a reliable
3131 way to do it. */
3133 if (finfo->fname)
3134 SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool));
3136 if (finfo->name)
3137 SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool));
3139 return SVN_NO_ERROR;
3143 svn_error_t *
3144 svn_io_dir_walk(const char *dirname,
3145 apr_int32_t wanted,
3146 svn_io_walk_func_t walk_func,
3147 void *walk_baton,
3148 apr_pool_t *pool)
3150 apr_status_t apr_err;
3151 apr_dir_t *handle;
3152 apr_pool_t *subpool;
3153 const char *dirname_apr;
3154 apr_finfo_t finfo;
3156 wanted |= APR_FINFO_TYPE | APR_FINFO_NAME;
3158 /* The documentation for apr_dir_read used to state that "." and ".."
3159 will be returned as the first two files, but it doesn't
3160 work that way in practice, in particular ext3 on Linux-2.6 doesn't
3161 follow the rules. For details see
3162 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666
3164 If APR ever does implement "dot-first" then it would be possible to
3165 remove the svn_io_stat and walk_func calls and use the walk_func
3166 inside the loop.
3168 Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is
3169 documented to provide it, so we have to do a bit extra. */
3170 SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool));
3171 SVN_ERR(cstring_from_utf8(&finfo.name,
3172 svn_path_basename(dirname, pool),
3173 pool));
3174 finfo.valid |= APR_FINFO_NAME;
3175 SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool));
3177 SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
3179 apr_err = apr_dir_open(&handle, dirname_apr, pool);
3180 if (apr_err)
3181 return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"),
3182 svn_path_local_style(dirname, pool));
3184 /* iteration subpool */
3185 subpool = svn_pool_create(pool);
3187 while (1)
3189 const char *name_utf8;
3190 const char *full_path;
3192 svn_pool_clear(subpool);
3194 apr_err = apr_dir_read(&finfo, wanted, handle);
3195 if (APR_STATUS_IS_ENOENT(apr_err))
3196 break;
3197 else if (apr_err)
3199 return svn_error_wrap_apr
3200 (apr_err, _("Can't read directory entry in '%s'"),
3201 svn_path_local_style(dirname, pool));
3204 if (finfo.filetype == APR_DIR)
3206 if (finfo.name[0] == '.'
3207 && (finfo.name[1] == '\0'
3208 || (finfo.name[1] == '.' && finfo.name[2] == '\0')))
3209 /* skip "." and ".." */
3210 continue;
3212 /* some other directory. recurse. it will be passed to the
3213 callback inside the recursion. */
3214 SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
3215 subpool));
3216 full_path = svn_path_join(dirname, name_utf8, subpool);
3217 SVN_ERR(svn_io_dir_walk(full_path,
3218 wanted,
3219 walk_func,
3220 walk_baton,
3221 subpool));
3223 else if (finfo.filetype == APR_REG)
3225 /* some other directory. pass it to the callback. */
3226 SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
3227 subpool));
3228 full_path = svn_path_join(dirname, name_utf8, subpool);
3229 SVN_ERR((*walk_func)(walk_baton,
3230 full_path,
3231 &finfo,
3232 subpool));
3234 /* else:
3235 some other type of file; skip it.
3240 svn_pool_destroy(subpool);
3242 apr_err = apr_dir_close(handle);
3243 if (apr_err)
3244 return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"),
3245 svn_path_local_style(dirname, pool));
3247 return SVN_NO_ERROR;
3253 * Determine if a directory is empty or not.
3254 * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not.
3255 * @param path The directory.
3256 * @param pool Used for temporary allocation.
3257 * @remark If path is not a directory, or some other error occurs,
3258 * then return the appropriate apr status code.
3260 * (This function is written in APR style, in anticipation of
3261 * perhaps someday being moved to APR as 'apr_dir_is_empty'.)
3263 static apr_status_t
3264 dir_is_empty(const char *dir, apr_pool_t *pool)
3266 apr_status_t apr_err;
3267 apr_dir_t *dir_handle;
3268 apr_finfo_t finfo;
3269 apr_status_t retval = APR_SUCCESS;
3271 /* APR doesn't like "" directories */
3272 if (dir[0] == '\0')
3273 dir = ".";
3275 apr_err = apr_dir_open(&dir_handle, dir, pool);
3276 if (apr_err != APR_SUCCESS)
3277 return apr_err;
3279 for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle);
3280 apr_err == APR_SUCCESS;
3281 apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle))
3283 /* Ignore entries for this dir and its parent, robustly.
3284 (APR promises that they'll come first, so technically
3285 this guard could be moved outside the loop. But Ryan Bloom
3286 says he doesn't believe it, and I believe him. */
3287 if (! (finfo.name[0] == '.'
3288 && (finfo.name[1] == '\0'
3289 || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
3291 retval = APR_ENOTEMPTY;
3292 break;
3296 /* Make sure we broke out of the loop for the right reason. */
3297 if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err))
3298 return apr_err;
3300 apr_err = apr_dir_close(dir_handle);
3301 if (apr_err != APR_SUCCESS)
3302 return apr_err;
3304 return retval;
3308 svn_error_t *
3309 svn_io_dir_empty(svn_boolean_t *is_empty_p,
3310 const char *path,
3311 apr_pool_t *pool)
3313 apr_status_t status;
3314 const char *path_apr;
3316 SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
3318 status = dir_is_empty(path_apr, pool);
3320 if (!status)
3321 *is_empty_p = TRUE;
3322 else if (APR_STATUS_IS_ENOTEMPTY(status))
3323 *is_empty_p = FALSE;
3324 else
3325 return svn_error_wrap_apr(status, _("Can't check directory '%s'"),
3326 svn_path_local_style(path, pool));
3328 return SVN_NO_ERROR;
3333 /*** Version/format files ***/
3335 svn_error_t *
3336 svn_io_write_version_file(const char *path,
3337 int version,
3338 apr_pool_t *pool)
3340 const char *path_tmp;
3341 const char *format_contents = apr_psprintf(pool, "%d\n", version);
3343 SVN_ERR_ASSERT(version >= 0);
3345 SVN_ERR(svn_io_write_unique(&path_tmp,
3346 svn_path_dirname(path, pool),
3347 format_contents, strlen(format_contents),
3348 svn_io_file_del_none, pool));
3350 #ifdef WIN32
3351 /* make the destination writable, but only on Windows, because
3352 Windows does not let us replace read-only files. */
3353 SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
3354 #endif /* WIN32 */
3356 /* rename the temp file as the real destination */
3357 SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
3359 /* And finally remove the perms to make it read only */
3360 return svn_io_set_file_read_only(path, FALSE, pool);
3364 svn_error_t *
3365 svn_io_read_version_file(int *version,
3366 const char *path,
3367 apr_pool_t *pool)
3369 apr_file_t *format_file;
3370 char buf[80];
3371 apr_size_t len;
3373 /* Read a chunk of data from PATH */
3374 SVN_ERR(svn_io_file_open(&format_file, path, APR_READ,
3375 APR_OS_DEFAULT, pool));
3376 len = sizeof(buf);
3377 SVN_ERR(svn_io_file_read(format_file, buf, &len, pool));
3379 /* Close the file. */
3380 SVN_ERR(svn_io_file_close(format_file, pool));
3382 /* If there was no data in PATH, return an error. */
3383 if (len == 0)
3384 return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
3385 _("Reading '%s'"),
3386 svn_path_local_style(path, pool));
3388 /* Check that the first line contains only digits. */
3390 apr_size_t i;
3392 for (i = 0; i < len; ++i)
3394 char c = buf[i];
3396 if (i > 0 && (c == '\r' || c == '\n'))
3397 break;
3398 if (! apr_isdigit(c))
3399 return svn_error_createf
3400 (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
3401 _("First line of '%s' contains non-digit"),
3402 svn_path_local_style(path, pool));
3406 /* Convert to integer. */
3407 *version = atoi(buf);
3409 return SVN_NO_ERROR;
3414 /* Do a byte-for-byte comparison of FILE1 and FILE2. */
3415 static svn_error_t *
3416 contents_identical_p(svn_boolean_t *identical_p,
3417 const char *file1,
3418 const char *file2,
3419 apr_pool_t *pool)
3421 svn_error_t *err1;
3422 svn_error_t *err2;
3423 apr_size_t bytes_read1, bytes_read2;
3424 char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
3425 char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
3426 apr_file_t *file1_h = NULL;
3427 apr_file_t *file2_h = NULL;
3429 SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
3430 pool));
3431 SVN_ERR(svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
3432 pool));
3434 *identical_p = TRUE; /* assume TRUE, until disproved below */
3437 err1 = svn_io_file_read_full(file1_h, buf1,
3438 SVN__STREAM_CHUNK_SIZE, &bytes_read1, pool);
3439 if (err1 && !APR_STATUS_IS_EOF(err1->apr_err))
3440 return err1;
3442 err2 = svn_io_file_read_full(file2_h, buf2,
3443 SVN__STREAM_CHUNK_SIZE, &bytes_read2, pool);
3444 if (err2 && !APR_STATUS_IS_EOF(err2->apr_err))
3446 svn_error_clear(err1);
3447 return err2;
3450 if ((bytes_read1 != bytes_read2)
3451 || (memcmp(buf1, buf2, bytes_read1)))
3453 *identical_p = FALSE;
3454 break;
3456 } while (! err1 && ! err2);
3458 svn_error_clear(err1);
3459 svn_error_clear(err2);
3461 SVN_ERR(svn_io_file_close(file1_h, pool));
3462 return svn_io_file_close(file2_h, pool);
3467 svn_error_t *
3468 svn_io_files_contents_same_p(svn_boolean_t *same,
3469 const char *file1,
3470 const char *file2,
3471 apr_pool_t *pool)
3473 svn_boolean_t q;
3475 SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool));
3477 if (q)
3479 *same = 0;
3480 return SVN_NO_ERROR;
3483 SVN_ERR(contents_identical_p(&q, file1, file2, pool));
3485 if (q)
3486 *same = 1;
3487 else
3488 *same = 0;
3490 return SVN_NO_ERROR;