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