In the command-line client, forbid
[svn.git] / subversion / libsvn_subr / io.c
blobdd29b820768981d3a92e744ea06e0754597376b3
1 /*
2 * io.c: shared file reading, writing, and probing code.
4 * ====================================================================
5 * Copyright (c) 2000-2007 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>
22 #include <assert.h>
24 #ifndef WIN32
25 #include <unistd.h>
26 #endif
28 #ifndef APR_STATUS_IS_EPERM
29 #include <errno.h>
30 #ifdef EPERM
31 #define APR_STATUS_IS_EPERM(s) ((s) == EPERM)
32 #else
33 #define APR_STATUS_IS_EPERM(s) (0)
34 #endif
35 #endif
37 #include <apr_lib.h>
38 #include <apr_pools.h>
39 #include <apr_file_io.h>
40 #include <apr_file_info.h>
41 #include <apr_general.h>
42 #include <apr_strings.h>
43 #include <apr_portable.h>
44 #include <apr_md5.h>
46 #include "svn_types.h"
47 #include "svn_path.h"
48 #include "svn_string.h"
49 #include "svn_error.h"
50 #include "svn_io.h"
51 #include "svn_pools.h"
52 #include "svn_utf.h"
53 #include "svn_config.h"
54 #include "svn_private_config.h"
57 Windows is 'aided' by a number of types of applications that
58 follow other applications around and open up files they have
59 changed for various reasons (the most intrusive are virus
60 scanners). So, if one of these other apps has glommed onto
61 our file we may get an 'access denied' error.
63 This retry loop does not completely solve the problem (who
64 knows how long the other app is going to hold onto it for), but
65 goes a long way towards minimizing it. It is not an infinite
66 loop because there might really be an error.
68 Another reason for retrying delete operations on Windows
69 is that they are asynchronous -- the file or directory is not
70 actually deleted until the last handle to it is closed. The
71 retry loop cannot completely solve this problem either, but can
72 help mitigate it.
74 #ifdef WIN32
75 #define WIN32_RETRY_LOOP(err, expr) \
76 do \
77 { \
78 apr_status_t os_err = APR_TO_OS_ERROR(err); \
79 int sleep_count = 1000; \
80 int retries; \
81 for (retries = 0; \
82 retries < 100 && (os_err == ERROR_ACCESS_DENIED \
83 || os_err == ERROR_SHARING_VIOLATION \
84 || os_err == ERROR_DIR_NOT_EMPTY); \
85 ++retries, os_err = APR_TO_OS_ERROR(err)) \
86 { \
87 apr_sleep(sleep_count); \
88 if (sleep_count < 128000) \
89 sleep_count *= 2; \
90 (err) = (expr); \
91 } \
92 } \
93 while (0)
94 #else
95 #define WIN32_RETRY_LOOP(err, expr) ((void)0)
96 #endif
99 static void
100 map_apr_finfo_to_node_kind(svn_node_kind_t *kind,
101 svn_boolean_t *is_special,
102 apr_finfo_t *finfo)
104 *is_special = FALSE;
106 if (finfo->filetype == APR_REG)
107 *kind = svn_node_file;
108 else if (finfo->filetype == APR_DIR)
109 *kind = svn_node_dir;
110 else if (finfo->filetype == APR_LNK)
112 *is_special = TRUE;
113 *kind = svn_node_file;
115 else
116 *kind = svn_node_unknown;
119 /* Helper for svn_io_check_path() and svn_io_check_resolved_path();
120 essentially the same semantics as those two, with the obvious
121 interpretation for RESOLVE_SYMLINKS. */
122 static svn_error_t *
123 io_check_path(const char *path,
124 svn_boolean_t resolve_symlinks,
125 svn_boolean_t *is_special_p,
126 svn_node_kind_t *kind,
127 apr_pool_t *pool)
129 apr_int32_t flags;
130 apr_finfo_t finfo;
131 apr_status_t apr_err;
132 const char *path_apr;
133 svn_boolean_t is_special = FALSE;
135 if (path[0] == '\0')
136 path = ".";
138 /* Not using svn_io_stat() here because we want to check the
139 apr_err return explicitly. */
140 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
142 flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
143 apr_err = apr_stat(&finfo, path_apr, flags, pool);
145 if (APR_STATUS_IS_ENOENT(apr_err))
146 *kind = svn_node_none;
147 else if (APR_STATUS_IS_ENOTDIR(apr_err))
148 *kind = svn_node_none;
149 else if (apr_err)
150 return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"),
151 svn_path_local_style(path, pool));
152 else
153 map_apr_finfo_to_node_kind(kind, &is_special, &finfo);
155 *is_special_p = is_special;
157 return SVN_NO_ERROR;
161 /* Wrapper for apr_file_open() that handles CCSID problems on OS400 V5R4. */
162 static apr_status_t
163 file_open(apr_file_t **f,
164 const char *fname,
165 apr_int32_t flag,
166 apr_fileperms_t perm,
167 apr_pool_t *pool)
169 apr_status_t status;
171 #ifdef AS400
172 /* All files in OS400 are tagged with a metadata CCSID (Coded Character Set
173 * Identifier) which indicates the character encoding of the file's
174 * contents. Even binary files are assigned a CCSID, typically the system
175 * CCSID of the machine, which is some variant of EBCDIC (there are many
176 * variants of EBCDIC: CCSID 37 - COM EUROPE EBCDIC, CCSID 273 - AUSTRIAN/
177 * GERMAN EBCDIC, CCSID 284 - SPANISH EBCDIC, etc.. In this comment the
178 * assumed system CCSID is 37).
180 * APR on OS400 V5R4 is built with what IBM calls "UTF support" which means
181 * that within the application text file contents are assumed to be in CCSID
182 * 1208.
184 * On OS400 when using apr_file_open() to read, write, and/or create a file
185 * there is an interplay between the APR_BINARY flag and the file's CCSID:
187 * File | APR_BINARY | Existing | Created | Conversion | Conversion
188 * Exists? | Flag | File's | File's | When | When
189 * | Passed | CCSID | CCSID | Writing | Reading
190 * --------------------------------------------------------------------
191 * Yes | Yes | 1208 | N/A | None | None
192 * Yes | Yes | 37 | N/A | None | None
193 * Yes | No | 1208 | N/A | None | None
194 * Yes | No | 37 | N/A | 1208-->37 | 37-->1208
195 * No | Yes | N/A | 37 | None | None
196 * No | No | N/A | 1208 | None | None
198 * For example: If an existing file with CCSID 37 is opened for reading
199 * without the APR_BINARY flag, the OS will attempt to convert
200 * the file's contents from EBCDIC 37 to UTF-8.
202 * Now for the problem...
204 * - The files Subversion handles have either binary or UTF-8 content.
206 * - Subversion is not structured to differentiate between text files and
207 * binary files. It just always passes the APR_BINARY flag when calling
208 * apr_file_open().
210 * So when Subversion creates a new file it always has a CCSID of 37 even
211 * though the file *may* contain UTF-8 encoded text. This isn't a problem
212 * for Subversion directly since it always passes APR_BINARY when opening
213 * files, therefore the content is never converted when reading/writing the
214 * file.
216 * The problem is that other OS400 applications/utilities rely on the CCSID
217 * to represent the file's contents. For example, when a text editor opens
218 * a svnserve.conf file tagged with CCSID 37 but actually containing UTF-8
219 * text, the OS will attempt to convert what it thinks is EBCDIC text to
220 * UTF-8. Worse, if the file is empty, the text editor would save the
221 * contents as EBCDIC. Later, when Subversion opens the conf file it's
222 * reading in "UTF-8" data that is actually EBCDIC.
224 * The solution to this problem is to catch the case where Subversion wants
225 * to create a file and make an initial call to apr_file_open() in text mode
226 * (i.e. without the APR_BINARY flag), close the file, and then re-open the
227 * file in binary mode (i.e. with the APR_BINARY flag).
229 apr_status_t apr_err;
230 if (flag & APR_CREATE)
232 /* If we are trying to create a file on OS400 ensure its CCSID is
233 * 1208. */
234 apr_err = apr_file_open(f, fname, flag & ~APR_BINARY, perm, pool);
236 if (apr_err)
237 return apr_err;
239 apr_file_close(*f);
241 /* Unset APR_EXCL so the next call to apr_file_open() doesn't
242 * return an error. */
243 flag &= ~APR_EXCL;
245 #endif /* AS400 */
246 status = apr_file_open(f, fname, flag, perm, pool);
247 WIN32_RETRY_LOOP(status, apr_file_open(f, fname, flag, perm, pool));
248 return status;
252 svn_error_t *
253 svn_io_check_resolved_path(const char *path,
254 svn_node_kind_t *kind,
255 apr_pool_t *pool)
257 svn_boolean_t ignored;
258 return io_check_path(path, TRUE, &ignored, kind, pool);
261 svn_error_t *
262 svn_io_check_path(const char *path,
263 svn_node_kind_t *kind,
264 apr_pool_t *pool)
266 svn_boolean_t ignored;
267 return io_check_path(path, FALSE, &ignored, kind, pool);
270 svn_error_t *
271 svn_io_check_special_path(const char *path,
272 svn_node_kind_t *kind,
273 svn_boolean_t *is_special,
274 apr_pool_t *pool)
276 return io_check_path(path, FALSE, is_special, kind, pool);
279 struct temp_file_cleanup_s
281 apr_pool_t *pool;
282 const char *name;
286 static apr_status_t
287 temp_file_plain_cleanup_handler(void *baton)
289 struct temp_file_cleanup_s *b = baton;
290 apr_status_t apr_err = APR_SUCCESS;
292 if (b->name)
294 apr_err = apr_file_remove(b->name, b->pool);
295 WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->name, b->pool));
298 return apr_err;
302 static apr_status_t
303 temp_file_child_cleanup_handler(void *baton)
305 struct temp_file_cleanup_s *b = baton;
307 apr_pool_cleanup_kill(b->pool, b,
308 temp_file_plain_cleanup_handler);
310 return APR_SUCCESS;
314 svn_error_t *
315 svn_io_open_unique_file2(apr_file_t **f,
316 const char **unique_name_p,
317 const char *path,
318 const char *suffix,
319 svn_io_file_del_t delete_when,
320 apr_pool_t *pool)
322 unsigned int i;
323 apr_file_t *file;
324 const char *unique_name;
325 const char *unique_name_apr;
326 struct temp_file_cleanup_s *baton = NULL;
328 assert(f || unique_name_p);
330 if (delete_when == svn_io_file_del_on_pool_cleanup)
332 baton = apr_palloc(pool, sizeof(*baton));
334 baton->pool = pool;
335 baton->name = NULL;
337 /* Because cleanups are run LIFO, we need to make sure to register
338 our cleanup before the apr_file_close cleanup:
340 On Windows, you can't remove an open file.
342 apr_pool_cleanup_register(pool, baton, temp_file_plain_cleanup_handler,
343 temp_file_child_cleanup_handler);
346 for (i = 1; i <= 99999; i++)
348 apr_status_t apr_err;
349 apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL
350 | APR_BUFFERED | APR_BINARY);
352 if (delete_when == svn_io_file_del_on_close)
353 flag |= APR_DELONCLOSE;
355 /* Special case the first attempt -- if we can avoid having a
356 generated numeric portion at all, that's best. So first we
357 try with just the suffix; then future tries add a number
358 before the suffix. (A do-while loop could avoid the repeated
359 conditional, but it's not worth the clarity loss.)
361 If the first attempt fails, the first number will be "2".
362 This is good, since "1" would misleadingly imply that
363 the second attempt was actually the first... and if someone's
364 got conflicts on their conflicts, we probably don't want to
365 add to their confusion :-). */
366 if (i == 1)
367 unique_name = apr_psprintf(pool, "%s%s", path, suffix);
368 else
369 unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
371 /* Hmmm. Ideally, we would append to a native-encoding buf
372 before starting iteration, then convert back to UTF-8 for
373 return. But I suppose that would make the appending code
374 sensitive to i18n in a way it shouldn't be... Oh well. */
375 SVN_ERR(svn_path_cstring_from_utf8(&unique_name_apr, unique_name,
376 pool));
378 apr_err = file_open(&file, unique_name_apr, flag,
379 APR_OS_DEFAULT, pool);
381 if (APR_STATUS_IS_EEXIST(apr_err))
382 continue;
383 else if (apr_err)
385 /* On Win32, CreateFile failswith an "Access Denied" error
386 code, rather than "File Already Exists", if the colliding
387 name belongs to a directory. */
388 if (APR_STATUS_IS_EACCES(apr_err))
390 apr_finfo_t finfo;
391 apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
392 APR_FINFO_TYPE, pool);
394 if (!apr_err_2
395 && (finfo.filetype == APR_DIR))
396 continue;
398 /* Else ignore apr_err_2; better to fall through and
399 return the original error. */
402 if (f) *f = NULL;
403 if (unique_name_p) *unique_name_p = NULL;
404 return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
405 svn_path_local_style(unique_name, pool));
407 else
409 if (delete_when == svn_io_file_del_on_pool_cleanup)
410 baton->name = unique_name_apr;
412 if (f)
413 *f = file;
414 else
415 apr_file_close(file);
416 if (unique_name_p) *unique_name_p = unique_name;
418 return SVN_NO_ERROR;
422 if (f) *f = NULL;
423 if (unique_name_p) *unique_name_p = NULL;
424 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
425 NULL,
426 _("Unable to make name for '%s'"),
427 svn_path_local_style(path, pool));
430 svn_error_t *
431 svn_io_open_unique_file(apr_file_t **f,
432 const char **unique_name_p,
433 const char *path,
434 const char *suffix,
435 svn_boolean_t delete_on_close,
436 apr_pool_t *pool)
438 return svn_io_open_unique_file2(f, unique_name_p,
439 path, suffix,
440 delete_on_close
441 ? svn_io_file_del_on_close
442 : svn_io_file_del_none,
443 pool);
446 svn_error_t *
447 svn_io_create_unique_link(const char **unique_name_p,
448 const char *path,
449 const char *dest,
450 const char *suffix,
451 apr_pool_t *pool)
453 #ifdef HAVE_SYMLINK
454 unsigned int i;
455 const char *unique_name;
456 const char *unique_name_apr;
457 const char *dest_apr;
458 int rv;
459 #ifdef AS400_UTF8
460 const char *dest_apr_ebcdic;
461 #endif
463 SVN_ERR(svn_path_cstring_from_utf8(&dest_apr, dest, pool));
465 #ifdef AS400_UTF8
466 /* On OS400 with UTF support a native cstring is UTF-8, but
467 * symlink() *really* needs EBCDIC paths. */
468 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&dest_apr_ebcdic, dest_apr,
469 (const char*)0, pool));
470 dest_apr = dest_apr_ebcdic;
471 #endif
473 for (i = 1; i <= 99999; i++)
475 apr_status_t apr_err;
477 /* Special case the first attempt -- if we can avoid having a
478 generated numeric portion at all, that's best. So first we
479 try with just the suffix; then future tries add a number
480 before the suffix. (A do-while loop could avoid the repeated
481 conditional, but it's not worth the clarity loss.)
483 If the first attempt fails, the first number will be "2".
484 This is good, since "1" would misleadingly imply that
485 the second attempt was actually the first... and if someone's
486 got conflicts on their conflicts, we probably don't want to
487 add to their confusion :-). */
488 if (i == 1)
489 unique_name = apr_psprintf(pool, "%s%s", path, suffix);
490 else
491 unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
493 /* Hmmm. Ideally, we would append to a native-encoding buf
494 before starting iteration, then convert back to UTF-8 for
495 return. But I suppose that would make the appending code
496 sensitive to i18n in a way it shouldn't be... Oh well. */
497 #ifndef AS400_UTF8
498 SVN_ERR(svn_path_cstring_from_utf8(&unique_name_apr, unique_name,
499 pool));
500 #else
501 /* On OS400 with UTF support a native cstring is UTF-8,
502 * but symlink() *really* needs an EBCDIC path. */
503 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&unique_name_apr, unique_name,
504 (const char*)0, pool));
505 #endif
507 do {
508 rv = symlink(dest_apr, unique_name_apr);
509 } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
511 apr_err = apr_get_os_error();
513 if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err))
514 continue;
515 else if (rv == -1 && apr_err)
517 /* On Win32, CreateFile fails with an "Access Denied" error
518 code, rather than "File Already Exists", if the colliding
519 name belongs to a directory. */
520 if (APR_STATUS_IS_EACCES(apr_err))
522 apr_finfo_t finfo;
523 apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
524 APR_FINFO_TYPE, pool);
526 if (!apr_err_2
527 && (finfo.filetype == APR_DIR))
528 continue;
530 /* Else ignore apr_err_2; better to fall through and
531 return the original error. */
534 *unique_name_p = NULL;
535 return svn_error_wrap_apr(apr_err,
536 _("Can't create symbolic link '%s'"),
537 svn_path_local_style(unique_name, pool));
539 else
541 *unique_name_p = unique_name;
542 return SVN_NO_ERROR;
546 *unique_name_p = NULL;
547 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
548 NULL,
549 _("Unable to make name for '%s'"),
550 svn_path_local_style(path, pool));
551 #else
552 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
553 _("Symbolic links are not supported on this "
554 "platform"));
555 #endif
558 svn_error_t *
559 svn_io_read_link(svn_string_t **dest,
560 const char *path,
561 apr_pool_t *pool)
563 #ifdef HAVE_READLINK
564 svn_string_t dest_apr;
565 const char *path_apr;
566 char buf[1025];
567 int rv;
568 #ifdef AS400_UTF8
569 const char *buf_utf8;
570 #endif
572 #ifndef AS400_UTF8
573 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
574 #else
575 /* On OS400 with UTF support a native cstring is UTF-8, but
576 * readlink() *really* needs an EBCDIC path. */
577 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&path_apr, path, (const char*)0,
578 pool));
579 #endif
580 do {
581 rv = readlink(path_apr, buf, sizeof(buf) - 1);
582 } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
584 if (rv == -1)
585 return svn_error_wrap_apr
586 (apr_get_os_error(), _("Can't read contents of link"));
588 buf[rv] = '\0';
589 dest_apr.data = buf;
590 dest_apr.len = rv;
592 #ifndef AS400_UTF8
593 /* ### Cast needed, one of these interfaces is wrong */
594 SVN_ERR(svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr,
595 pool));
596 #else
597 /* The buf filled by readline() is ebcdic encoded
598 * despite V5R4's UTF support. */
599 SVN_ERR(svn_utf_cstring_to_utf8_ex2(&buf_utf8, dest_apr.data,
600 (const char *)0, pool));
601 *dest = svn_string_create(buf_utf8, pool);
602 #endif
604 return SVN_NO_ERROR;
605 #else
606 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
607 _("Symbolic links are not supported on this "
608 "platform"));
609 #endif
613 svn_error_t *
614 svn_io_copy_link(const char *src,
615 const char *dst,
616 apr_pool_t *pool)
619 #ifdef HAVE_READLINK
620 svn_string_t *link_dest;
621 const char *dst_tmp;
623 /* Notice what the link is pointing at... */
624 SVN_ERR(svn_io_read_link(&link_dest, src, pool));
626 /* Make a tmp-link pointing at the same thing. */
627 SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data,
628 ".tmp", pool));
630 /* Move the tmp-link to link. */
631 return svn_io_file_rename(dst_tmp, dst, pool);
633 #else
634 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
635 _("Symbolic links are not supported on this "
636 "platform"));
637 #endif
641 svn_error_t *
642 svn_io_temp_dir(const char **dir,
643 apr_pool_t *pool)
645 apr_status_t apr_err = apr_temp_dir_get(dir, pool);
647 if (apr_err)
648 return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory"));
650 *dir = svn_path_canonicalize(*dir, pool);
652 return svn_path_cstring_to_utf8(dir, *dir, pool);
658 /*** Creating, copying and appending files. ***/
660 /* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary
661 * allocations.
663 * NOTE: We don't use apr_copy_file() for this, since it takes filenames
664 * as parameters. Since we want to copy to a temporary file
665 * and rename for atomicity (see below), this would require an extra
666 * close/open pair, which can be expensive, especially on
667 * remote file systems.
670 * Also, On OS400 apr_file_copy() attempts to convert the contents of
671 * the source file from its CCSID to the CCSID of the destination
672 * file. This may corrupt the destination file's contents if the
673 * files' CCSIDs differ from each other and/or the system CCSID.
674 * (See comments for file_open() for more info on CCSIDs.)
676 static apr_status_t
677 copy_contents(apr_file_t *from_file,
678 apr_file_t *to_file,
679 apr_pool_t *pool)
681 /* Copy bytes till the cows come home. */
682 while (1)
684 char buf[SVN__STREAM_CHUNK_SIZE];
685 apr_size_t bytes_this_time = sizeof(buf);
686 apr_status_t read_err;
687 apr_status_t write_err;
689 /* Read 'em. */
690 read_err = apr_file_read(from_file, buf, &bytes_this_time);
691 if (read_err && !APR_STATUS_IS_EOF(read_err))
693 return read_err;
696 /* Write 'em. */
697 write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL);
698 if (write_err)
700 return write_err;
703 if (read_err && APR_STATUS_IS_EOF(read_err))
705 /* Return the results of this close: an error, or success. */
706 return APR_SUCCESS;
709 /* NOTREACHED */
713 svn_error_t *
714 svn_io_copy_file(const char *src,
715 const char *dst,
716 svn_boolean_t copy_perms,
717 apr_pool_t *pool)
719 apr_file_t *from_file, *to_file;
720 apr_status_t apr_err;
721 const char *src_apr, *dst_tmp_apr;
722 const char *dst_tmp;
723 svn_error_t *err, *err2;
725 SVN_ERR(svn_path_cstring_from_utf8(&src_apr, src, pool));
727 SVN_ERR(svn_io_file_open(&from_file, src, APR_READ | APR_BINARY,
728 APR_OS_DEFAULT, pool));
730 /* For atomicity, we copy to a tmp file and then rename the tmp
731 file over the real destination. */
733 SVN_ERR(svn_io_open_unique_file2(&to_file, &dst_tmp, dst, ".tmp",
734 svn_io_file_del_none, pool));
735 SVN_ERR(svn_path_cstring_from_utf8(&dst_tmp_apr, dst_tmp, pool));
737 apr_err = copy_contents(from_file, to_file, pool);
739 if (apr_err)
741 err = svn_error_wrap_apr
742 (apr_err, _("Can't copy '%s' to '%s'"),
743 svn_path_local_style(src, pool),
744 svn_path_local_style(dst_tmp, pool));
746 else
747 err = NULL;
749 err2 = svn_io_file_close(from_file, pool);
750 if (! err)
751 err = err2;
752 else
753 svn_error_clear(err2);
754 err2 = svn_io_file_close(to_file, pool);
755 if (! err)
756 err = err2;
757 else
758 svn_error_clear(err2);
759 if (err)
761 apr_err = apr_file_remove(dst_tmp_apr, pool);
762 WIN32_RETRY_LOOP(apr_err, apr_file_remove(dst_tmp_apr, pool));
763 return err;
766 /* If copying perms, set the perms on dst_tmp now, so they will be
767 atomically inherited in the upcoming rename. But note that we
768 had to wait until now to set perms, because if they say
769 read-only, then we'd have failed filling dst_tmp's contents. */
771 /* ### FIXME: apr_file_copy with perms may fail on Win32. We need a
772 platform-specific implementation to get the permissions right. */
773 #ifndef WIN32
774 if (copy_perms)
776 apr_file_t *s;
777 apr_finfo_t finfo;
779 SVN_ERR(svn_io_file_open(&s, src, APR_READ, APR_OS_DEFAULT, pool));
780 SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, s, pool));
781 SVN_ERR(svn_io_file_close(s, pool));
783 apr_err = apr_file_perms_set(dst_tmp_apr, finfo.protection);
785 /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL
786 here under normal circumstances, because the perms themselves
787 came from a call to apr_file_info_get(), and we already know
788 this is the non-Win32 case. But if it does happen, it's not
789 an error. */
790 if ((apr_err != APR_SUCCESS)
791 && (apr_err != APR_INCOMPLETE)
792 && (apr_err != APR_ENOTIMPL))
794 return svn_error_wrap_apr
795 (apr_err, _("Can't set permissions on '%s'"),
796 svn_path_local_style(dst_tmp, pool));
799 #endif /* ! WIN32 */
801 return svn_io_file_rename(dst_tmp, dst, pool);
805 svn_error_t *
806 svn_io_append_file(const char *src, const char *dst, apr_pool_t *pool)
808 apr_status_t apr_err;
809 const char *src_apr, *dst_apr;
811 SVN_ERR(svn_path_cstring_from_utf8(&src_apr, src, pool));
812 SVN_ERR(svn_path_cstring_from_utf8(&dst_apr, dst, pool));
814 apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool);
816 if (apr_err)
817 return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"),
818 svn_path_local_style(src, pool),
819 svn_path_local_style(dst, pool));
821 return SVN_NO_ERROR;
825 svn_error_t *svn_io_copy_dir_recursively(const char *src,
826 const char *dst_parent,
827 const char *dst_basename,
828 svn_boolean_t copy_perms,
829 svn_cancel_func_t cancel_func,
830 void *cancel_baton,
831 apr_pool_t *pool)
833 svn_node_kind_t kind;
834 apr_status_t status;
835 const char *dst_path;
836 apr_dir_t *this_dir;
837 apr_finfo_t this_entry;
838 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
840 /* Make a subpool for recursion */
841 apr_pool_t *subpool = svn_pool_create(pool);
843 /* The 'dst_path' is simply dst_parent/dst_basename */
844 dst_path = svn_path_join(dst_parent, dst_basename, pool);
846 /* Sanity checks: SRC and DST_PARENT are directories, and
847 DST_BASENAME doesn't already exist in DST_PARENT. */
848 SVN_ERR(svn_io_check_path(src, &kind, subpool));
849 if (kind != svn_node_dir)
850 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
851 _("Source '%s' is not a directory"),
852 svn_path_local_style(src, pool));
854 SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
855 if (kind != svn_node_dir)
856 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
857 _("Destination '%s' is not a directory"),
858 svn_path_local_style(dst_parent, pool));
860 SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
861 if (kind != svn_node_none)
862 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
863 _("Destination '%s' already exists"),
864 svn_path_local_style(dst_path, pool));
866 /* Create the new directory. */
867 /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
868 SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool));
870 /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
871 SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
873 for (status = apr_dir_read(&this_entry, flags, this_dir);
874 status == APR_SUCCESS;
875 status = apr_dir_read(&this_entry, flags, this_dir))
877 if ((this_entry.name[0] == '.')
878 && ((this_entry.name[1] == '\0')
879 || ((this_entry.name[1] == '.')
880 && (this_entry.name[2] == '\0'))))
882 continue;
884 else
886 const char *src_target, *entryname_utf8;
888 if (cancel_func)
889 SVN_ERR(cancel_func(cancel_baton));
891 SVN_ERR(svn_path_cstring_to_utf8(&entryname_utf8,
892 this_entry.name, subpool));
893 src_target = svn_path_join(src, entryname_utf8, subpool);
895 if (this_entry.filetype == APR_REG) /* regular file */
897 const char *dst_target = svn_path_join(dst_path, entryname_utf8,
898 subpool);
899 SVN_ERR(svn_io_copy_file(src_target, dst_target,
900 copy_perms, subpool));
902 else if (this_entry.filetype == APR_LNK) /* symlink */
904 const char *dst_target = svn_path_join(dst_path, entryname_utf8,
905 subpool);
906 SVN_ERR(svn_io_copy_link(src_target, dst_target,
907 subpool));
909 else if (this_entry.filetype == APR_DIR) /* recurse */
911 /* Prevent infinite recursion by filtering off our
912 newly created destination path. */
913 if (strcmp(src, dst_parent) == 0
914 && strcmp(entryname_utf8, dst_basename) == 0)
915 continue;
917 SVN_ERR(svn_io_copy_dir_recursively
918 (src_target,
919 dst_path,
920 entryname_utf8,
921 copy_perms,
922 cancel_func,
923 cancel_baton,
924 subpool));
926 /* ### support other APR node types someday?? */
931 if (! (APR_STATUS_IS_ENOENT(status)))
932 return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
933 svn_path_local_style(src, pool));
935 status = apr_dir_close(this_dir);
936 if (status)
937 return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
938 svn_path_local_style(src, pool));
940 /* Free any memory used by recursion */
941 svn_pool_destroy(subpool);
943 return SVN_NO_ERROR;
947 svn_error_t *
948 svn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
950 const char *path_apr;
951 apr_status_t apr_err;
953 if (svn_path_is_empty(path))
954 /* Empty path (current dir) is assumed to always exist,
955 so we do nothing, per docs. */
956 return SVN_NO_ERROR;
958 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
960 apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool);
961 WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr,
962 APR_OS_DEFAULT, pool));
964 if (apr_err)
965 return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"),
966 svn_path_local_style(path, pool));
968 return SVN_NO_ERROR;
971 svn_error_t *svn_io_file_create(const char *file,
972 const char *contents,
973 apr_pool_t *pool)
975 apr_file_t *f;
976 apr_size_t written;
978 SVN_ERR(svn_io_file_open(&f, file,
979 (APR_WRITE | APR_CREATE | APR_EXCL),
980 APR_OS_DEFAULT,
981 pool));
982 SVN_ERR(svn_io_file_write_full(f, contents, strlen(contents),
983 &written, pool));
984 SVN_ERR(svn_io_file_close(f, pool));
986 return SVN_NO_ERROR;
989 svn_error_t *svn_io_dir_file_copy(const char *src_path,
990 const char *dest_path,
991 const char *file,
992 apr_pool_t *pool)
994 const char *file_dest_path = svn_path_join(dest_path, file, pool);
995 const char *file_src_path = svn_path_join(src_path, file, pool);
997 SVN_ERR(svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool));
999 return SVN_NO_ERROR;
1003 /*** Modtime checking. ***/
1005 svn_error_t *
1006 svn_io_file_affected_time(apr_time_t *apr_time,
1007 const char *path,
1008 apr_pool_t *pool)
1010 apr_finfo_t finfo;
1012 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1014 *apr_time = finfo.mtime;
1016 return SVN_NO_ERROR;
1020 svn_error_t *
1021 svn_io_set_file_affected_time(apr_time_t apr_time,
1022 const char *path,
1023 apr_pool_t *pool)
1025 apr_status_t status;
1026 const char *native_path;
1027 #ifdef AS400
1028 apr_utimbuf_t aubuf;
1029 apr_finfo_t finfo;
1030 #endif
1032 SVN_ERR(svn_path_cstring_from_utf8(&native_path, path, pool));
1034 #ifndef AS400
1035 status = apr_file_mtime_set(native_path, apr_time, pool);
1036 #else
1037 /* apr_file_mtime_set() isn't implemented on OS400, but IBM does provide
1038 * the OS400 specific function apr_utime() which can be used instead. */
1040 /* Get the file's current access time, we don't want to change that,
1041 * just the mod time. */
1042 status = apr_stat(&finfo, native_path, APR_FINFO_ATIME, pool);
1043 if (!status)
1045 aubuf.atime = finfo.atime;
1046 aubuf.mtime = apr_time;
1047 status = apr_utime(native_path, &aubuf);
1049 #endif
1050 if (status)
1051 return svn_error_wrap_apr
1052 (status, _("Can't set access time of '%s'"),
1053 svn_path_local_style(path, pool));
1055 return SVN_NO_ERROR;
1059 svn_error_t *
1060 svn_io_filesizes_different_p(svn_boolean_t *different_p,
1061 const char *file1,
1062 const char *file2,
1063 apr_pool_t *pool)
1065 apr_finfo_t finfo1;
1066 apr_finfo_t finfo2;
1067 apr_status_t status;
1068 const char *file1_apr, *file2_apr;
1070 /* Not using svn_io_stat() because don't want to generate
1071 svn_error_t objects for non-error conditions. */
1073 SVN_ERR(svn_path_cstring_from_utf8(&file1_apr, file1, pool));
1074 SVN_ERR(svn_path_cstring_from_utf8(&file2_apr, file2, pool));
1076 /* Stat both files */
1077 status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool);
1078 if (status)
1080 /* If we got an error stat'ing a file, it could be because the
1081 file was removed... or who knows. Whatever the case, we
1082 don't know if the filesizes are definitely different, so
1083 assume that they're not. */
1084 *different_p = FALSE;
1085 return SVN_NO_ERROR;
1088 status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool);
1089 if (status)
1091 /* See previous comment. */
1092 *different_p = FALSE;
1093 return SVN_NO_ERROR;
1096 /* Examine file sizes */
1097 if (finfo1.size == finfo2.size)
1098 *different_p = FALSE;
1099 else
1100 *different_p = TRUE;
1102 return SVN_NO_ERROR;
1106 svn_error_t *
1107 svn_io_file_checksum(unsigned char digest[],
1108 const char *file,
1109 apr_pool_t *pool)
1111 struct apr_md5_ctx_t context;
1112 apr_file_t *f = NULL;
1113 svn_error_t *err;
1114 char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
1115 apr_size_t len;
1117 /* ### The apr_md5 functions return apr_status_t, but they only
1118 return success, and really, what could go wrong? So below, we
1119 ignore their return values. */
1121 apr_md5_init(&context);
1123 SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool));
1125 len = SVN__STREAM_CHUNK_SIZE;
1126 err = svn_io_file_read(f, buf, &len, pool);
1127 while (! err)
1129 apr_md5_update(&context, buf, len);
1130 len = SVN__STREAM_CHUNK_SIZE;
1131 err = svn_io_file_read(f, buf, &len, pool);
1134 if (err && ! APR_STATUS_IS_EOF(err->apr_err))
1135 return err;
1136 svn_error_clear(err);
1138 SVN_ERR(svn_io_file_close(f, pool));
1140 apr_md5_final(digest, &context);
1142 return SVN_NO_ERROR;
1147 /*** Permissions and modes. ***/
1149 #ifndef WIN32
1150 /* Given the file specified by PATH_APR, attempt to create an
1151 identical version of it owned by the current user. This is done by
1152 moving it to a temporary location, copying the file back to its old
1153 path, then deleting the temporarily moved version. All temporary
1154 allocations are done in POOL. */
1155 static svn_error_t *
1156 reown_file(const char *path_apr,
1157 apr_pool_t *pool)
1159 const char *unique_name;
1161 SVN_ERR(svn_io_open_unique_file2(NULL, &unique_name, path_apr,
1162 ".tmp", svn_io_file_del_none, pool));
1163 SVN_ERR(svn_io_file_rename(path_apr, unique_name, pool));
1164 SVN_ERR(svn_io_copy_file(unique_name, path_apr, TRUE, pool));
1165 SVN_ERR(svn_io_remove_file(unique_name, pool));
1167 return SVN_NO_ERROR;
1170 /* Determine what the read-write PERMS for PATH should be by ORing
1171 together the permissions of PATH and the permissions of a temporary
1172 file that we create. Unfortunately, this is the only way to
1173 determine which combination of write bits (User/Group/World) should
1174 be set to restore a file from read-only to read-write. Make
1175 temporary allocations in POOL. */
1176 static svn_error_t *
1177 get_default_file_perms(const char *path, apr_fileperms_t *perms,
1178 apr_pool_t *pool)
1180 apr_status_t status;
1181 apr_finfo_t tmp_finfo, finfo;
1182 apr_file_t *fd;
1183 const char *tmp_path;
1184 const char *apr_path;
1186 /* Get the perms for a newly created file to find out what write
1187 * bits should be set. */
1188 SVN_ERR(svn_io_open_unique_file2(&fd, &tmp_path, path,
1189 ".tmp", svn_io_file_del_on_close, pool));
1190 status = apr_stat(&tmp_finfo, tmp_path, APR_FINFO_PROT, pool);
1191 if (status)
1192 return svn_error_wrap_apr(status, _("Can't get default file perms "
1193 "for file at '%s' (file stat error)"),
1194 path);
1195 apr_file_close(fd);
1197 /* Get the perms for the original file so we'll have any other bits
1198 * that were already set (like the execute bits, for example). */
1199 SVN_ERR(svn_path_cstring_from_utf8(&apr_path, path, pool));
1200 status = apr_file_open(&fd, apr_path, APR_READ | APR_BINARY,
1201 APR_OS_DEFAULT, pool);
1202 if (status)
1203 return svn_error_wrap_apr(status, _("Can't open file at '%s'"), path);
1205 status = apr_stat(&finfo, apr_path, APR_FINFO_PROT, pool);
1206 if (status)
1207 return svn_error_wrap_apr(status, _("Can't get file perms for file at "
1208 "'%s' (file stat error)"), path);
1209 apr_file_close(fd);
1211 /* Glom the perms together. */
1212 *perms = tmp_finfo.protection | finfo.protection;
1213 return SVN_NO_ERROR;
1216 /* This is a helper function for the svn_io_set_file_read* functions
1217 that attempts to honor the users umask when dealing with
1218 permission changes. It is a no-op when invoked on a symlink. */
1219 static svn_error_t *
1220 io_set_file_perms(const char *path,
1221 svn_boolean_t change_readwrite,
1222 svn_boolean_t enable_write,
1223 svn_boolean_t change_executable,
1224 svn_boolean_t executable,
1225 svn_boolean_t ignore_enoent,
1226 apr_pool_t *pool)
1228 apr_status_t status;
1229 const char *path_apr;
1230 apr_finfo_t finfo;
1231 apr_fileperms_t perms_to_set;
1233 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
1235 /* Try to change only a minimal amount of the perms first
1236 by getting the current perms and adding bits
1237 only on where read perms are granted. If this fails
1238 fall through to just setting file attributes. */
1239 status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool);
1240 if (status)
1242 if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1243 return SVN_NO_ERROR;
1244 else if (status != APR_ENOTIMPL)
1245 return svn_error_wrap_apr(status,
1246 _("Can't change perms of file '%s'"),
1247 svn_path_local_style(path, pool));
1248 return SVN_NO_ERROR;
1251 if (finfo.filetype == APR_LNK)
1252 return SVN_NO_ERROR;
1254 perms_to_set = finfo.protection;
1255 if (change_readwrite)
1257 if (enable_write) /* Make read-write. */
1258 SVN_ERR(get_default_file_perms(path, &perms_to_set, pool));
1259 else
1261 if (finfo.protection & APR_UREAD)
1262 perms_to_set &= ~APR_UWRITE;
1263 if (finfo.protection & APR_GREAD)
1264 perms_to_set &= ~APR_GWRITE;
1265 if (finfo.protection & APR_WREAD)
1266 perms_to_set &= ~APR_WWRITE;
1270 if (change_executable)
1272 if (executable)
1274 if (finfo.protection & APR_UREAD)
1275 perms_to_set |= APR_UEXECUTE;
1276 if (finfo.protection & APR_GREAD)
1277 perms_to_set |= APR_GEXECUTE;
1278 if (finfo.protection & APR_WREAD)
1279 perms_to_set |= APR_WEXECUTE;
1281 else
1283 if (finfo.protection & APR_UREAD)
1284 perms_to_set &= ~APR_UEXECUTE;
1285 if (finfo.protection & APR_GREAD)
1286 perms_to_set &= ~APR_GEXECUTE;
1287 if (finfo.protection & APR_WREAD)
1288 perms_to_set &= ~APR_WEXECUTE;
1292 /* If we aren't changing anything then just return, this saves
1293 some system calls and helps with shared working copies */
1294 if (perms_to_set == finfo.protection)
1295 return SVN_NO_ERROR;
1297 status = apr_file_perms_set(path_apr, perms_to_set);
1298 if (!status)
1299 return SVN_NO_ERROR;
1301 if (APR_STATUS_IS_EPERM(status))
1303 /* We don't have permissions to change the
1304 permissions! Try a move, copy, and delete
1305 workaround to see if we can get the file owned by
1306 us. If these succeed, try the permissions set
1307 again.
1309 Note that we only attempt this in the
1310 stat-available path. This assumes that the
1311 move-copy workaround will only be helpful on
1312 platforms that implement apr_stat. */
1313 SVN_ERR(reown_file(path_apr, pool));
1314 status = apr_file_perms_set(path_apr, perms_to_set);
1317 if (!status)
1318 return SVN_NO_ERROR;
1320 if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1321 return SVN_NO_ERROR;
1322 else if (status == APR_ENOTIMPL)
1324 /* At least try to set the attributes. */
1325 apr_fileattrs_t attrs = 0;
1326 apr_fileattrs_t attrs_values = 0;
1328 if (change_readwrite)
1330 attrs = APR_FILE_ATTR_READONLY;
1331 if (!enable_write)
1332 attrs_values = APR_FILE_ATTR_READONLY;
1334 if (change_executable)
1336 attrs = APR_FILE_ATTR_EXECUTABLE;
1337 if (executable)
1338 attrs_values = APR_FILE_ATTR_EXECUTABLE;
1340 status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
1343 return svn_error_wrap_apr(status,
1344 _("Can't change perms of file '%s'"),
1345 svn_path_local_style(path, pool));
1347 #endif
1349 svn_error_t *
1350 svn_io_set_file_read_write_carefully(const char *path,
1351 svn_boolean_t enable_write,
1352 svn_boolean_t ignore_enoent,
1353 apr_pool_t *pool)
1355 if (enable_write)
1356 return svn_io_set_file_read_write(path, ignore_enoent, pool);
1357 return svn_io_set_file_read_only(path, ignore_enoent, pool);
1360 svn_error_t *
1361 svn_io_set_file_read_only(const char *path,
1362 svn_boolean_t ignore_enoent,
1363 apr_pool_t *pool)
1365 /* On Windows, just set the file attributes -- on unix call
1366 our internal function which attempts to honor the umask. */
1367 #ifndef WIN32
1368 return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
1369 ignore_enoent, pool);
1370 #else
1371 apr_status_t status;
1372 const char *path_apr;
1374 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
1376 status = apr_file_attrs_set(path_apr,
1377 APR_FILE_ATTR_READONLY,
1378 APR_FILE_ATTR_READONLY,
1379 pool);
1381 if (status && status != APR_ENOTIMPL)
1382 if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
1383 return svn_error_wrap_apr(status,
1384 _("Can't set file '%s' read-only"),
1385 svn_path_local_style(path, pool));
1387 return SVN_NO_ERROR;
1388 #endif
1392 svn_error_t *
1393 svn_io_set_file_read_write(const char *path,
1394 svn_boolean_t ignore_enoent,
1395 apr_pool_t *pool)
1397 /* On Windows, just set the file attributes -- on unix call
1398 our internal function which attempts to honor the umask. */
1399 #ifndef WIN32
1400 return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
1401 ignore_enoent, pool);
1402 #else
1403 apr_status_t status;
1404 const char *path_apr;
1406 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
1408 status = apr_file_attrs_set(path_apr,
1410 APR_FILE_ATTR_READONLY,
1411 pool);
1413 if (status && status != APR_ENOTIMPL)
1414 if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
1415 return svn_error_wrap_apr(status,
1416 _("Can't set file '%s' read-write"),
1417 svn_path_local_style(path, pool));
1419 return SVN_NO_ERROR;
1420 #endif
1423 svn_error_t *
1424 svn_io_set_file_executable(const char *path,
1425 svn_boolean_t executable,
1426 svn_boolean_t ignore_enoent,
1427 apr_pool_t *pool)
1429 /* On Windows, just exit -- on unix call our internal function
1430 which attempts to honor the umask. */
1431 #ifndef WIN32
1432 return io_set_file_perms(path, FALSE, FALSE, TRUE, executable,
1433 ignore_enoent, pool);
1434 #else
1435 return SVN_NO_ERROR;
1436 #endif
1440 svn_error_t *
1441 svn_io_is_file_executable(svn_boolean_t *executable,
1442 const char *path,
1443 apr_pool_t *pool)
1445 #if defined(APR_HAS_USER) && !defined(WIN32)
1446 apr_finfo_t file_info;
1447 apr_status_t apr_err;
1448 apr_uid_t uid;
1449 apr_gid_t gid;
1451 *executable = FALSE;
1453 /* Get file and user info. */
1454 SVN_ERR(svn_io_stat(&file_info, path,
1455 (APR_FINFO_PROT | APR_FINFO_OWNER),
1456 pool));
1457 apr_err = apr_uid_current(&uid, &gid, pool);
1459 if (apr_err)
1460 return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
1462 /* Check executable bit for current user. */
1463 if (apr_uid_compare(uid, file_info.user) == APR_SUCCESS)
1464 *executable = (file_info.protection & APR_UEXECUTE);
1466 else if (apr_gid_compare(gid, file_info.group) == APR_SUCCESS)
1467 *executable = (file_info.protection & APR_GEXECUTE);
1469 else
1470 *executable = (file_info.protection & APR_WEXECUTE);
1472 #else /* defined(WIN32) || !defined(APR_HAS_USER) */
1473 *executable = FALSE;
1474 #endif
1476 return SVN_NO_ERROR;
1480 /*** File locking. ***/
1481 /* Clear all outstanding locks on ARG, an open apr_file_t *. */
1482 static apr_status_t
1483 svn_io__file_clear_and_close(void *arg)
1485 apr_status_t apr_err;
1486 apr_file_t *f = arg;
1488 /* Remove locks. */
1489 apr_err = apr_file_unlock(f);
1490 if (apr_err)
1491 return apr_err;
1493 /* Close the file. */
1494 apr_err = apr_file_close(f);
1495 if (apr_err)
1496 return apr_err;
1498 return 0;
1502 svn_error_t *svn_io_file_lock(const char *lock_file,
1503 svn_boolean_t exclusive,
1504 apr_pool_t *pool)
1506 return svn_io_file_lock2(lock_file, exclusive, FALSE, pool);
1509 svn_error_t *svn_io_file_lock2(const char *lock_file,
1510 svn_boolean_t exclusive,
1511 svn_boolean_t nonblocking,
1512 apr_pool_t *pool)
1514 int locktype = APR_FLOCK_SHARED;
1515 apr_file_t *lockfile_handle;
1516 apr_int32_t flags;
1517 apr_status_t apr_err;
1519 if (exclusive == TRUE)
1520 locktype = APR_FLOCK_EXCLUSIVE;
1522 flags = APR_READ;
1523 if (locktype == APR_FLOCK_EXCLUSIVE)
1524 flags |= APR_WRITE;
1526 if (nonblocking == TRUE)
1527 locktype |= APR_FLOCK_NONBLOCK;
1529 SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags,
1530 APR_OS_DEFAULT,
1531 pool));
1533 /* Get lock on the filehandle. */
1534 apr_err = apr_file_lock(lockfile_handle, locktype);
1535 if (apr_err)
1537 switch (locktype & APR_FLOCK_TYPEMASK)
1539 case APR_FLOCK_SHARED:
1540 return svn_error_wrap_apr
1541 (apr_err, _("Can't get shared lock on file '%s'"),
1542 svn_path_local_style(lock_file, pool));
1543 case APR_FLOCK_EXCLUSIVE:
1544 return svn_error_wrap_apr
1545 (apr_err, _("Can't get exclusive lock on file '%s'"),
1546 svn_path_local_style(lock_file, pool));
1547 default:
1548 /* Cannot happen. */
1549 abort();
1553 apr_pool_cleanup_register(pool, lockfile_handle,
1554 svn_io__file_clear_and_close,
1555 apr_pool_cleanup_null);
1557 return SVN_NO_ERROR;
1562 /* Data consistency/coherency operations. */
1564 static svn_error_t *
1565 do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
1566 const char *msg, const char *msg_no_name,
1567 apr_pool_t *pool);
1569 svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
1570 apr_pool_t *pool)
1572 apr_os_file_t filehand;
1574 /* First make sure that any user-space buffered data is flushed. */
1575 SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
1576 N_("Can't flush file '%s'"),
1577 N_("Can't flush stream"),
1578 pool));
1580 apr_os_file_get(&filehand, file);
1582 /* Call the operating system specific function to actually force the
1583 data to disk. */
1585 #ifdef WIN32
1587 if (! FlushFileBuffers(filehand))
1588 return svn_error_wrap_apr
1589 (apr_get_os_error(), _("Can't flush file to disk"));
1591 #else
1592 int rv;
1594 do {
1595 rv = fsync(filehand);
1596 } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
1598 /* If the file is in a memory filesystem, fsync() may return
1599 EINVAL. Presumably the user knows the risks, and we can just
1600 ignore the error. */
1601 if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error()))
1602 return SVN_NO_ERROR;
1604 if (rv == -1)
1605 return svn_error_wrap_apr
1606 (apr_get_os_error(), _("Can't flush file to disk"));
1608 #endif
1610 return SVN_NO_ERROR;
1615 /* TODO write test for these two functions, then refactor. */
1617 svn_error_t *
1618 svn_stringbuf_from_file2(svn_stringbuf_t **result,
1619 const char *filename,
1620 apr_pool_t *pool)
1622 apr_file_t *f = NULL;
1624 if (filename[0] == '-' && filename[1] == '\0')
1626 apr_status_t apr_err;
1627 if ((apr_err = apr_file_open_stdin(&f, pool)))
1628 return svn_error_wrap_apr(apr_err, _("Can't open stdin"));
1630 else
1632 SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool));
1635 SVN_ERR(svn_stringbuf_from_aprfile(result, f, pool));
1636 SVN_ERR(svn_io_file_close(f, pool));
1637 return SVN_NO_ERROR;
1641 svn_error_t *
1642 svn_stringbuf_from_file(svn_stringbuf_t **result,
1643 const char *filename,
1644 apr_pool_t *pool)
1646 if (filename[0] == '-' && filename[1] == '\0')
1647 return svn_error_create
1648 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1649 _("Reading from stdin is disallowed"));
1650 return svn_stringbuf_from_file2(result, filename, pool);
1654 /* Get the name of FILE, or NULL if FILE is an unnamed stream. */
1655 static svn_error_t *
1656 file_name_get(const char **fname_utf8, apr_file_t *file, apr_pool_t *pool)
1658 apr_status_t apr_err;
1659 const char *fname;
1661 apr_err = apr_file_name_get(&fname, file);
1662 if (apr_err)
1663 return svn_error_wrap_apr(apr_err, _("Can't get file name"));
1665 if (fname)
1666 SVN_ERR(svn_path_cstring_to_utf8(fname_utf8, fname, pool));
1667 else
1668 *fname_utf8 = NULL;
1670 return SVN_NO_ERROR;
1674 svn_error_t *
1675 svn_stringbuf_from_aprfile(svn_stringbuf_t **result,
1676 apr_file_t *file,
1677 apr_pool_t *pool)
1679 apr_size_t len;
1680 svn_error_t *err;
1681 svn_stringbuf_t *res = svn_stringbuf_create("", pool);
1682 char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
1684 /* XXX: We should check the incoming data for being of type binary. */
1686 /* apr_file_read will not return data and eof in the same call. So this loop
1687 * is safe from missing read data. */
1688 len = SVN__STREAM_CHUNK_SIZE;
1689 err = svn_io_file_read(file, buf, &len, pool);
1690 while (! err)
1692 svn_stringbuf_appendbytes(res, buf, len);
1693 len = SVN__STREAM_CHUNK_SIZE;
1694 err = svn_io_file_read(file, buf, &len, pool);
1697 /* Having read all the data we *expect* EOF */
1698 if (err && !APR_STATUS_IS_EOF(err->apr_err))
1699 return err;
1700 svn_error_clear(err);
1702 /* Null terminate the stringbuf. */
1703 res->data[res->len] = 0;
1705 *result = res;
1706 return SVN_NO_ERROR;
1711 /* Deletion. */
1713 svn_error_t *
1714 svn_io_remove_file(const char *path, apr_pool_t *pool)
1716 apr_status_t apr_err;
1717 const char *path_apr;
1719 #ifdef WIN32
1720 /* Set the file writable but only on Windows, because Windows
1721 will not allow us to remove files that are read-only. */
1722 SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
1723 #endif /* WIN32 */
1725 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
1727 apr_err = apr_file_remove(path_apr, pool);
1728 WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr, pool));
1730 if (apr_err)
1731 return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"),
1732 svn_path_local_style(path, pool));
1734 return SVN_NO_ERROR;
1738 svn_error_t *
1739 svn_io_remove_dir(const char *path, apr_pool_t *pool)
1741 return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
1745 Mac OS X has a bug where if you're readding the contents of a
1746 directory via readdir in a loop, and you remove one of the entries in
1747 the directory and the directory has 338 or more files in it you will
1748 skip over some of the entries in the directory. Needless to say,
1749 this causes problems if you are using this kind of loop inside a
1750 function that is recursively deleting a directory, because when you
1751 get around to removing the directory it will still have something in
1754 Similar problem has been observed on FreeBSD.
1756 See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 for more
1757 discussion and an initial solution.
1759 To work around the problem, we do a rewinddir after we delete all files
1760 and see if there's anything left. We repeat the steps untill there's
1761 nothing left to delete.
1763 This workaround causes issues on Windows where delete's are asynchronous,
1764 however, so we never rewind if we're on Windows (the delete says it is
1765 complete, we rewind, we see the same file and try to delete it again,
1766 we fail.
1769 /* Neither windows nor unix allows us to delete a non-empty
1770 directory.
1772 This is a function to perform the equivalent of 'rm -rf'. */
1773 svn_error_t *
1774 svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
1775 svn_cancel_func_t cancel_func, void *cancel_baton,
1776 apr_pool_t *pool)
1778 apr_status_t status;
1779 apr_dir_t *this_dir;
1780 apr_finfo_t this_entry;
1781 apr_pool_t *subpool;
1782 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
1783 const char *path_apr;
1784 int need_rewind;
1786 /* Check for pending cancellation request.
1787 If we need to bail out, do so early. */
1789 if (cancel_func)
1790 SVN_ERR((*cancel_func)(cancel_baton));
1792 /* APR doesn't like "" directories */
1793 if (path[0] == '\0')
1794 path = ".";
1796 /* Convert path to native here and call apr_dir_open directly,
1797 instead of just using svn_io_dir_open, because we're going to
1798 need path_apr later anyway when we remove the dir itself. */
1800 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
1802 status = apr_dir_open(&this_dir, path_apr, pool);
1803 if (status)
1805 /* if the directory doesn't exist, our mission is accomplished */
1806 if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
1807 return SVN_NO_ERROR;
1808 else
1809 return svn_error_wrap_apr(status,
1810 _("Can't open directory '%s'"),
1811 svn_path_local_style(path, pool));
1814 subpool = svn_pool_create(pool);
1818 need_rewind = FALSE;
1820 for (status = apr_dir_read(&this_entry, flags, this_dir);
1821 status == APR_SUCCESS;
1822 status = apr_dir_read(&this_entry, flags, this_dir))
1824 svn_pool_clear(subpool);
1825 if ((this_entry.filetype == APR_DIR)
1826 && ((this_entry.name[0] == '.')
1827 && ((this_entry.name[1] == '\0')
1828 || ((this_entry.name[1] == '.')
1829 && (this_entry.name[2] == '\0')))))
1831 continue;
1833 else /* something other than "." or "..", so proceed */
1835 const char *fullpath, *entry_utf8;
1837 #ifndef WIN32
1838 need_rewind = TRUE;
1839 #endif
1841 SVN_ERR(svn_path_cstring_to_utf8(&entry_utf8, this_entry.name,
1842 subpool));
1844 fullpath = svn_path_join(path, entry_utf8, subpool);
1846 if (this_entry.filetype == APR_DIR)
1848 /* Don't check for cancellation, the callee
1849 will immediately do so */
1850 SVN_ERR(svn_io_remove_dir2(fullpath, FALSE,
1851 cancel_func, cancel_baton,
1852 subpool));
1854 else
1856 svn_error_t *err;
1858 if (cancel_func)
1859 SVN_ERR((*cancel_func)(cancel_baton));
1861 err = svn_io_remove_file(fullpath, subpool);
1862 if (err)
1863 return svn_error_createf
1864 (err->apr_err, err, _("Can't remove '%s'"),
1865 svn_path_local_style(fullpath, subpool));
1870 if (need_rewind)
1872 status = apr_dir_rewind(this_dir);
1873 if (status)
1874 return svn_error_wrap_apr(status, _("Can't rewind directory '%s'"),
1875 svn_path_local_style (path, pool));
1878 while (need_rewind);
1880 svn_pool_destroy(subpool);
1882 if (!APR_STATUS_IS_ENOENT(status))
1883 return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
1884 svn_path_local_style(path, pool));
1886 status = apr_dir_close(this_dir);
1887 if (status)
1888 return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
1889 svn_path_local_style(path, pool));
1891 status = apr_dir_remove(path_apr, pool);
1892 WIN32_RETRY_LOOP(status, apr_dir_remove(path_apr, pool));
1893 if (status)
1894 return svn_error_wrap_apr(status, _("Can't remove '%s'"),
1895 svn_path_local_style(path, pool));
1897 return APR_SUCCESS;
1900 svn_error_t *
1901 svn_io_get_dir_filenames(apr_hash_t **dirents,
1902 const char *path,
1903 apr_pool_t *pool)
1905 apr_status_t status;
1906 apr_dir_t *this_dir;
1907 apr_finfo_t this_entry;
1908 apr_int32_t flags = APR_FINFO_NAME;
1910 *dirents = apr_hash_make(pool);
1912 SVN_ERR(svn_io_dir_open(&this_dir, path, pool));
1914 for (status = apr_dir_read(&this_entry, flags, this_dir);
1915 status == APR_SUCCESS;
1916 status = apr_dir_read(&this_entry, flags, this_dir))
1918 if ((this_entry.name[0] == '.')
1919 && ((this_entry.name[1] == '\0')
1920 || ((this_entry.name[1] == '.')
1921 && (this_entry.name[2] == '\0'))))
1923 continue;
1925 else
1927 const char *name;
1928 SVN_ERR(svn_path_cstring_to_utf8(&name, this_entry.name, pool));
1929 apr_hash_set(*dirents, name, APR_HASH_KEY_STRING, name);
1933 if (! (APR_STATUS_IS_ENOENT(status)))
1934 return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
1935 svn_path_local_style(path, pool));
1937 status = apr_dir_close(this_dir);
1938 if (status)
1939 return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
1940 svn_path_local_style(path, pool));
1942 return SVN_NO_ERROR;
1945 svn_error_t *
1946 svn_io_get_dirents2(apr_hash_t **dirents,
1947 const char *path,
1948 apr_pool_t *pool)
1950 apr_status_t status;
1951 apr_dir_t *this_dir;
1952 apr_finfo_t this_entry;
1953 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
1955 *dirents = apr_hash_make(pool);
1957 SVN_ERR(svn_io_dir_open(&this_dir, path, pool));
1959 for (status = apr_dir_read(&this_entry, flags, this_dir);
1960 status == APR_SUCCESS;
1961 status = apr_dir_read(&this_entry, flags, this_dir))
1963 if ((this_entry.name[0] == '.')
1964 && ((this_entry.name[1] == '\0')
1965 || ((this_entry.name[1] == '.')
1966 && (this_entry.name[2] == '\0'))))
1968 continue;
1970 else
1972 const char *name;
1973 svn_io_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
1975 SVN_ERR(svn_path_cstring_to_utf8(&name, this_entry.name, pool));
1977 map_apr_finfo_to_node_kind(&(dirent->kind),
1978 &(dirent->special),
1979 &this_entry);
1981 apr_hash_set(*dirents, name, APR_HASH_KEY_STRING, dirent);
1985 if (! (APR_STATUS_IS_ENOENT(status)))
1986 return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
1987 svn_path_local_style(path, pool));
1989 status = apr_dir_close(this_dir);
1990 if (status)
1991 return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
1992 svn_path_local_style(path, pool));
1994 return SVN_NO_ERROR;
1997 svn_error_t *
1998 svn_io_get_dirents(apr_hash_t **dirents,
1999 const char *path,
2000 apr_pool_t *pool)
2002 /* Note that in C, padding is not allowed at the beginning of structs,
2003 so this is actually portable, since the kind field of svn_io_dirent_t
2004 is first in that struct. */
2005 return svn_io_get_dirents2(dirents, path, pool);
2008 /* Pool userdata key for the error file passed to svn_io_start_cmd(). */
2009 #define ERRFILE_KEY "svn-io-start-cmd-errfile"
2011 /* Handle an error from the child process (before command execution) by
2012 printing DESC and the error string corresponding to STATUS to stderr. */
2013 static void
2014 handle_child_process_error(apr_pool_t *pool, apr_status_t status,
2015 const char *desc)
2017 char errbuf[256];
2018 apr_file_t *errfile;
2019 void *p;
2021 /* We can't do anything if we get an error here, so just return. */
2022 if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool))
2023 return;
2024 errfile = p;
2026 if (errfile)
2027 /* What we get from APR is in native encoding. */
2028 apr_file_printf(errfile, "%s: %s",
2029 desc, apr_strerror(status, errbuf,
2030 sizeof(errbuf)));
2034 svn_error_t *
2035 svn_io_start_cmd(apr_proc_t *cmd_proc,
2036 const char *path,
2037 const char *cmd,
2038 const char *const *args,
2039 svn_boolean_t inherit,
2040 apr_file_t *infile,
2041 apr_file_t *outfile,
2042 apr_file_t *errfile,
2043 apr_pool_t *pool)
2045 apr_status_t apr_err;
2046 apr_procattr_t *cmdproc_attr;
2047 int num_args;
2048 const char **args_native;
2049 const char *cmd_apr;
2051 /* Create the process attributes. */
2052 apr_err = apr_procattr_create(&cmdproc_attr, pool);
2053 if (apr_err)
2054 return svn_error_wrap_apr
2055 (apr_err, _("Can't create process '%s' attributes"), cmd);
2057 /* Make sure we invoke cmd directly, not through a shell. */
2058 apr_err = apr_procattr_cmdtype_set(cmdproc_attr,
2059 inherit?APR_PROGRAM_PATH:APR_PROGRAM);
2060 if (apr_err)
2061 return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"),
2062 cmd);
2064 /* Set the process's working directory. */
2065 if (path)
2067 const char *path_apr;
2069 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
2070 apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr);
2071 if (apr_err)
2072 return svn_error_wrap_apr
2073 (apr_err, _("Can't set process '%s' directory"), cmd);
2076 /* Use requested inputs and outputs.
2078 ### Unfortunately each of these apr functions creates a pipe and then
2079 overwrites the pipe file descriptor with the descriptor we pass
2080 in. The pipes can then never be closed. This is an APR bug. */
2081 if (infile)
2083 apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL);
2084 if (apr_err)
2085 return svn_error_wrap_apr
2086 (apr_err, _("Can't set process '%s' child input"), cmd);
2088 if (outfile)
2090 apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL);
2091 if (apr_err)
2092 return svn_error_wrap_apr
2093 (apr_err, _("Can't set process '%s' child outfile"), cmd);
2095 if (errfile)
2097 apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL);
2098 if (apr_err)
2099 return svn_error_wrap_apr
2100 (apr_err, _("Can't set process '%s' child errfile"), cmd);
2103 /* Have the child print any problems executing its program to errfile. */
2104 apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool);
2105 if (apr_err)
2106 return svn_error_wrap_apr
2107 (apr_err, _("Can't set process '%s' child errfile for error handler"),
2108 cmd);
2109 apr_err = apr_procattr_child_errfn_set(cmdproc_attr,
2110 handle_child_process_error);
2111 if (apr_err)
2112 return svn_error_wrap_apr
2113 (apr_err, _("Can't set process '%s' error handler"), cmd);
2115 /* Convert cmd and args from UTF-8 */
2116 SVN_ERR(svn_path_cstring_from_utf8(&cmd_apr, cmd, pool));
2117 for (num_args = 0; args[num_args]; num_args++)
2119 args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *));
2120 args_native[num_args] = NULL;
2121 while (num_args--)
2123 /* ### Well, it turns out that on APR on Windows expects all
2124 program args to be in UTF-8. Callers of svn_io_run_cmd
2125 should be aware of that. */
2126 SVN_ERR(svn_path_cstring_from_utf8(&args_native[num_args],
2127 args[num_args],
2128 pool));
2132 /* Start the cmd command. */
2133 apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native, NULL,
2134 cmdproc_attr, pool);
2135 if (apr_err)
2136 return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd);
2138 return SVN_NO_ERROR;
2141 #undef ERRFILE_KEY
2143 svn_error_t *
2144 svn_io_wait_for_cmd(apr_proc_t *cmd_proc,
2145 const char *cmd,
2146 int *exitcode,
2147 apr_exit_why_e *exitwhy,
2148 apr_pool_t *pool)
2150 apr_status_t apr_err;
2151 apr_exit_why_e exitwhy_val;
2152 int exitcode_val;
2154 /* The Win32 apr_proc_wait doesn't set this... */
2155 exitwhy_val = APR_PROC_EXIT;
2157 /* Wait for the cmd command to finish. */
2158 apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT);
2159 if (!APR_STATUS_IS_CHILD_DONE(apr_err))
2160 return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"),
2161 cmd);
2163 if (exitwhy)
2164 *exitwhy = exitwhy_val;
2165 else if (! APR_PROC_CHECK_EXIT(exitwhy_val))
2166 return svn_error_createf
2167 (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2168 _("Process '%s' failed (exitwhy %d)"), cmd, exitwhy_val);
2170 if (exitcode)
2171 *exitcode = exitcode_val;
2172 else if (exitcode_val != 0)
2173 return svn_error_createf
2174 (SVN_ERR_EXTERNAL_PROGRAM, NULL,
2175 _("Process '%s' returned error exitcode %d"), cmd, exitcode_val);
2177 return SVN_NO_ERROR;
2181 svn_error_t *
2182 svn_io_run_cmd(const char *path,
2183 const char *cmd,
2184 const char *const *args,
2185 int *exitcode,
2186 apr_exit_why_e *exitwhy,
2187 svn_boolean_t inherit,
2188 apr_file_t *infile,
2189 apr_file_t *outfile,
2190 apr_file_t *errfile,
2191 apr_pool_t *pool)
2193 apr_proc_t cmd_proc;
2195 SVN_ERR(svn_io_start_cmd(&cmd_proc, path, cmd, args, inherit,
2196 infile, outfile, errfile, pool));
2198 SVN_ERR(svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool));
2200 return SVN_NO_ERROR;
2204 svn_error_t *
2205 svn_io_run_diff(const char *dir,
2206 const char *const *user_args,
2207 int num_user_args,
2208 const char *label1,
2209 const char *label2,
2210 const char *from,
2211 const char *to,
2212 int *pexitcode,
2213 apr_file_t *outfile,
2214 apr_file_t *errfile,
2215 const char *diff_cmd,
2216 apr_pool_t *pool)
2218 const char **args;
2219 int i;
2220 int exitcode;
2221 int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */
2222 const char *diff_utf8;
2223 apr_pool_t *subpool = svn_pool_create(pool);
2225 SVN_ERR(svn_path_cstring_to_utf8(&diff_utf8, diff_cmd, pool));
2227 if (pexitcode == NULL)
2228 pexitcode = &exitcode;
2230 if (user_args != NULL)
2231 nargs += num_user_args;
2232 else
2233 nargs += 1; /* -u */
2235 if (label1 != NULL)
2236 nargs += 2; /* the -L and the label itself */
2237 if (label2 != NULL)
2238 nargs += 2; /* the -L and the label itself */
2240 args = apr_palloc(subpool, nargs * sizeof(char *));
2242 i = 0;
2243 args[i++] = diff_utf8;
2245 if (user_args != NULL)
2247 int j;
2248 for (j = 0; j < num_user_args; ++j)
2249 args[i++] = user_args[j];
2251 else
2252 args[i++] = "-u"; /* assume -u if the user didn't give us any args */
2254 if (label1 != NULL)
2256 args[i++] = "-L";
2257 args[i++] = label1;
2259 if (label2 != NULL)
2261 args[i++] = "-L";
2262 args[i++] = label2;
2265 args[i++] = svn_path_local_style(from, subpool);
2266 args[i++] = svn_path_local_style(to, subpool);
2267 args[i++] = NULL;
2269 assert(i == nargs);
2271 SVN_ERR(svn_io_run_cmd(dir, diff_utf8, args, pexitcode, NULL, TRUE,
2272 NULL, outfile, errfile, subpool));
2274 /* The man page for (GNU) diff describes the return value as:
2276 "An exit status of 0 means no differences were found, 1 means
2277 some differences were found, and 2 means trouble."
2279 A return value of 2 typically occurs when diff cannot read its input
2280 or write to its output, but in any case we probably ought to return an
2281 error for anything other than 0 or 1 as the output is likely to be
2282 corrupt.
2284 if (*pexitcode != 0 && *pexitcode != 1)
2285 return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
2286 _("'%s' returned %d"),
2287 svn_path_local_style(diff_utf8, pool),
2288 *pexitcode);
2290 svn_pool_destroy(subpool);
2292 return SVN_NO_ERROR;
2297 svn_error_t *
2298 svn_io_run_diff3_2(int *exitcode,
2299 const char *dir,
2300 const char *mine,
2301 const char *older,
2302 const char *yours,
2303 const char *mine_label,
2304 const char *older_label,
2305 const char *yours_label,
2306 apr_file_t *merged,
2307 const char *diff3_cmd,
2308 const apr_array_header_t *user_args,
2309 apr_pool_t *pool)
2311 const char **args = apr_palloc(pool,
2312 sizeof(char*) * (13
2313 + (user_args
2314 ? user_args->nelts
2315 : 1)));
2316 const char *diff3_utf8;
2317 #ifndef NDEBUG
2318 int nargs = 12;
2319 #endif
2320 int i = 0;
2322 SVN_ERR(svn_path_cstring_to_utf8(&diff3_utf8, diff3_cmd, pool));
2324 /* Labels fall back to sensible defaults if not specified. */
2325 if (mine_label == NULL)
2326 mine_label = ".working";
2327 if (older_label == NULL)
2328 older_label = ".old";
2329 if (yours_label == NULL)
2330 yours_label = ".new";
2332 /* Set up diff3 command line. */
2333 args[i++] = diff3_utf8;
2334 if (user_args)
2336 int j;
2337 for (j = 0; j < user_args->nelts; ++j)
2338 args[i++] = APR_ARRAY_IDX(user_args, j, const char *);
2339 #ifndef NDEBUG
2340 nargs += user_args->nelts;
2341 #endif
2343 else
2345 args[i++] = "-E"; /* We tried "-A" here, but that caused
2346 overlapping identical changes to
2347 conflict. See issue #682. */
2348 #ifndef NDEBUG
2349 ++nargs;
2350 #endif
2352 args[i++] = "-m";
2353 args[i++] = "-L";
2354 args[i++] = mine_label;
2355 args[i++] = "-L";
2356 args[i++] = older_label; /* note: this label is ignored if
2357 using 2-part markers, which is the
2358 case with "-E". */
2359 args[i++] = "-L";
2360 args[i++] = yours_label;
2361 #ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG
2363 svn_boolean_t has_arg;
2365 /* ### FIXME: we really shouldn't be reading the config here;
2366 instead, the necessary bits should be passed in by the caller.
2367 But should we add another parameter to this function, when the
2368 whole external diff3 thing might eventually go away? */
2369 apr_hash_t *config;
2370 svn_config_t *cfg;
2372 SVN_ERR(svn_config_get_config(&config, pool));
2373 cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
2374 APR_HASH_KEY_STRING) : NULL;
2375 SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS,
2376 SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG,
2377 TRUE));
2378 if (has_arg)
2380 const char *diff_cmd, *diff_utf8;
2381 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
2382 SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF);
2383 SVN_ERR(svn_path_cstring_to_utf8(&diff_utf8, diff_cmd, pool));
2384 args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL);
2385 #ifndef NDEBUG
2386 ++nargs;
2387 #endif
2390 #endif
2391 args[i++] = svn_path_local_style(mine, pool);
2392 args[i++] = svn_path_local_style(older, pool);
2393 args[i++] = svn_path_local_style(yours, pool);
2394 args[i++] = NULL;
2395 assert(i == nargs);
2397 /* Run diff3, output the merged text into the scratch file. */
2398 SVN_ERR(svn_io_run_cmd(dir, diff3_utf8, args,
2399 exitcode, NULL,
2400 TRUE, /* keep environment */
2401 NULL, merged, NULL,
2402 pool));
2404 /* According to the diff3 docs, a '0' means the merge was clean, and
2405 '1' means conflict markers were found. Anything else is real
2406 error. */
2407 if ((*exitcode != 0) && (*exitcode != 1))
2408 return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
2409 _("Error running '%s': exitcode was %d, "
2410 "args were:"
2411 "\nin directory '%s', basenames:\n%s\n%s\n%s"),
2412 svn_path_local_style(diff3_utf8, pool),
2413 *exitcode,
2414 svn_path_local_style(dir, pool),
2415 /* Don't call svn_path_local_style() on
2416 the basenames. We don't want them to
2417 be absolute, and we don't need the
2418 separator conversion. */
2419 mine, older, yours);
2421 return SVN_NO_ERROR;
2424 svn_error_t *
2425 svn_io_run_diff3(const char *dir,
2426 const char *mine,
2427 const char *older,
2428 const char *yours,
2429 const char *mine_label,
2430 const char *older_label,
2431 const char *yours_label,
2432 apr_file_t *merged,
2433 int *exitcode,
2434 const char *diff3_cmd,
2435 apr_pool_t *pool)
2437 return svn_io_run_diff3_2(exitcode, dir, mine, older, yours,
2438 mine_label, older_label, yours_label,
2439 merged, diff3_cmd, NULL, pool);
2443 svn_error_t *
2444 svn_io_parse_mimetypes_file(apr_hash_t **type_map,
2445 const char *mimetypes_file,
2446 apr_pool_t *pool)
2448 svn_error_t *err = SVN_NO_ERROR;
2449 apr_hash_t *types = apr_hash_make(pool);
2450 svn_boolean_t eof = FALSE;
2451 svn_stringbuf_t *buf;
2452 apr_pool_t *subpool = svn_pool_create(pool);
2453 apr_file_t *types_file;
2454 svn_stream_t *mimetypes_stream;
2456 SVN_ERR(svn_io_file_open(&types_file, mimetypes_file,
2457 APR_READ, APR_OS_DEFAULT, pool));
2458 mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool);
2460 while (1)
2462 apr_array_header_t *tokens;
2463 const char *type;
2464 int i;
2466 svn_pool_clear(subpool);
2468 /* Read a line. */
2469 if ((err = svn_stream_readline(mimetypes_stream, &buf,
2470 APR_EOL_STR, &eof, subpool)))
2471 break;
2473 /* Only pay attention to non-empty, non-comment lines. */
2474 if (buf->len)
2476 if (buf->data[0] == '#')
2477 continue;
2479 /* Tokenize (into our return pool). */
2480 tokens = svn_cstring_split(buf->data, " \t", TRUE, pool);
2481 if (tokens->nelts < 2)
2482 continue;
2484 /* The first token in a multi-token line is the media type.
2485 Subsequent tokens are filename extensions associated with
2486 that media type. */
2487 type = APR_ARRAY_IDX(tokens, 0, const char *);
2488 for (i = 1; i < tokens->nelts; i++)
2490 const char *ext = APR_ARRAY_IDX(tokens, i, const char *);
2491 apr_hash_set(types, ext, APR_HASH_KEY_STRING, type);
2494 if (eof)
2495 break;
2497 svn_pool_destroy(subpool);
2499 /* If there was an error above, close the file (ignoring any error
2500 from *that*) and return the originally error. */
2501 if (err)
2503 svn_error_clear(svn_stream_close(mimetypes_stream));
2504 return err;
2507 /* Close the stream (which closes the underlying file, too). */
2508 SVN_ERR(svn_stream_close(mimetypes_stream));
2510 *type_map = types;
2511 return SVN_NO_ERROR;
2515 svn_error_t *
2516 svn_io_detect_mimetype2(const char **mimetype,
2517 const char *file,
2518 apr_hash_t *mimetype_map,
2519 apr_pool_t *pool)
2521 static const char * const generic_binary = "application/octet-stream";
2523 svn_node_kind_t kind;
2524 apr_file_t *fh;
2525 svn_error_t *err;
2526 unsigned char block[1024];
2527 apr_size_t amt_read = sizeof(block);
2529 /* Default return value is NULL. */
2530 *mimetype = NULL;
2532 /* See if this file even exists, and make sure it really is a file. */
2533 SVN_ERR(svn_io_check_path(file, &kind, pool));
2534 if (kind != svn_node_file)
2535 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
2536 _("Can't detect MIME type of non-file '%s'"),
2537 svn_path_local_style(file, pool));
2539 /* If there is a mimetype_map provided, we'll first try to look up
2540 our file's extension in the map. Failing that, we'll run the
2541 heuristic. */
2542 if (mimetype_map)
2544 const char *type_from_map, *path_ext;
2545 svn_path_splitext(NULL, &path_ext, file, pool);
2546 if ((type_from_map = apr_hash_get(mimetype_map, path_ext,
2547 APR_HASH_KEY_STRING)))
2549 *mimetype = type_from_map;
2550 return SVN_NO_ERROR;
2554 SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool));
2556 /* Read a block of data from FILE. */
2557 err = svn_io_file_read(fh, block, &amt_read, pool);
2558 if (err && ! APR_STATUS_IS_EOF(err->apr_err))
2559 return err;
2560 svn_error_clear(err);
2562 /* Now close the file. No use keeping it open any more. */
2563 SVN_ERR(svn_io_file_close(fh, pool));
2566 /* Right now, this function is going to be really stupid. It's
2567 going to examine the first block of data, and make sure that 85%
2568 of the bytes are such that their value is in the ranges 0x07-0x0D
2569 or 0x20-0x7F, and that 100% of those bytes is not 0x00.
2571 If those criteria are not met, we're calling it binary. */
2572 if (amt_read > 0)
2574 apr_size_t i;
2575 int binary_count = 0;
2577 /* Run through the data we've read, counting the 'binary-ish'
2578 bytes. HINT: If we see a 0x00 byte, we'll set our count to its
2579 max and stop reading the file. */
2580 for (i = 0; i < amt_read; i++)
2582 if (block[i] == 0)
2584 binary_count = amt_read;
2585 break;
2587 if ((block[i] < 0x07)
2588 || ((block[i] > 0x0D) && (block[i] < 0x20))
2589 || (block[i] > 0x7F))
2591 binary_count++;
2595 if (((binary_count * 1000) / amt_read) > 850)
2597 *mimetype = generic_binary;
2598 return SVN_NO_ERROR;
2602 return SVN_NO_ERROR;
2606 svn_error_t *
2607 svn_io_detect_mimetype(const char **mimetype,
2608 const char *file,
2609 apr_pool_t *pool)
2611 return svn_io_detect_mimetype2(mimetype, file, NULL, pool);
2614 svn_error_t *
2615 svn_io_file_open(apr_file_t **new_file, const char *fname,
2616 apr_int32_t flag, apr_fileperms_t perm,
2617 apr_pool_t *pool)
2619 const char *fname_apr;
2620 apr_status_t status;
2622 SVN_ERR(svn_path_cstring_from_utf8(&fname_apr, fname, pool));
2623 status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, pool);
2625 if (status)
2626 return svn_error_wrap_apr(status, _("Can't open file '%s'"),
2627 svn_path_local_style(fname, pool));
2628 else
2629 return SVN_NO_ERROR;
2633 static svn_error_t *
2634 do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
2635 const char *msg, const char *msg_no_name,
2636 apr_pool_t *pool)
2638 const char *name;
2639 svn_error_t *err;
2641 if (! status)
2642 return SVN_NO_ERROR;
2644 err = file_name_get(&name, file, pool);
2645 if (err)
2646 name = NULL;
2647 svn_error_clear(err);
2649 if (name)
2650 return svn_error_wrap_apr(status, _(msg),
2651 svn_path_local_style(name, pool));
2652 else
2653 return svn_error_wrap_apr(status, _(msg_no_name));
2657 svn_error_t *
2658 svn_io_file_close(apr_file_t *file, apr_pool_t *pool)
2660 return do_io_file_wrapper_cleanup
2661 (file, apr_file_close(file),
2662 N_("Can't close file '%s'"),
2663 N_("Can't close stream"),
2664 pool);
2668 svn_error_t *
2669 svn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool)
2671 return do_io_file_wrapper_cleanup
2672 (file, apr_file_getc(ch, file),
2673 N_("Can't read file '%s'"),
2674 N_("Can't read stream"),
2675 pool);
2679 svn_error_t *
2680 svn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted,
2681 apr_file_t *file, apr_pool_t *pool)
2683 return do_io_file_wrapper_cleanup
2684 (file, apr_file_info_get(finfo, wanted, file),
2685 N_("Can't get attribute information from file '%s'"),
2686 N_("Can't get attribute information from stream"),
2687 pool);
2691 svn_error_t *
2692 svn_io_file_read(apr_file_t *file, void *buf,
2693 apr_size_t *nbytes, apr_pool_t *pool)
2695 return do_io_file_wrapper_cleanup
2696 (file, apr_file_read(file, buf, nbytes),
2697 N_("Can't read file '%s'"),
2698 N_("Can't read stream"),
2699 pool);
2703 svn_error_t *
2704 svn_io_file_read_full(apr_file_t *file, void *buf,
2705 apr_size_t nbytes, apr_size_t *bytes_read,
2706 apr_pool_t *pool)
2708 return do_io_file_wrapper_cleanup
2709 (file, apr_file_read_full(file, buf, nbytes, bytes_read),
2710 N_("Can't read file '%s'"),
2711 N_("Can't read stream"),
2712 pool);
2716 svn_error_t *
2717 svn_io_file_seek(apr_file_t *file, apr_seek_where_t where,
2718 apr_off_t *offset, apr_pool_t *pool)
2720 return do_io_file_wrapper_cleanup
2721 (file, apr_file_seek(file, where, offset),
2722 N_("Can't set position pointer in file '%s'"),
2723 N_("Can't set position pointer in stream"),
2724 pool);
2728 svn_error_t *
2729 svn_io_file_write(apr_file_t *file, const void *buf,
2730 apr_size_t *nbytes, apr_pool_t *pool)
2732 return do_io_file_wrapper_cleanup
2733 (file, apr_file_write(file, buf, nbytes),
2734 N_("Can't write to file '%s'"),
2735 N_("Can't write to stream"),
2736 pool);
2740 svn_error_t *
2741 svn_io_file_write_full(apr_file_t *file, const void *buf,
2742 apr_size_t nbytes, apr_size_t *bytes_written,
2743 apr_pool_t *pool)
2745 apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written);
2747 #ifdef WIN32
2748 #define MAXBUFSIZE 30*1024
2749 if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY)
2750 && nbytes > MAXBUFSIZE)
2752 apr_size_t bw = 0;
2753 *bytes_written = 0;
2755 do {
2756 rv = apr_file_write_full(file, buf,
2757 nbytes > MAXBUFSIZE ? MAXBUFSIZE : nbytes, &bw);
2758 *bytes_written += bw;
2759 buf = (char *)buf + bw;
2760 nbytes -= bw;
2761 } while (rv == APR_SUCCESS && nbytes > 0);
2763 #undef MAXBUFSIZE
2764 #endif
2766 return do_io_file_wrapper_cleanup
2767 (file, rv,
2768 N_("Can't write to file '%s'"),
2769 N_("Can't write to stream"),
2770 pool);
2774 svn_error_t *
2775 svn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit,
2776 apr_pool_t *pool)
2778 const char *name;
2779 svn_error_t *err;
2780 apr_size_t i;
2781 char c;
2783 for (i = 0; i < *limit; i++)
2785 SVN_ERR(svn_io_file_getc(&c, file, pool));
2786 /* Note: this error could be APR_EOF, which
2787 is totally fine. The caller should be aware of
2788 this. */
2790 if (c == '\n')
2792 buf[i] = '\0';
2793 *limit = i;
2794 return SVN_NO_ERROR;
2796 else
2798 buf[i] = c;
2802 err = file_name_get(&name, file, pool);
2803 if (err)
2804 name = NULL;
2805 svn_error_clear(err);
2807 if (name)
2808 return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
2809 _("Can't read length line in file '%s'"),
2810 svn_path_local_style(name, pool));
2811 else
2812 return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
2813 _("Can't read length line in stream"));
2817 svn_error_t *
2818 svn_io_stat(apr_finfo_t *finfo, const char *fname,
2819 apr_int32_t wanted, apr_pool_t *pool)
2821 apr_status_t status;
2822 const char *fname_apr;
2824 /* APR doesn't like "" directories */
2825 if (fname[0] == '\0')
2826 fname = ".";
2828 SVN_ERR(svn_path_cstring_from_utf8(&fname_apr, fname, pool));
2830 status = apr_stat(finfo, fname_apr, wanted, pool);
2831 if (status)
2832 return svn_error_wrap_apr(status, _("Can't stat '%s'"),
2833 svn_path_local_style(fname, pool));
2835 return SVN_NO_ERROR;
2839 svn_error_t *
2840 svn_io_file_rename(const char *from_path, const char *to_path,
2841 apr_pool_t *pool)
2843 apr_status_t status = APR_SUCCESS;
2844 const char *from_path_apr, *to_path_apr;
2846 #ifdef WIN32
2847 /* Set the destination file writable but only on Windows, because
2848 Windows will not allow us to rename over files that are read-only. */
2849 SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
2850 #endif /* WIN32 */
2852 SVN_ERR(svn_path_cstring_from_utf8(&from_path_apr, from_path, pool));
2853 SVN_ERR(svn_path_cstring_from_utf8(&to_path_apr, to_path, pool));
2855 status = apr_file_rename(from_path_apr, to_path_apr, pool);
2856 WIN32_RETRY_LOOP(status,
2857 apr_file_rename(from_path_apr, to_path_apr, pool));
2859 if (status)
2860 return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"),
2861 svn_path_local_style(from_path, pool),
2862 svn_path_local_style(to_path, pool));
2864 return SVN_NO_ERROR;
2868 svn_error_t *
2869 svn_io_file_move(const char *from_path, const char *to_path,
2870 apr_pool_t *pool)
2872 svn_error_t *err = svn_io_file_rename(from_path, to_path, pool);
2874 if (err && APR_STATUS_IS_EXDEV(err->apr_err))
2876 const char *tmp_to_path;
2878 svn_error_clear(err);
2880 SVN_ERR(svn_io_open_unique_file2(NULL, &tmp_to_path, to_path,
2881 "tmp", svn_io_file_del_none, pool));
2883 err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool);
2884 if (err)
2885 goto failed_tmp;
2887 err = svn_io_file_rename(tmp_to_path, to_path, pool);
2888 if (err)
2889 goto failed_tmp;
2891 err = svn_io_remove_file(from_path, pool);
2892 if (! err)
2893 return SVN_NO_ERROR;
2895 svn_error_clear(svn_io_remove_file(to_path, pool));
2897 return err;
2899 failed_tmp:
2900 svn_error_clear(svn_io_remove_file(tmp_to_path, pool));
2903 return err;
2906 /* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden.
2907 HIDDEN determines if the hidden attribute
2908 should be set on the newly created directory. */
2909 static svn_error_t *
2910 dir_make(const char *path, apr_fileperms_t perm,
2911 svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool)
2913 apr_status_t status;
2914 const char *path_apr;
2916 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
2918 /* APR doesn't like "" directories */
2919 if (path_apr[0] == '\0')
2920 path_apr = ".";
2922 #if (APR_OS_DEFAULT & APR_WSTICKY)
2923 /* The APR shipped with httpd 2.0.50 contains a bug where
2924 APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits.
2925 There is a special case for file creation, but not directory
2926 creation, so directories wind up getting created with the sticky
2927 bit set. (There is no such thing as a setuid directory, and the
2928 setgid bit is apparently ignored at mkdir() time.) If we detect
2929 this problem, work around it by unsetting those bits if we are
2930 passed APR_OS_DEFAULT. */
2931 if (perm == APR_OS_DEFAULT)
2932 perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY);
2933 #endif
2935 status = apr_dir_make(path_apr, perm, pool);
2936 WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
2938 if (status)
2939 return svn_error_wrap_apr(status, _("Can't create directory '%s'"),
2940 svn_path_local_style(path, pool));
2942 #ifdef APR_FILE_ATTR_HIDDEN
2943 if (hidden)
2945 status = apr_file_attrs_set(path_apr,
2946 APR_FILE_ATTR_HIDDEN,
2947 APR_FILE_ATTR_HIDDEN,
2948 pool);
2949 if (status)
2950 return svn_error_wrap_apr(status, _("Can't hide directory '%s'"),
2951 svn_path_local_style(path, pool));
2953 #endif
2955 if (sgid)
2957 apr_finfo_t finfo;
2959 /* Per our contract, don't do error-checking. Some filesystems
2960 * don't support the sgid bit, and that's okay. */
2961 status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool);
2963 if (!status)
2964 apr_file_perms_set(path_apr, finfo.protection | APR_GSETID);
2967 return SVN_NO_ERROR;
2970 svn_error_t *
2971 svn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool)
2973 return dir_make(path, perm, FALSE, FALSE, pool);
2976 svn_error_t *
2977 svn_io_dir_make_hidden(const char *path, apr_fileperms_t perm,
2978 apr_pool_t *pool)
2980 return dir_make(path, perm, TRUE, FALSE, pool);
2983 svn_error_t *
2984 svn_io_dir_make_sgid(const char *path, apr_fileperms_t perm,
2985 apr_pool_t *pool)
2987 return dir_make(path, perm, FALSE, TRUE, pool);
2991 svn_error_t *
2992 svn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool)
2994 apr_status_t status;
2995 const char *dirname_apr;
2997 /* APR doesn't like "" directories */
2998 if (dirname[0] == '\0')
2999 dirname = ".";
3001 SVN_ERR(svn_path_cstring_from_utf8(&dirname_apr, dirname, pool));
3003 status = apr_dir_open(new_dir, dirname_apr, pool);
3004 if (status)
3005 return svn_error_wrap_apr(status, _("Can't open directory '%s'"),
3006 svn_path_local_style(dirname, pool));
3008 return SVN_NO_ERROR;
3012 svn_error_t *
3013 svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool)
3015 apr_status_t status;
3016 const char *dirname_apr;
3018 SVN_ERR(svn_path_cstring_from_utf8(&dirname_apr, dirname, pool));
3020 status = apr_dir_remove(dirname_apr, pool);
3021 WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool));
3022 if (status)
3023 return svn_error_wrap_apr(status, _("Can't remove directory '%s'"),
3024 svn_path_local_style(dirname, pool));
3026 return SVN_NO_ERROR;
3030 svn_error_t *
3031 svn_io_dir_read(apr_finfo_t *finfo,
3032 apr_int32_t wanted,
3033 apr_dir_t *thedir,
3034 apr_pool_t *pool)
3036 apr_status_t status;
3038 status = apr_dir_read(finfo, wanted, thedir);
3040 if (status)
3041 return svn_error_wrap_apr(status, _("Can't read directory"));
3043 if (finfo->fname)
3044 SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool));
3046 if (finfo->name)
3047 SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool));
3049 return SVN_NO_ERROR;
3053 svn_error_t *
3054 svn_io_dir_walk(const char *dirname,
3055 apr_int32_t wanted,
3056 svn_io_walk_func_t walk_func,
3057 void *walk_baton,
3058 apr_pool_t *pool)
3060 apr_status_t apr_err;
3061 apr_dir_t *handle;
3062 apr_pool_t *subpool;
3063 const char *dirname_apr;
3064 apr_finfo_t finfo;
3066 wanted |= APR_FINFO_TYPE | APR_FINFO_NAME;
3068 /* The documentation for apr_dir_read used to state that "." and ".."
3069 will be returned as the first two files, but it doesn't
3070 work that way in practice, in particular ext3 on Linux-2.6 doesn't
3071 follow the rules. For details see
3072 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666
3074 If APR ever does implement "dot-first" then it would be possible to
3075 remove the svn_io_stat and walk_func calls and use the walk_func
3076 inside the loop.
3078 Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is
3079 documented to provide it, so we have to do a bit extra. */
3080 SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool));
3081 SVN_ERR(svn_path_cstring_from_utf8(&finfo.name,
3082 svn_path_basename(dirname, pool),
3083 pool));
3084 finfo.valid |= APR_FINFO_NAME;
3085 SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool));
3087 SVN_ERR(svn_path_cstring_from_utf8(&dirname_apr, dirname, pool));
3089 apr_err = apr_dir_open(&handle, dirname_apr, pool);
3090 if (apr_err)
3091 return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"),
3092 svn_path_local_style(dirname, pool));
3094 /* iteration subpool */
3095 subpool = svn_pool_create(pool);
3097 while (1)
3099 const char *name_utf8;
3100 const char *full_path;
3102 svn_pool_clear(subpool);
3104 apr_err = apr_dir_read(&finfo, wanted, handle);
3105 if (APR_STATUS_IS_ENOENT(apr_err))
3106 break;
3107 else if (apr_err)
3109 return svn_error_wrap_apr
3110 (apr_err, _("Can't read directory entry in '%s'"),
3111 svn_path_local_style(dirname, pool));
3114 if (finfo.filetype == APR_DIR)
3116 if (finfo.name[0] == '.'
3117 && (finfo.name[1] == '\0'
3118 || (finfo.name[1] == '.' && finfo.name[2] == '\0')))
3119 /* skip "." and ".." */
3120 continue;
3122 /* some other directory. recurse. it will be passed to the
3123 callback inside the recursion. */
3124 SVN_ERR(svn_path_cstring_to_utf8(&name_utf8, finfo.name,
3125 subpool));
3126 full_path = svn_path_join(dirname, name_utf8, subpool);
3127 SVN_ERR(svn_io_dir_walk(full_path,
3128 wanted,
3129 walk_func,
3130 walk_baton,
3131 subpool));
3133 else if (finfo.filetype == APR_REG)
3135 /* some other directory. pass it to the callback. */
3136 SVN_ERR(svn_path_cstring_to_utf8(&name_utf8, finfo.name,
3137 subpool));
3138 full_path = svn_path_join(dirname, name_utf8, subpool);
3139 SVN_ERR((*walk_func)(walk_baton,
3140 full_path,
3141 &finfo,
3142 subpool));
3144 /* else:
3145 some other type of file; skip it.
3150 svn_pool_destroy(subpool);
3152 apr_err = apr_dir_close(handle);
3153 if (apr_err)
3154 return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"),
3155 svn_path_local_style(dirname, pool));
3157 return SVN_NO_ERROR;
3163 * Determine if a directory is empty or not.
3164 * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not.
3165 * @param path The directory.
3166 * @param pool Used for temporary allocation.
3167 * @remark If path is not a directory, or some other error occurs,
3168 * then return the appropriate apr status code.
3170 * (This function is written in APR style, in anticipation of
3171 * perhaps someday being moved to APR as 'apr_dir_is_empty'.)
3173 static apr_status_t
3174 dir_is_empty(const char *dir, apr_pool_t *pool)
3176 apr_status_t apr_err;
3177 apr_dir_t *dir_handle;
3178 apr_finfo_t finfo;
3179 apr_status_t retval = APR_SUCCESS;
3181 /* APR doesn't like "" directories */
3182 if (dir[0] == '\0')
3183 dir = ".";
3185 apr_err = apr_dir_open(&dir_handle, dir, pool);
3186 if (apr_err != APR_SUCCESS)
3187 return apr_err;
3189 for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle);
3190 apr_err == APR_SUCCESS;
3191 apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle))
3193 /* Ignore entries for this dir and its parent, robustly.
3194 (APR promises that they'll come first, so technically
3195 this guard could be moved outside the loop. But Ryan Bloom
3196 says he doesn't believe it, and I believe him. */
3197 if (! (finfo.name[0] == '.'
3198 && (finfo.name[1] == '\0'
3199 || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
3201 retval = APR_ENOTEMPTY;
3202 break;
3206 /* Make sure we broke out of the loop for the right reason. */
3207 if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err))
3208 return apr_err;
3210 apr_err = apr_dir_close(dir_handle);
3211 if (apr_err != APR_SUCCESS)
3212 return apr_err;
3214 return retval;
3218 svn_error_t *
3219 svn_io_dir_empty(svn_boolean_t *is_empty_p,
3220 const char *path,
3221 apr_pool_t *pool)
3223 apr_status_t status;
3224 const char *path_apr;
3226 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool));
3228 status = dir_is_empty(path_apr, pool);
3230 if (!status)
3231 *is_empty_p = TRUE;
3232 else if (APR_STATUS_IS_ENOTEMPTY(status))
3233 *is_empty_p = FALSE;
3234 else
3235 return svn_error_wrap_apr(status, _("Can't check directory '%s'"),
3236 svn_path_local_style(path, pool));
3238 return SVN_NO_ERROR;
3243 /*** Version/format files ***/
3245 svn_error_t *
3246 svn_io_write_version_file(const char *path,
3247 int version,
3248 apr_pool_t *pool)
3250 apr_file_t *format_file = NULL;
3251 const char *path_tmp;
3252 const char *format_contents = apr_psprintf(pool, "%d\n", version);
3254 /* We only promise to handle non-negative integers. */
3255 if (version < 0)
3256 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
3257 _("Version %d is not non-negative"), version);
3259 /* Create a temporary file to write the data to */
3260 SVN_ERR(svn_io_open_unique_file2(&format_file, &path_tmp, path, ".tmp",
3261 svn_io_file_del_none, pool));
3263 /* ...dump out our version number string... */
3264 SVN_ERR(svn_io_file_write_full(format_file, format_contents,
3265 strlen(format_contents), NULL, pool));
3267 /* ...and close the file. */
3268 SVN_ERR(svn_io_file_close(format_file, pool));
3270 #ifdef WIN32
3271 /* make the destination writable, but only on Windows, because
3272 Windows does not let us replace read-only files. */
3273 SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
3274 #endif /* WIN32 */
3276 /* rename the temp file as the real destination */
3277 SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
3279 /* And finally remove the perms to make it read only */
3280 SVN_ERR(svn_io_set_file_read_only(path, FALSE, pool));
3282 return SVN_NO_ERROR;
3286 svn_error_t *
3287 svn_io_read_version_file(int *version,
3288 const char *path,
3289 apr_pool_t *pool)
3291 apr_file_t *format_file;
3292 char buf[80];
3293 apr_size_t len;
3295 /* Read a chunk of data from PATH */
3296 SVN_ERR(svn_io_file_open(&format_file, path, APR_READ,
3297 APR_OS_DEFAULT, pool));
3298 len = sizeof(buf);
3299 SVN_ERR(svn_io_file_read(format_file, buf, &len, pool));
3301 /* Close the file. */
3302 SVN_ERR(svn_io_file_close(format_file, pool));
3304 /* If there was no data in PATH, return an error. */
3305 if (len == 0)
3306 return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
3307 _("Reading '%s'"),
3308 svn_path_local_style(path, pool));
3310 /* Check that the first line contains only digits. */
3312 apr_size_t i;
3314 for (i = 0; i < len; ++i)
3316 char c = buf[i];
3318 if (i > 0 && (c == '\r' || c == '\n'))
3319 break;
3320 if (! apr_isdigit(c))
3321 return svn_error_createf
3322 (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
3323 _("First line of '%s' contains non-digit"),
3324 svn_path_local_style(path, pool));
3328 /* Convert to integer. */
3329 *version = atoi(buf);
3331 return SVN_NO_ERROR;
3336 /* Do a byte-for-byte comparison of FILE1 and FILE2. */
3337 static svn_error_t *
3338 contents_identical_p(svn_boolean_t *identical_p,
3339 const char *file1,
3340 const char *file2,
3341 apr_pool_t *pool)
3343 svn_error_t *err1;
3344 svn_error_t *err2;
3345 apr_size_t bytes_read1, bytes_read2;
3346 char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
3347 char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
3348 apr_file_t *file1_h = NULL;
3349 apr_file_t *file2_h = NULL;
3351 SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
3352 pool));
3353 SVN_ERR(svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
3354 pool));
3356 *identical_p = TRUE; /* assume TRUE, until disproved below */
3359 err1 = svn_io_file_read_full(file1_h, buf1,
3360 SVN__STREAM_CHUNK_SIZE, &bytes_read1, pool);
3361 if (err1 && !APR_STATUS_IS_EOF(err1->apr_err))
3362 return err1;
3364 err2 = svn_io_file_read_full(file2_h, buf2,
3365 SVN__STREAM_CHUNK_SIZE, &bytes_read2, pool);
3366 if (err2 && !APR_STATUS_IS_EOF(err2->apr_err))
3368 svn_error_clear(err1);
3369 return err2;
3372 if ((bytes_read1 != bytes_read2)
3373 || (memcmp(buf1, buf2, bytes_read1)))
3375 *identical_p = FALSE;
3376 break;
3378 } while (! err1 && ! err2);
3380 svn_error_clear(err1);
3381 svn_error_clear(err2);
3383 SVN_ERR(svn_io_file_close(file1_h, pool));
3384 SVN_ERR(svn_io_file_close(file2_h, pool));
3386 return SVN_NO_ERROR;
3391 svn_error_t *
3392 svn_io_files_contents_same_p(svn_boolean_t *same,
3393 const char *file1,
3394 const char *file2,
3395 apr_pool_t *pool)
3397 svn_boolean_t q;
3399 SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool));
3401 if (q)
3403 *same = 0;
3404 return SVN_NO_ERROR;
3407 SVN_ERR(contents_identical_p(&q, file1, file2, pool));
3409 if (q)
3410 *same = 1;
3411 else
3412 *same = 0;
3414 return SVN_NO_ERROR;