1251881Speter/* 2251881Speter * io.c: shared file reading, writing, and probing code. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#include <stdio.h> 27251881Speter 28251881Speter#ifndef WIN32 29251881Speter#include <unistd.h> 30251881Speter#endif 31251881Speter 32251881Speter#ifndef APR_STATUS_IS_EPERM 33251881Speter#include <errno.h> 34251881Speter#ifdef EPERM 35251881Speter#define APR_STATUS_IS_EPERM(s) ((s) == EPERM) 36251881Speter#else 37251881Speter#define APR_STATUS_IS_EPERM(s) (0) 38251881Speter#endif 39251881Speter#endif 40251881Speter 41251881Speter#include <apr_lib.h> 42251881Speter#include <apr_pools.h> 43251881Speter#include <apr_file_io.h> 44251881Speter#include <apr_file_info.h> 45251881Speter#include <apr_general.h> 46251881Speter#include <apr_strings.h> 47251881Speter#include <apr_portable.h> 48251881Speter#include <apr_md5.h> 49251881Speter 50251881Speter#ifdef WIN32 51251881Speter#include <arch/win32/apr_arch_file_io.h> 52251881Speter#endif 53251881Speter 54251881Speter#include "svn_hash.h" 55251881Speter#include "svn_types.h" 56251881Speter#include "svn_dirent_uri.h" 57251881Speter#include "svn_path.h" 58251881Speter#include "svn_string.h" 59251881Speter#include "svn_error.h" 60251881Speter#include "svn_io.h" 61251881Speter#include "svn_pools.h" 62251881Speter#include "svn_utf.h" 63251881Speter#include "svn_config.h" 64251881Speter#include "svn_private_config.h" 65251881Speter#include "svn_ctype.h" 66251881Speter 67251881Speter#include "private/svn_atomic.h" 68251881Speter#include "private/svn_io_private.h" 69251881Speter 70251881Speter#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS" 71251881Speter 72251881Speter/* 73251881Speter Windows is 'aided' by a number of types of applications that 74251881Speter follow other applications around and open up files they have 75251881Speter changed for various reasons (the most intrusive are virus 76251881Speter scanners). So, if one of these other apps has glommed onto 77251881Speter our file we may get an 'access denied' error. 78251881Speter 79251881Speter This retry loop does not completely solve the problem (who 80251881Speter knows how long the other app is going to hold onto it for), but 81251881Speter goes a long way towards minimizing it. It is not an infinite 82251881Speter loop because there might really be an error. 83251881Speter 84251881Speter Another reason for retrying delete operations on Windows 85251881Speter is that they are asynchronous -- the file or directory is not 86251881Speter actually deleted until the last handle to it is closed. The 87251881Speter retry loop cannot completely solve this problem either, but can 88251881Speter help mitigate it. 89251881Speter*/ 90251881Speter#define RETRY_MAX_ATTEMPTS 100 91251881Speter#define RETRY_INITIAL_SLEEP 1000 92251881Speter#define RETRY_MAX_SLEEP 128000 93251881Speter 94251881Speter#define RETRY_LOOP(err, expr, retry_test, sleep_test) \ 95251881Speter do \ 96251881Speter { \ 97251881Speter apr_status_t os_err = APR_TO_OS_ERROR(err); \ 98251881Speter int sleep_count = RETRY_INITIAL_SLEEP; \ 99251881Speter int retries; \ 100251881Speter for (retries = 0; \ 101251881Speter retries < RETRY_MAX_ATTEMPTS && (retry_test); \ 102251881Speter os_err = APR_TO_OS_ERROR(err)) \ 103251881Speter { \ 104251881Speter if (sleep_test) \ 105251881Speter { \ 106251881Speter ++retries; \ 107251881Speter apr_sleep(sleep_count); \ 108251881Speter if (sleep_count < RETRY_MAX_SLEEP) \ 109251881Speter sleep_count *= 2; \ 110251881Speter } \ 111251881Speter (err) = (expr); \ 112251881Speter } \ 113251881Speter } \ 114251881Speter while (0) 115251881Speter 116251881Speter#if defined(EDEADLK) && APR_HAS_THREADS 117251881Speter#define FILE_LOCK_RETRY_LOOP(err, expr) \ 118251881Speter RETRY_LOOP(err, \ 119251881Speter expr, \ 120251881Speter (APR_STATUS_IS_EINTR(err) || os_err == EDEADLK), \ 121251881Speter (!APR_STATUS_IS_EINTR(err))) 122251881Speter#else 123251881Speter#define FILE_LOCK_RETRY_LOOP(err, expr) \ 124251881Speter RETRY_LOOP(err, \ 125251881Speter expr, \ 126251881Speter (APR_STATUS_IS_EINTR(err)), \ 127251881Speter 0) 128251881Speter#endif 129251881Speter 130251881Speter#ifndef WIN32_RETRY_LOOP 131251881Speter#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP) 132251881Speter#define WIN32_RETRY_LOOP(err, expr) \ 133251881Speter RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED \ 134251881Speter || os_err == ERROR_SHARING_VIOLATION \ 135251881Speter || os_err == ERROR_DIR_NOT_EMPTY), \ 136251881Speter 1) 137251881Speter#else 138251881Speter#define WIN32_RETRY_LOOP(err, expr) ((void)0) 139251881Speter#endif 140251881Speter#endif 141251881Speter 142251881Speter/* Forward declaration */ 143251881Speterstatic apr_status_t 144251881Speterdir_is_empty(const char *dir, apr_pool_t *pool); 145251881Speterstatic APR_INLINE svn_error_t * 146251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status, 147251881Speter const char *msg, const char *msg_no_name, 148251881Speter apr_pool_t *pool); 149251881Speter 150251881Speter/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on 151251881Speter * operating systems where APR always uses utf-8 as native path format */ 152251881Speterstatic svn_error_t * 153251881Spetercstring_to_utf8(const char **path_utf8, 154251881Speter const char *path_apr, 155251881Speter apr_pool_t *pool) 156251881Speter{ 157251881Speter#if defined(WIN32) || defined(DARWIN) 158251881Speter *path_utf8 = path_apr; 159251881Speter return SVN_NO_ERROR; 160251881Speter#else 161251881Speter return svn_path_cstring_to_utf8(path_utf8, path_apr, pool); 162251881Speter#endif 163251881Speter} 164251881Speter 165251881Speter/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on 166251881Speter * operating systems where APR always uses utf-8 as native path format */ 167251881Speterstatic svn_error_t * 168251881Spetercstring_from_utf8(const char **path_apr, 169251881Speter const char *path_utf8, 170251881Speter apr_pool_t *pool) 171251881Speter{ 172251881Speter#if defined(WIN32) || defined(DARWIN) 173251881Speter *path_apr = path_utf8; 174251881Speter return SVN_NO_ERROR; 175251881Speter#else 176251881Speter return svn_path_cstring_from_utf8(path_apr, path_utf8, pool); 177251881Speter#endif 178251881Speter} 179251881Speter 180251881Speter/* Helper function that allows to convert an APR-level PATH to something 181251881Speter * that we can pass the svn_error_wrap_apr. Since we use it in context 182251881Speter * of error reporting, having *some* path info may be more useful than 183251881Speter * having none. Therefore, we use a best effort approach here. 184251881Speter * 185251881Speter * This is different from svn_io_file_name_get in that it uses a different 186251881Speter * signature style and will never fail. 187251881Speter */ 188251881Speterstatic const char * 189251881Spetertry_utf8_from_internal_style(const char *path, apr_pool_t *pool) 190251881Speter{ 191251881Speter svn_error_t *error; 192251881Speter const char *path_utf8; 193251881Speter 194251881Speter /* Special case. */ 195251881Speter if (path == NULL) 196251881Speter return "(NULL)"; 197251881Speter 198251881Speter /* (try to) convert PATH to UTF-8. If that fails, continue with the plain 199251881Speter * PATH because it is the best we have. It may actually be UTF-8 already. 200251881Speter */ 201251881Speter error = cstring_to_utf8(&path_utf8, path, pool); 202251881Speter if (error) 203251881Speter { 204251881Speter /* fallback to best representation we have */ 205251881Speter 206251881Speter svn_error_clear(error); 207251881Speter path_utf8 = path; 208251881Speter } 209251881Speter 210251881Speter /* Toggle (back-)slashes etc. as necessary. 211251881Speter */ 212251881Speter return svn_dirent_local_style(path_utf8, pool); 213251881Speter} 214251881Speter 215251881Speter 216251881Speter/* Set *NAME_P to the UTF-8 representation of directory entry NAME. 217251881Speter * NAME is in the internal encoding used by APR; PARENT is in 218251881Speter * UTF-8 and in internal (not local) style. 219251881Speter * 220251881Speter * Use PARENT only for generating an error string if the conversion 221251881Speter * fails because NAME could not be represented in UTF-8. In that 222251881Speter * case, return a two-level error in which the outer error's message 223251881Speter * mentions PARENT, but the inner error's message does not mention 224251881Speter * NAME (except possibly in hex) since NAME may not be printable. 225251881Speter * Such a compound error at least allows the user to go looking in the 226251881Speter * right directory for the problem. 227251881Speter * 228251881Speter * If there is any other error, just return that error directly. 229251881Speter * 230251881Speter * If there is any error, the effect on *NAME_P is undefined. 231251881Speter * 232251881Speter * *NAME_P and NAME may refer to the same storage. 233251881Speter */ 234251881Speterstatic svn_error_t * 235251881Speterentry_name_to_utf8(const char **name_p, 236251881Speter const char *name, 237251881Speter const char *parent, 238251881Speter apr_pool_t *pool) 239251881Speter{ 240251881Speter#if defined(WIN32) || defined(DARWIN) 241251881Speter *name_p = apr_pstrdup(pool, name); 242251881Speter return SVN_NO_ERROR; 243251881Speter#else 244251881Speter svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool); 245251881Speter if (err && err->apr_err == APR_EINVAL) 246251881Speter { 247251881Speter return svn_error_createf(err->apr_err, err, 248251881Speter _("Error converting entry " 249251881Speter "in directory '%s' to UTF-8"), 250251881Speter svn_dirent_local_style(parent, pool)); 251251881Speter } 252251881Speter return err; 253251881Speter#endif 254251881Speter} 255251881Speter 256251881Speter 257251881Speter 258251881Speterstatic void 259251881Spetermap_apr_finfo_to_node_kind(svn_node_kind_t *kind, 260251881Speter svn_boolean_t *is_special, 261251881Speter apr_finfo_t *finfo) 262251881Speter{ 263251881Speter *is_special = FALSE; 264251881Speter 265251881Speter if (finfo->filetype == APR_REG) 266251881Speter *kind = svn_node_file; 267251881Speter else if (finfo->filetype == APR_DIR) 268251881Speter *kind = svn_node_dir; 269251881Speter else if (finfo->filetype == APR_LNK) 270251881Speter { 271251881Speter *is_special = TRUE; 272251881Speter *kind = svn_node_file; 273251881Speter } 274251881Speter else 275251881Speter *kind = svn_node_unknown; 276251881Speter} 277251881Speter 278251881Speter/* Helper for svn_io_check_path() and svn_io_check_resolved_path(); 279251881Speter essentially the same semantics as those two, with the obvious 280251881Speter interpretation for RESOLVE_SYMLINKS. */ 281251881Speterstatic svn_error_t * 282251881Speterio_check_path(const char *path, 283251881Speter svn_boolean_t resolve_symlinks, 284251881Speter svn_boolean_t *is_special_p, 285251881Speter svn_node_kind_t *kind, 286251881Speter apr_pool_t *pool) 287251881Speter{ 288251881Speter apr_int32_t flags; 289251881Speter apr_finfo_t finfo; 290251881Speter apr_status_t apr_err; 291251881Speter const char *path_apr; 292251881Speter svn_boolean_t is_special = FALSE; 293251881Speter 294251881Speter if (path[0] == '\0') 295251881Speter path = "."; 296251881Speter 297251881Speter /* Not using svn_io_stat() here because we want to check the 298251881Speter apr_err return explicitly. */ 299251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 300251881Speter 301251881Speter flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK); 302251881Speter apr_err = apr_stat(&finfo, path_apr, flags, pool); 303251881Speter 304251881Speter if (APR_STATUS_IS_ENOENT(apr_err)) 305251881Speter *kind = svn_node_none; 306251881Speter else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err)) 307251881Speter *kind = svn_node_none; 308251881Speter else if (apr_err) 309251881Speter return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"), 310251881Speter svn_dirent_local_style(path, pool)); 311251881Speter else 312251881Speter map_apr_finfo_to_node_kind(kind, &is_special, &finfo); 313251881Speter 314251881Speter *is_special_p = is_special; 315251881Speter 316251881Speter return SVN_NO_ERROR; 317251881Speter} 318251881Speter 319251881Speter 320251881Speter/* Wrapper for apr_file_open(), taking an APR-encoded filename. */ 321251881Speterstatic apr_status_t 322251881Speterfile_open(apr_file_t **f, 323251881Speter const char *fname_apr, 324251881Speter apr_int32_t flag, 325251881Speter apr_fileperms_t perm, 326251881Speter svn_boolean_t retry_on_failure, 327251881Speter apr_pool_t *pool) 328251881Speter{ 329251881Speter apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool); 330251881Speter 331251881Speter if (retry_on_failure) 332251881Speter { 333251881Speter WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool)); 334251881Speter } 335251881Speter return status; 336251881Speter} 337251881Speter 338251881Speter 339251881Spetersvn_error_t * 340251881Spetersvn_io_check_resolved_path(const char *path, 341251881Speter svn_node_kind_t *kind, 342251881Speter apr_pool_t *pool) 343251881Speter{ 344251881Speter svn_boolean_t ignored; 345251881Speter return io_check_path(path, TRUE, &ignored, kind, pool); 346251881Speter} 347251881Speter 348251881Spetersvn_error_t * 349251881Spetersvn_io_check_path(const char *path, 350251881Speter svn_node_kind_t *kind, 351251881Speter apr_pool_t *pool) 352251881Speter{ 353251881Speter svn_boolean_t ignored; 354251881Speter return io_check_path(path, FALSE, &ignored, kind, pool); 355251881Speter} 356251881Speter 357251881Spetersvn_error_t * 358251881Spetersvn_io_check_special_path(const char *path, 359251881Speter svn_node_kind_t *kind, 360251881Speter svn_boolean_t *is_special, 361251881Speter apr_pool_t *pool) 362251881Speter{ 363251881Speter return io_check_path(path, FALSE, is_special, kind, pool); 364251881Speter} 365251881Speter 366251881Speterstruct temp_file_cleanup_s 367251881Speter{ 368251881Speter apr_pool_t *pool; 369251881Speter /* The (APR-encoded) full path of the file to be removed, or NULL if 370251881Speter * nothing to do. */ 371251881Speter const char *fname_apr; 372251881Speter}; 373251881Speter 374251881Speter 375251881Speterstatic apr_status_t 376251881Spetertemp_file_plain_cleanup_handler(void *baton) 377251881Speter{ 378251881Speter struct temp_file_cleanup_s *b = baton; 379251881Speter apr_status_t apr_err = APR_SUCCESS; 380251881Speter 381251881Speter if (b->fname_apr) 382251881Speter { 383251881Speter apr_err = apr_file_remove(b->fname_apr, b->pool); 384251881Speter WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool)); 385251881Speter } 386251881Speter 387251881Speter return apr_err; 388251881Speter} 389251881Speter 390251881Speter 391251881Speterstatic apr_status_t 392251881Spetertemp_file_child_cleanup_handler(void *baton) 393251881Speter{ 394251881Speter struct temp_file_cleanup_s *b = baton; 395251881Speter 396251881Speter apr_pool_cleanup_kill(b->pool, b, 397251881Speter temp_file_plain_cleanup_handler); 398251881Speter 399251881Speter return APR_SUCCESS; 400251881Speter} 401251881Speter 402251881Speter 403251881Spetersvn_error_t * 404251881Spetersvn_io_open_uniquely_named(apr_file_t **file, 405251881Speter const char **unique_path, 406251881Speter const char *dirpath, 407251881Speter const char *filename, 408251881Speter const char *suffix, 409251881Speter svn_io_file_del_t delete_when, 410251881Speter apr_pool_t *result_pool, 411251881Speter apr_pool_t *scratch_pool) 412251881Speter{ 413251881Speter const char *path; 414251881Speter unsigned int i; 415251881Speter struct temp_file_cleanup_s *baton = NULL; 416251881Speter 417251881Speter /* At the beginning, we don't know whether unique_path will need 418251881Speter UTF8 conversion */ 419251881Speter svn_boolean_t needs_utf8_conversion = TRUE; 420251881Speter 421251881Speter SVN_ERR_ASSERT(file || unique_path); 422251881Speter 423251881Speter if (dirpath == NULL) 424251881Speter SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool)); 425251881Speter if (filename == NULL) 426251881Speter filename = "tempfile"; 427251881Speter if (suffix == NULL) 428251881Speter suffix = ".tmp"; 429251881Speter 430251881Speter path = svn_dirent_join(dirpath, filename, scratch_pool); 431251881Speter 432251881Speter if (delete_when == svn_io_file_del_on_pool_cleanup) 433251881Speter { 434251881Speter baton = apr_palloc(result_pool, sizeof(*baton)); 435251881Speter 436251881Speter baton->pool = result_pool; 437251881Speter baton->fname_apr = NULL; 438251881Speter 439251881Speter /* Because cleanups are run LIFO, we need to make sure to register 440251881Speter our cleanup before the apr_file_close cleanup: 441251881Speter 442251881Speter On Windows, you can't remove an open file. 443251881Speter */ 444251881Speter apr_pool_cleanup_register(result_pool, baton, 445251881Speter temp_file_plain_cleanup_handler, 446251881Speter temp_file_child_cleanup_handler); 447251881Speter } 448251881Speter 449251881Speter for (i = 1; i <= 99999; i++) 450251881Speter { 451251881Speter const char *unique_name; 452251881Speter const char *unique_name_apr; 453251881Speter apr_file_t *try_file; 454251881Speter apr_status_t apr_err; 455251881Speter apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL 456251881Speter | APR_BUFFERED | APR_BINARY); 457251881Speter 458251881Speter if (delete_when == svn_io_file_del_on_close) 459251881Speter flag |= APR_DELONCLOSE; 460251881Speter 461251881Speter /* Special case the first attempt -- if we can avoid having a 462251881Speter generated numeric portion at all, that's best. So first we 463251881Speter try with just the suffix; then future tries add a number 464251881Speter before the suffix. (A do-while loop could avoid the repeated 465251881Speter conditional, but it's not worth the clarity loss.) 466251881Speter 467251881Speter If the first attempt fails, the first number will be "2". 468251881Speter This is good, since "1" would misleadingly imply that 469251881Speter the second attempt was actually the first... and if someone's 470251881Speter got conflicts on their conflicts, we probably don't want to 471251881Speter add to their confusion :-). */ 472251881Speter if (i == 1) 473251881Speter unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix); 474251881Speter else 475251881Speter unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix); 476251881Speter 477251881Speter /* Hmmm. Ideally, we would append to a native-encoding buf 478251881Speter before starting iteration, then convert back to UTF-8 for 479251881Speter return. But I suppose that would make the appending code 480251881Speter sensitive to i18n in a way it shouldn't be... Oh well. */ 481251881Speter if (needs_utf8_conversion) 482251881Speter { 483251881Speter SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, 484251881Speter scratch_pool)); 485251881Speter if (i == 1) 486251881Speter { 487251881Speter /* The variable parts of unique_name will not require UTF8 488251881Speter conversion. Therefore, if UTF8 conversion had no effect 489251881Speter on it in the first iteration, it won't require conversion 490251881Speter in any future iteration. */ 491251881Speter needs_utf8_conversion = strcmp(unique_name_apr, unique_name); 492251881Speter } 493251881Speter } 494251881Speter else 495251881Speter unique_name_apr = unique_name; 496251881Speter 497251881Speter apr_err = file_open(&try_file, unique_name_apr, flag, 498251881Speter APR_OS_DEFAULT, FALSE, result_pool); 499251881Speter 500251881Speter if (APR_STATUS_IS_EEXIST(apr_err)) 501251881Speter continue; 502251881Speter else if (apr_err) 503251881Speter { 504251881Speter /* On Win32, CreateFile fails with an "Access Denied" error 505251881Speter code, rather than "File Already Exists", if the colliding 506251881Speter name belongs to a directory. */ 507251881Speter if (APR_STATUS_IS_EACCES(apr_err)) 508251881Speter { 509251881Speter apr_finfo_t finfo; 510251881Speter apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, 511251881Speter APR_FINFO_TYPE, scratch_pool); 512251881Speter 513251881Speter if (!apr_err_2 && finfo.filetype == APR_DIR) 514251881Speter continue; 515251881Speter 516251881Speter#ifdef WIN32 517251881Speter apr_err_2 = APR_TO_OS_ERROR(apr_err); 518251881Speter 519251881Speter if (apr_err_2 == ERROR_ACCESS_DENIED || 520251881Speter apr_err_2 == ERROR_SHARING_VIOLATION) 521251881Speter { 522251881Speter /* The file is in use by another process or is hidden; 523251881Speter create a new name, but don't do this 99999 times in 524251881Speter case the folder is not writable */ 525251881Speter i += 797; 526251881Speter continue; 527251881Speter } 528251881Speter#endif 529251881Speter 530251881Speter /* Else fall through and return the original error. */ 531251881Speter } 532251881Speter 533251881Speter if (file) 534251881Speter *file = NULL; 535251881Speter if (unique_path) 536251881Speter *unique_path = NULL; 537251881Speter return svn_error_wrap_apr(apr_err, _("Can't open '%s'"), 538251881Speter svn_dirent_local_style(unique_name, 539251881Speter scratch_pool)); 540251881Speter } 541251881Speter else 542251881Speter { 543251881Speter if (delete_when == svn_io_file_del_on_pool_cleanup) 544251881Speter baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr); 545251881Speter 546251881Speter if (file) 547251881Speter *file = try_file; 548251881Speter else 549251881Speter apr_file_close(try_file); 550251881Speter if (unique_path) 551251881Speter *unique_path = apr_pstrdup(result_pool, unique_name); 552251881Speter 553251881Speter return SVN_NO_ERROR; 554251881Speter } 555251881Speter } 556251881Speter 557251881Speter if (file) 558251881Speter *file = NULL; 559251881Speter if (unique_path) 560251881Speter *unique_path = NULL; 561251881Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 562251881Speter NULL, 563251881Speter _("Unable to make name for '%s'"), 564251881Speter svn_dirent_local_style(path, scratch_pool)); 565251881Speter} 566251881Speter 567251881Spetersvn_error_t * 568251881Spetersvn_io_create_unique_link(const char **unique_name_p, 569251881Speter const char *path, 570251881Speter const char *dest, 571251881Speter const char *suffix, 572251881Speter apr_pool_t *pool) 573251881Speter{ 574251881Speter#ifdef HAVE_SYMLINK 575251881Speter unsigned int i; 576251881Speter const char *unique_name; 577251881Speter const char *unique_name_apr; 578251881Speter const char *dest_apr; 579251881Speter int rv; 580251881Speter 581251881Speter SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool)); 582251881Speter for (i = 1; i <= 99999; i++) 583251881Speter { 584251881Speter apr_status_t apr_err; 585251881Speter 586251881Speter /* Special case the first attempt -- if we can avoid having a 587251881Speter generated numeric portion at all, that's best. So first we 588251881Speter try with just the suffix; then future tries add a number 589251881Speter before the suffix. (A do-while loop could avoid the repeated 590251881Speter conditional, but it's not worth the clarity loss.) 591251881Speter 592251881Speter If the first attempt fails, the first number will be "2". 593251881Speter This is good, since "1" would misleadingly imply that 594251881Speter the second attempt was actually the first... and if someone's 595251881Speter got conflicts on their conflicts, we probably don't want to 596251881Speter add to their confusion :-). */ 597251881Speter if (i == 1) 598251881Speter unique_name = apr_psprintf(pool, "%s%s", path, suffix); 599251881Speter else 600251881Speter unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix); 601251881Speter 602251881Speter /* Hmmm. Ideally, we would append to a native-encoding buf 603251881Speter before starting iteration, then convert back to UTF-8 for 604251881Speter return. But I suppose that would make the appending code 605251881Speter sensitive to i18n in a way it shouldn't be... Oh well. */ 606251881Speter SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool)); 607251881Speter do { 608251881Speter rv = symlink(dest_apr, unique_name_apr); 609251881Speter } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); 610251881Speter 611251881Speter apr_err = apr_get_os_error(); 612251881Speter 613251881Speter if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err)) 614251881Speter continue; 615251881Speter else if (rv == -1 && apr_err) 616251881Speter { 617251881Speter /* On Win32, CreateFile fails with an "Access Denied" error 618251881Speter code, rather than "File Already Exists", if the colliding 619251881Speter name belongs to a directory. */ 620251881Speter if (APR_STATUS_IS_EACCES(apr_err)) 621251881Speter { 622251881Speter apr_finfo_t finfo; 623251881Speter apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, 624251881Speter APR_FINFO_TYPE, pool); 625251881Speter 626251881Speter if (!apr_err_2 627251881Speter && (finfo.filetype == APR_DIR)) 628251881Speter continue; 629251881Speter 630251881Speter /* Else ignore apr_err_2; better to fall through and 631251881Speter return the original error. */ 632251881Speter } 633251881Speter 634251881Speter *unique_name_p = NULL; 635251881Speter return svn_error_wrap_apr(apr_err, 636251881Speter _("Can't create symbolic link '%s'"), 637251881Speter svn_dirent_local_style(unique_name, pool)); 638251881Speter } 639251881Speter else 640251881Speter { 641251881Speter *unique_name_p = unique_name; 642251881Speter return SVN_NO_ERROR; 643251881Speter } 644251881Speter } 645251881Speter 646251881Speter *unique_name_p = NULL; 647251881Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 648251881Speter NULL, 649251881Speter _("Unable to make name for '%s'"), 650251881Speter svn_dirent_local_style(path, pool)); 651251881Speter#else 652251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 653251881Speter _("Symbolic links are not supported on this " 654251881Speter "platform")); 655251881Speter#endif 656251881Speter} 657251881Speter 658251881Spetersvn_error_t * 659251881Spetersvn_io_read_link(svn_string_t **dest, 660251881Speter const char *path, 661251881Speter apr_pool_t *pool) 662251881Speter{ 663251881Speter#ifdef HAVE_READLINK 664251881Speter svn_string_t dest_apr; 665251881Speter const char *path_apr; 666251881Speter char buf[1025]; 667251881Speter ssize_t rv; 668251881Speter 669251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 670251881Speter do { 671251881Speter rv = readlink(path_apr, buf, sizeof(buf) - 1); 672251881Speter } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); 673251881Speter 674251881Speter if (rv == -1) 675251881Speter return svn_error_wrap_apr(apr_get_os_error(), 676251881Speter _("Can't read contents of link")); 677251881Speter 678251881Speter buf[rv] = '\0'; 679251881Speter dest_apr.data = buf; 680251881Speter dest_apr.len = rv; 681251881Speter 682251881Speter /* ### Cast needed, one of these interfaces is wrong */ 683251881Speter return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool); 684251881Speter#else 685251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 686251881Speter _("Symbolic links are not supported on this " 687251881Speter "platform")); 688251881Speter#endif 689251881Speter} 690251881Speter 691251881Speter 692251881Spetersvn_error_t * 693251881Spetersvn_io_copy_link(const char *src, 694251881Speter const char *dst, 695251881Speter apr_pool_t *pool) 696251881Speter 697251881Speter{ 698251881Speter#ifdef HAVE_READLINK 699251881Speter svn_string_t *link_dest; 700251881Speter const char *dst_tmp; 701251881Speter 702251881Speter /* Notice what the link is pointing at... */ 703251881Speter SVN_ERR(svn_io_read_link(&link_dest, src, pool)); 704251881Speter 705251881Speter /* Make a tmp-link pointing at the same thing. */ 706251881Speter SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data, 707251881Speter ".tmp", pool)); 708251881Speter 709251881Speter /* Move the tmp-link to link. */ 710251881Speter return svn_io_file_rename(dst_tmp, dst, pool); 711251881Speter 712251881Speter#else 713251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 714251881Speter _("Symbolic links are not supported on this " 715251881Speter "platform")); 716251881Speter#endif 717251881Speter} 718251881Speter 719251881Speter/* Temporary directory name cache for svn_io_temp_dir() */ 720251881Speterstatic volatile svn_atomic_t temp_dir_init_state = 0; 721251881Speterstatic const char *temp_dir; 722251881Speter 723251881Speter/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */ 724251881Speterstatic svn_error_t * 725251881Speterinit_temp_dir(void *baton, apr_pool_t *scratch_pool) 726251881Speter{ 727251881Speter /* Global pool for the temp path */ 728251881Speter apr_pool_t *global_pool = svn_pool_create(NULL); 729251881Speter const char *dir; 730251881Speter 731251881Speter apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool); 732251881Speter 733251881Speter if (apr_err) 734251881Speter return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory")); 735251881Speter 736251881Speter SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool)); 737251881Speter 738251881Speter dir = svn_dirent_internal_style(dir, scratch_pool); 739251881Speter 740251881Speter SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool)); 741251881Speter 742251881Speter return SVN_NO_ERROR; 743251881Speter} 744251881Speter 745251881Speter 746251881Spetersvn_error_t * 747251881Spetersvn_io_temp_dir(const char **dir, 748251881Speter apr_pool_t *pool) 749251881Speter{ 750251881Speter SVN_ERR(svn_atomic__init_once(&temp_dir_init_state, 751251881Speter init_temp_dir, NULL, pool)); 752251881Speter 753251881Speter *dir = apr_pstrdup(pool, temp_dir); 754251881Speter 755251881Speter return SVN_NO_ERROR; 756251881Speter} 757251881Speter 758251881Speter 759251881Speter 760251881Speter 761251881Speter/*** Creating, copying and appending files. ***/ 762251881Speter 763251881Speter/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary 764251881Speter * allocations. 765251881Speter * 766251881Speter * NOTE: We don't use apr_copy_file() for this, since it takes filenames 767251881Speter * as parameters. Since we want to copy to a temporary file 768251881Speter * and rename for atomicity (see below), this would require an extra 769251881Speter * close/open pair, which can be expensive, especially on 770251881Speter * remote file systems. 771251881Speter */ 772251881Speterstatic apr_status_t 773251881Spetercopy_contents(apr_file_t *from_file, 774251881Speter apr_file_t *to_file, 775251881Speter apr_pool_t *pool) 776251881Speter{ 777251881Speter /* Copy bytes till the cows come home. */ 778251881Speter while (1) 779251881Speter { 780251881Speter char buf[SVN__STREAM_CHUNK_SIZE]; 781251881Speter apr_size_t bytes_this_time = sizeof(buf); 782251881Speter apr_status_t read_err; 783251881Speter apr_status_t write_err; 784251881Speter 785251881Speter /* Read 'em. */ 786251881Speter read_err = apr_file_read(from_file, buf, &bytes_this_time); 787251881Speter if (read_err && !APR_STATUS_IS_EOF(read_err)) 788251881Speter { 789251881Speter return read_err; 790251881Speter } 791251881Speter 792251881Speter /* Write 'em. */ 793251881Speter write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL); 794251881Speter if (write_err) 795251881Speter { 796251881Speter return write_err; 797251881Speter } 798251881Speter 799251881Speter if (read_err && APR_STATUS_IS_EOF(read_err)) 800251881Speter { 801251881Speter /* Return the results of this close: an error, or success. */ 802251881Speter return APR_SUCCESS; 803251881Speter } 804251881Speter } 805251881Speter /* NOTREACHED */ 806251881Speter} 807251881Speter 808251881Speter 809251881Spetersvn_error_t * 810251881Spetersvn_io_copy_file(const char *src, 811251881Speter const char *dst, 812251881Speter svn_boolean_t copy_perms, 813251881Speter apr_pool_t *pool) 814251881Speter{ 815251881Speter apr_file_t *from_file, *to_file; 816251881Speter apr_status_t apr_err; 817251881Speter const char *dst_tmp; 818251881Speter svn_error_t *err; 819251881Speter 820251881Speter /* ### NOTE: sometimes src == dst. In this case, because we copy to a 821251881Speter ### temporary file, and then rename over the top of the destination, 822251881Speter ### the net result is resetting the permissions on src/dst. 823251881Speter ### 824251881Speter ### Note: specifically, this can happen during a switch when the desired 825251881Speter ### permissions for a file change from one branch to another. See 826251881Speter ### switch_tests 17. 827251881Speter ### 828251881Speter ### ... yes, we should avoid copying to the same file, and we should 829251881Speter ### make the "reset perms" explicit. The switch *happens* to work 830251881Speter ### because of this copy-to-temp-then-rename implementation. If it 831251881Speter ### weren't for that, the switch would break. 832251881Speter */ 833251881Speter#ifdef CHECK_FOR_SAME_FILE 834251881Speter if (strcmp(src, dst) == 0) 835251881Speter return SVN_NO_ERROR; 836251881Speter#endif 837251881Speter 838251881Speter SVN_ERR(svn_io_file_open(&from_file, src, APR_READ, 839251881Speter APR_OS_DEFAULT, pool)); 840251881Speter 841251881Speter /* For atomicity, we copy to a tmp file and then rename the tmp 842251881Speter file over the real destination. */ 843251881Speter 844251881Speter SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp, 845251881Speter svn_dirent_dirname(dst, pool), 846251881Speter svn_io_file_del_none, pool, pool)); 847251881Speter 848251881Speter apr_err = copy_contents(from_file, to_file, pool); 849251881Speter 850251881Speter if (apr_err) 851251881Speter { 852251881Speter err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"), 853251881Speter svn_dirent_local_style(src, pool), 854251881Speter svn_dirent_local_style(dst_tmp, pool)); 855251881Speter } 856251881Speter else 857251881Speter err = NULL; 858251881Speter 859251881Speter err = svn_error_compose_create(err, 860251881Speter svn_io_file_close(from_file, pool)); 861251881Speter 862251881Speter err = svn_error_compose_create(err, 863251881Speter svn_io_file_close(to_file, pool)); 864251881Speter 865251881Speter if (err) 866251881Speter { 867251881Speter return svn_error_compose_create( 868251881Speter err, 869251881Speter svn_io_remove_file2(dst_tmp, TRUE, pool)); 870251881Speter } 871251881Speter 872251881Speter /* If copying perms, set the perms on dst_tmp now, so they will be 873251881Speter atomically inherited in the upcoming rename. But note that we 874251881Speter had to wait until now to set perms, because if they say 875251881Speter read-only, then we'd have failed filling dst_tmp's contents. */ 876251881Speter if (copy_perms) 877251881Speter SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool)); 878251881Speter 879251881Speter return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool)); 880251881Speter} 881251881Speter 882251881Speter#if !defined(WIN32) && !defined(__OS2__) 883251881Speter/* Wrapper for apr_file_perms_set(), taking a UTF8-encoded filename. */ 884251881Speterstatic svn_error_t * 885251881Speterfile_perms_set(const char *fname, apr_fileperms_t perms, 886251881Speter apr_pool_t *pool) 887251881Speter{ 888251881Speter const char *fname_apr; 889251881Speter apr_status_t status; 890251881Speter 891251881Speter SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); 892251881Speter 893251881Speter status = apr_file_perms_set(fname_apr, perms); 894251881Speter if (status) 895251881Speter return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"), 896251881Speter fname); 897251881Speter else 898251881Speter return SVN_NO_ERROR; 899251881Speter} 900251881Speter 901251881Speter/* Set permissions PERMS on the FILE. This is a cheaper variant of the 902251881Speter * file_perms_set wrapper() function because no locale-dependent string 903251881Speter * conversion is required. POOL will be used for allocations. 904251881Speter */ 905251881Speterstatic svn_error_t * 906251881Speterfile_perms_set2(apr_file_t* file, apr_fileperms_t perms, apr_pool_t *pool) 907251881Speter{ 908251881Speter const char *fname_apr; 909251881Speter apr_status_t status; 910251881Speter 911251881Speter status = apr_file_name_get(&fname_apr, file); 912251881Speter if (status) 913251881Speter return svn_error_wrap_apr(status, _("Can't get file name")); 914251881Speter 915251881Speter status = apr_file_perms_set(fname_apr, perms); 916251881Speter if (status) 917251881Speter return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"), 918251881Speter try_utf8_from_internal_style(fname_apr, pool)); 919251881Speter else 920251881Speter return SVN_NO_ERROR; 921251881Speter} 922251881Speter 923251881Speter#endif /* !WIN32 && !__OS2__ */ 924251881Speter 925251881Spetersvn_error_t * 926251881Spetersvn_io_copy_perms(const char *src, 927251881Speter const char *dst, 928251881Speter apr_pool_t *pool) 929251881Speter{ 930251881Speter /* ### On Windows or OS/2, apr_file_perms_set always returns APR_ENOTIMPL, 931251881Speter and the path passed to apr_file_perms_set must be encoded 932251881Speter in the platform-specific path encoding; not necessary UTF-8. 933251881Speter We need a platform-specific implementation to get the 934251881Speter permissions right. */ 935251881Speter 936251881Speter#if !defined(WIN32) && !defined(__OS2__) 937251881Speter { 938251881Speter apr_finfo_t finfo; 939251881Speter svn_node_kind_t kind; 940251881Speter svn_boolean_t is_special; 941251881Speter svn_error_t *err; 942251881Speter 943251881Speter /* If DST is a symlink, don't bother copying permissions. */ 944251881Speter SVN_ERR(svn_io_check_special_path(dst, &kind, &is_special, pool)); 945251881Speter if (is_special) 946251881Speter return SVN_NO_ERROR; 947251881Speter 948251881Speter SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_PROT, pool)); 949251881Speter err = file_perms_set(dst, finfo.protection, pool); 950251881Speter if (err) 951251881Speter { 952251881Speter /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL 953251881Speter here under normal circumstances, because the perms themselves 954251881Speter came from a call to apr_file_info_get(), and we already know 955251881Speter this is the non-Win32 case. But if it does happen, it's not 956251881Speter an error. */ 957251881Speter if (APR_STATUS_IS_INCOMPLETE(err->apr_err) || 958251881Speter APR_STATUS_IS_ENOTIMPL(err->apr_err)) 959251881Speter svn_error_clear(err); 960251881Speter else 961251881Speter { 962251881Speter const char *message; 963251881Speter message = apr_psprintf(pool, _("Can't set permissions on '%s'"), 964251881Speter svn_dirent_local_style(dst, pool)); 965251881Speter return svn_error_quick_wrap(err, message); 966251881Speter } 967251881Speter } 968251881Speter } 969251881Speter#endif /* !WIN32 && !__OS2__ */ 970251881Speter 971251881Speter return SVN_NO_ERROR; 972251881Speter} 973251881Speter 974251881Speter 975251881Spetersvn_error_t * 976251881Spetersvn_io_append_file(const char *src, const char *dst, apr_pool_t *pool) 977251881Speter{ 978251881Speter apr_status_t apr_err; 979251881Speter const char *src_apr, *dst_apr; 980251881Speter 981251881Speter SVN_ERR(cstring_from_utf8(&src_apr, src, pool)); 982251881Speter SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool)); 983251881Speter 984251881Speter apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool); 985251881Speter 986251881Speter if (apr_err) 987251881Speter return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"), 988251881Speter svn_dirent_local_style(src, pool), 989251881Speter svn_dirent_local_style(dst, pool)); 990251881Speter 991251881Speter return SVN_NO_ERROR; 992251881Speter} 993251881Speter 994251881Speter 995251881Spetersvn_error_t *svn_io_copy_dir_recursively(const char *src, 996251881Speter const char *dst_parent, 997251881Speter const char *dst_basename, 998251881Speter svn_boolean_t copy_perms, 999251881Speter svn_cancel_func_t cancel_func, 1000251881Speter void *cancel_baton, 1001251881Speter apr_pool_t *pool) 1002251881Speter{ 1003251881Speter svn_node_kind_t kind; 1004251881Speter apr_status_t status; 1005251881Speter const char *dst_path; 1006251881Speter apr_dir_t *this_dir; 1007251881Speter apr_finfo_t this_entry; 1008251881Speter apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; 1009251881Speter 1010251881Speter /* Make a subpool for recursion */ 1011251881Speter apr_pool_t *subpool = svn_pool_create(pool); 1012251881Speter 1013251881Speter /* The 'dst_path' is simply dst_parent/dst_basename */ 1014251881Speter dst_path = svn_dirent_join(dst_parent, dst_basename, pool); 1015251881Speter 1016251881Speter /* Sanity checks: SRC and DST_PARENT are directories, and 1017251881Speter DST_BASENAME doesn't already exist in DST_PARENT. */ 1018251881Speter SVN_ERR(svn_io_check_path(src, &kind, subpool)); 1019251881Speter if (kind != svn_node_dir) 1020251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 1021251881Speter _("Source '%s' is not a directory"), 1022251881Speter svn_dirent_local_style(src, pool)); 1023251881Speter 1024251881Speter SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool)); 1025251881Speter if (kind != svn_node_dir) 1026251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 1027251881Speter _("Destination '%s' is not a directory"), 1028251881Speter svn_dirent_local_style(dst_parent, pool)); 1029251881Speter 1030251881Speter SVN_ERR(svn_io_check_path(dst_path, &kind, subpool)); 1031251881Speter if (kind != svn_node_none) 1032251881Speter return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, 1033251881Speter _("Destination '%s' already exists"), 1034251881Speter svn_dirent_local_style(dst_path, pool)); 1035251881Speter 1036251881Speter /* Create the new directory. */ 1037251881Speter /* ### TODO: copy permissions (needs apr_file_attrs_get()) */ 1038251881Speter SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool)); 1039251881Speter 1040251881Speter /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */ 1041251881Speter SVN_ERR(svn_io_dir_open(&this_dir, src, subpool)); 1042251881Speter 1043251881Speter for (status = apr_dir_read(&this_entry, flags, this_dir); 1044251881Speter status == APR_SUCCESS; 1045251881Speter status = apr_dir_read(&this_entry, flags, this_dir)) 1046251881Speter { 1047251881Speter if ((this_entry.name[0] == '.') 1048251881Speter && ((this_entry.name[1] == '\0') 1049251881Speter || ((this_entry.name[1] == '.') 1050251881Speter && (this_entry.name[2] == '\0')))) 1051251881Speter { 1052251881Speter continue; 1053251881Speter } 1054251881Speter else 1055251881Speter { 1056251881Speter const char *src_target, *entryname_utf8; 1057251881Speter 1058251881Speter if (cancel_func) 1059251881Speter SVN_ERR(cancel_func(cancel_baton)); 1060251881Speter 1061251881Speter SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name, 1062251881Speter src, subpool)); 1063251881Speter src_target = svn_dirent_join(src, entryname_utf8, subpool); 1064251881Speter 1065251881Speter if (this_entry.filetype == APR_REG) /* regular file */ 1066251881Speter { 1067251881Speter const char *dst_target = svn_dirent_join(dst_path, 1068251881Speter entryname_utf8, 1069251881Speter subpool); 1070251881Speter SVN_ERR(svn_io_copy_file(src_target, dst_target, 1071251881Speter copy_perms, subpool)); 1072251881Speter } 1073251881Speter else if (this_entry.filetype == APR_LNK) /* symlink */ 1074251881Speter { 1075251881Speter const char *dst_target = svn_dirent_join(dst_path, 1076251881Speter entryname_utf8, 1077251881Speter subpool); 1078251881Speter SVN_ERR(svn_io_copy_link(src_target, dst_target, 1079251881Speter subpool)); 1080251881Speter } 1081251881Speter else if (this_entry.filetype == APR_DIR) /* recurse */ 1082251881Speter { 1083251881Speter /* Prevent infinite recursion by filtering off our 1084251881Speter newly created destination path. */ 1085251881Speter if (strcmp(src, dst_parent) == 0 1086251881Speter && strcmp(entryname_utf8, dst_basename) == 0) 1087251881Speter continue; 1088251881Speter 1089251881Speter SVN_ERR(svn_io_copy_dir_recursively 1090251881Speter (src_target, 1091251881Speter dst_path, 1092251881Speter entryname_utf8, 1093251881Speter copy_perms, 1094251881Speter cancel_func, 1095251881Speter cancel_baton, 1096251881Speter subpool)); 1097251881Speter } 1098251881Speter /* ### support other APR node types someday?? */ 1099251881Speter 1100251881Speter } 1101251881Speter } 1102251881Speter 1103251881Speter if (! (APR_STATUS_IS_ENOENT(status))) 1104251881Speter return svn_error_wrap_apr(status, _("Can't read directory '%s'"), 1105251881Speter svn_dirent_local_style(src, pool)); 1106251881Speter 1107251881Speter status = apr_dir_close(this_dir); 1108251881Speter if (status) 1109251881Speter return svn_error_wrap_apr(status, _("Error closing directory '%s'"), 1110251881Speter svn_dirent_local_style(src, pool)); 1111251881Speter 1112251881Speter /* Free any memory used by recursion */ 1113251881Speter svn_pool_destroy(subpool); 1114251881Speter 1115251881Speter return SVN_NO_ERROR; 1116251881Speter} 1117251881Speter 1118251881Speter 1119251881Spetersvn_error_t * 1120251881Spetersvn_io_make_dir_recursively(const char *path, apr_pool_t *pool) 1121251881Speter{ 1122251881Speter const char *path_apr; 1123251881Speter apr_status_t apr_err; 1124251881Speter 1125251881Speter if (svn_path_is_empty(path)) 1126251881Speter /* Empty path (current dir) is assumed to always exist, 1127251881Speter so we do nothing, per docs. */ 1128251881Speter return SVN_NO_ERROR; 1129251881Speter 1130251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1131251881Speter 1132251881Speter apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool); 1133251881Speter WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr, 1134251881Speter APR_OS_DEFAULT, pool)); 1135251881Speter 1136251881Speter if (apr_err) 1137251881Speter return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"), 1138251881Speter svn_dirent_local_style(path, pool)); 1139251881Speter 1140251881Speter return SVN_NO_ERROR; 1141251881Speter} 1142251881Speter 1143251881Spetersvn_error_t *svn_io_file_create(const char *file, 1144251881Speter const char *contents, 1145251881Speter apr_pool_t *pool) 1146251881Speter{ 1147251881Speter apr_file_t *f; 1148251881Speter apr_size_t written; 1149251881Speter svn_error_t *err = SVN_NO_ERROR; 1150251881Speter 1151251881Speter SVN_ERR(svn_io_file_open(&f, file, 1152251881Speter (APR_WRITE | APR_CREATE | APR_EXCL), 1153251881Speter APR_OS_DEFAULT, 1154251881Speter pool)); 1155251881Speter if (contents && *contents) 1156251881Speter err = svn_io_file_write_full(f, contents, strlen(contents), 1157251881Speter &written, pool); 1158251881Speter 1159251881Speter 1160251881Speter return svn_error_trace( 1161251881Speter svn_error_compose_create(err, 1162251881Speter svn_io_file_close(f, pool))); 1163251881Speter} 1164251881Speter 1165251881Spetersvn_error_t *svn_io_dir_file_copy(const char *src_path, 1166251881Speter const char *dest_path, 1167251881Speter const char *file, 1168251881Speter apr_pool_t *pool) 1169251881Speter{ 1170251881Speter const char *file_dest_path = svn_dirent_join(dest_path, file, pool); 1171251881Speter const char *file_src_path = svn_dirent_join(src_path, file, pool); 1172251881Speter 1173251881Speter return svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool); 1174251881Speter} 1175251881Speter 1176251881Speter 1177251881Speter/*** Modtime checking. ***/ 1178251881Speter 1179251881Spetersvn_error_t * 1180251881Spetersvn_io_file_affected_time(apr_time_t *apr_time, 1181251881Speter const char *path, 1182251881Speter apr_pool_t *pool) 1183251881Speter{ 1184251881Speter apr_finfo_t finfo; 1185251881Speter 1186251881Speter SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool)); 1187251881Speter 1188251881Speter *apr_time = finfo.mtime; 1189251881Speter 1190251881Speter return SVN_NO_ERROR; 1191251881Speter} 1192251881Speter 1193251881Speter 1194251881Spetersvn_error_t * 1195251881Spetersvn_io_set_file_affected_time(apr_time_t apr_time, 1196251881Speter const char *path, 1197251881Speter apr_pool_t *pool) 1198251881Speter{ 1199251881Speter apr_status_t status; 1200251881Speter const char *native_path; 1201251881Speter 1202251881Speter SVN_ERR(cstring_from_utf8(&native_path, path, pool)); 1203251881Speter status = apr_file_mtime_set(native_path, apr_time, pool); 1204251881Speter 1205251881Speter if (status) 1206251881Speter return svn_error_wrap_apr(status, _("Can't set access time of '%s'"), 1207251881Speter svn_dirent_local_style(path, pool)); 1208251881Speter 1209251881Speter return SVN_NO_ERROR; 1210251881Speter} 1211251881Speter 1212251881Speter 1213251881Spetervoid 1214251881Spetersvn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool) 1215251881Speter{ 1216251881Speter apr_time_t now, then; 1217251881Speter svn_error_t *err; 1218251881Speter char *sleep_env_var; 1219251881Speter 1220251881Speter sleep_env_var = getenv(SVN_SLEEP_ENV_VAR); 1221251881Speter 1222251881Speter if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0) 1223251881Speter return; /* Allow skipping for testing */ 1224251881Speter 1225251881Speter now = apr_time_now(); 1226251881Speter 1227251881Speter /* Calculate 0.02 seconds after the next second wallclock tick. */ 1228251881Speter then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50); 1229251881Speter 1230251881Speter /* Worst case is waiting one second, so we can use that time to determine 1231251881Speter if we can sleep shorter than that */ 1232251881Speter if (path) 1233251881Speter { 1234251881Speter apr_finfo_t finfo; 1235251881Speter 1236251881Speter err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool); 1237251881Speter 1238251881Speter if (err) 1239251881Speter { 1240251881Speter svn_error_clear(err); /* Fall back on original behavior */ 1241251881Speter } 1242251881Speter else if (finfo.mtime % APR_USEC_PER_SEC) 1243251881Speter { 1244251881Speter /* Very simplistic but safe approach: 1245251881Speter If the filesystem has < sec mtime we can be reasonably sure 1246269847Speter that the filesystem has some sub-second resolution. On Windows 1247269847Speter it is likely to be sub-millisecond; on Linux systems it depends 1248269847Speter on the filesystem, ext4 is typically 1ms, 4ms or 10ms resolution. 1249251881Speter 1250251881Speter ## Perhaps find a better algorithm here. This will fail once 1251269847Speter in every 1000 cases on a millisecond precision filesystem 1252269847Speter if the mtime happens to be an exact second. 1253251881Speter 1254251881Speter But better to fail once in every thousand cases than every 1255251881Speter time, like we did before. 1256251881Speter 1257251881Speter Note for further research on algorithm: 1258269847Speter FAT32 has < 1 sec precision on ctime, but 2 sec on mtime. 1259251881Speter 1260269847Speter Linux/ext4 with CONFIG_HZ=250 has high resolution 1261269847Speter apr_time_now and although the filesystem timestamps 1262269847Speter have similar high precision they are only updated with 1263269847Speter a coarser 4ms resolution. */ 1264251881Speter 1265269847Speter /* 10 milliseconds after now. */ 1266269847Speter#ifndef SVN_HI_RES_SLEEP_MS 1267269847Speter#define SVN_HI_RES_SLEEP_MS 10 1268269847Speter#endif 1269269847Speter then = now + apr_time_from_msec(SVN_HI_RES_SLEEP_MS); 1270251881Speter } 1271251881Speter 1272269847Speter /* Remove time taken to do stat() from sleep. */ 1273269847Speter now = apr_time_now(); 1274251881Speter } 1275251881Speter 1276269847Speter if (now >= then) 1277269847Speter return; /* Passing negative values may suspend indefinitely (Windows) */ 1278269847Speter 1279269847Speter /* (t < 1000 will be round to 0 in apr) */ 1280269847Speter if (then - now < 1000) 1281269847Speter apr_sleep(1000); 1282269847Speter else 1283269847Speter apr_sleep(then - now); 1284251881Speter} 1285251881Speter 1286251881Speter 1287251881Spetersvn_error_t * 1288251881Spetersvn_io_filesizes_different_p(svn_boolean_t *different_p, 1289251881Speter const char *file1, 1290251881Speter const char *file2, 1291251881Speter apr_pool_t *pool) 1292251881Speter{ 1293251881Speter apr_finfo_t finfo1; 1294251881Speter apr_finfo_t finfo2; 1295251881Speter apr_status_t status; 1296251881Speter const char *file1_apr, *file2_apr; 1297251881Speter 1298251881Speter /* Not using svn_io_stat() because don't want to generate 1299251881Speter svn_error_t objects for non-error conditions. */ 1300251881Speter 1301251881Speter SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool)); 1302251881Speter SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool)); 1303251881Speter 1304251881Speter /* Stat both files */ 1305251881Speter status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool); 1306251881Speter if (status) 1307251881Speter { 1308251881Speter /* If we got an error stat'ing a file, it could be because the 1309251881Speter file was removed... or who knows. Whatever the case, we 1310251881Speter don't know if the filesizes are definitely different, so 1311251881Speter assume that they're not. */ 1312251881Speter *different_p = FALSE; 1313251881Speter return SVN_NO_ERROR; 1314251881Speter } 1315251881Speter 1316251881Speter status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool); 1317251881Speter if (status) 1318251881Speter { 1319251881Speter /* See previous comment. */ 1320251881Speter *different_p = FALSE; 1321251881Speter return SVN_NO_ERROR; 1322251881Speter } 1323251881Speter 1324251881Speter /* Examine file sizes */ 1325251881Speter if (finfo1.size == finfo2.size) 1326251881Speter *different_p = FALSE; 1327251881Speter else 1328251881Speter *different_p = TRUE; 1329251881Speter 1330251881Speter return SVN_NO_ERROR; 1331251881Speter} 1332251881Speter 1333251881Speter 1334251881Spetersvn_error_t * 1335251881Spetersvn_io_filesizes_three_different_p(svn_boolean_t *different_p12, 1336251881Speter svn_boolean_t *different_p23, 1337251881Speter svn_boolean_t *different_p13, 1338251881Speter const char *file1, 1339251881Speter const char *file2, 1340251881Speter const char *file3, 1341251881Speter apr_pool_t *scratch_pool) 1342251881Speter{ 1343251881Speter apr_finfo_t finfo1, finfo2, finfo3; 1344251881Speter apr_status_t status1, status2, status3; 1345251881Speter const char *file1_apr, *file2_apr, *file3_apr; 1346251881Speter 1347251881Speter /* Not using svn_io_stat() because don't want to generate 1348251881Speter svn_error_t objects for non-error conditions. */ 1349251881Speter 1350251881Speter SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool)); 1351251881Speter SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool)); 1352251881Speter SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool)); 1353251881Speter 1354251881Speter /* Stat all three files */ 1355251881Speter status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool); 1356251881Speter status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool); 1357251881Speter status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool); 1358251881Speter 1359251881Speter /* If we got an error stat'ing a file, it could be because the 1360251881Speter file was removed... or who knows. Whatever the case, we 1361251881Speter don't know if the filesizes are definitely different, so 1362251881Speter assume that they're not. */ 1363251881Speter *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size; 1364251881Speter *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size; 1365251881Speter *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size; 1366251881Speter 1367251881Speter return SVN_NO_ERROR; 1368251881Speter} 1369251881Speter 1370251881Speter 1371251881Spetersvn_error_t * 1372251881Spetersvn_io_file_checksum2(svn_checksum_t **checksum, 1373251881Speter const char *file, 1374251881Speter svn_checksum_kind_t kind, 1375251881Speter apr_pool_t *pool) 1376251881Speter{ 1377251881Speter svn_stream_t *file_stream; 1378251881Speter svn_stream_t *checksum_stream; 1379251881Speter apr_file_t* f; 1380251881Speter 1381251881Speter SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool)); 1382251881Speter file_stream = svn_stream_from_aprfile2(f, FALSE, pool); 1383251881Speter checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind, 1384251881Speter TRUE, pool); 1385251881Speter 1386251881Speter /* Because the checksummed stream will force the reading (and 1387251881Speter checksumming) of all the file's bytes, we can just close the stream 1388251881Speter and let its magic work. */ 1389251881Speter return svn_stream_close(checksum_stream); 1390251881Speter} 1391251881Speter 1392251881Speter 1393251881Spetersvn_error_t * 1394251881Spetersvn_io_file_checksum(unsigned char digest[], 1395251881Speter const char *file, 1396251881Speter apr_pool_t *pool) 1397251881Speter{ 1398251881Speter svn_checksum_t *checksum; 1399251881Speter 1400251881Speter SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool)); 1401251881Speter memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE); 1402251881Speter 1403251881Speter return SVN_NO_ERROR; 1404251881Speter} 1405251881Speter 1406251881Speter 1407251881Speter 1408251881Speter/*** Permissions and modes. ***/ 1409251881Speter 1410251881Speter#if !defined(WIN32) && !defined(__OS2__) 1411251881Speter/* Given the file specified by PATH, attempt to create an 1412251881Speter identical version of it owned by the current user. This is done by 1413251881Speter moving it to a temporary location, copying the file back to its old 1414251881Speter path, then deleting the temporarily moved version. All temporary 1415251881Speter allocations are done in POOL. */ 1416251881Speterstatic svn_error_t * 1417251881Speterreown_file(const char *path, 1418251881Speter apr_pool_t *pool) 1419251881Speter{ 1420251881Speter const char *unique_name; 1421251881Speter 1422251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name, 1423251881Speter svn_dirent_dirname(path, pool), 1424251881Speter svn_io_file_del_none, pool, pool)); 1425251881Speter SVN_ERR(svn_io_file_rename(path, unique_name, pool)); 1426251881Speter SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool)); 1427251881Speter return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool)); 1428251881Speter} 1429251881Speter 1430251881Speter/* Determine what the PERMS for a new file should be by looking at the 1431251881Speter permissions of a temporary file that we create. 1432251881Speter Unfortunately, umask() as defined in POSIX provides no thread-safe way 1433251881Speter to get at the current value of the umask, so what we're doing here is 1434251881Speter the only way we have to determine which combination of write bits 1435251881Speter (User/Group/World) should be set by default. 1436251881Speter Make temporary allocations in SCRATCH_POOL. */ 1437251881Speterstatic svn_error_t * 1438251881Speterget_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool) 1439251881Speter{ 1440251881Speter /* the default permissions as read from the temp folder */ 1441251881Speter static apr_fileperms_t default_perms = 0; 1442251881Speter 1443251881Speter /* Technically, this "racy": Multiple threads may use enter here and 1444251881Speter try to figure out the default permission concurrently. That's fine 1445251881Speter since they will end up with the same results. Even more technical, 1446251881Speter apr_fileperms_t is an atomic type on 32+ bit machines. 1447251881Speter */ 1448251881Speter if (default_perms == 0) 1449251881Speter { 1450251881Speter apr_finfo_t finfo; 1451251881Speter apr_file_t *fd; 1452251881Speter const char *fname_base, *fname; 1453251881Speter apr_uint32_t randomish; 1454251881Speter svn_error_t *err; 1455251881Speter 1456251881Speter /* Get the perms for a newly created file to find out what bits 1457251881Speter should be set. 1458251881Speter 1459251881Speter Explictly delete the file because we want this file to be as 1460251881Speter short-lived as possible since its presence means other 1461251881Speter processes may have to try multiple names. 1462251881Speter 1463251881Speter Using svn_io_open_uniquely_named() here because other tempfile 1464251881Speter creation functions tweak the permission bits of files they create. 1465251881Speter */ 1466251881Speter randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool 1467251881Speter + (apr_uint32_t)apr_time_now()); 1468251881Speter fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish); 1469251881Speter 1470251881Speter SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base, 1471251881Speter NULL, svn_io_file_del_none, 1472251881Speter scratch_pool, scratch_pool)); 1473251881Speter err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool); 1474251881Speter err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool)); 1475251881Speter err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE, 1476251881Speter scratch_pool)); 1477251881Speter SVN_ERR(err); 1478251881Speter *perms = finfo.protection; 1479251881Speter default_perms = finfo.protection; 1480251881Speter } 1481251881Speter else 1482251881Speter *perms = default_perms; 1483251881Speter 1484251881Speter return SVN_NO_ERROR; 1485251881Speter} 1486251881Speter 1487251881Speter/* OR together permission bits of the file FD and the default permissions 1488251881Speter of a file as determined by get_default_file_perms(). Do temporary 1489251881Speter allocations in SCRATCH_POOL. */ 1490251881Speterstatic svn_error_t * 1491251881Spetermerge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms, 1492251881Speter apr_pool_t *scratch_pool) 1493251881Speter{ 1494251881Speter apr_finfo_t finfo; 1495251881Speter apr_fileperms_t default_perms; 1496251881Speter 1497251881Speter SVN_ERR(get_default_file_perms(&default_perms, scratch_pool)); 1498251881Speter SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool)); 1499251881Speter 1500251881Speter /* Glom the perms together. */ 1501251881Speter *perms = default_perms | finfo.protection; 1502251881Speter return SVN_NO_ERROR; 1503251881Speter} 1504251881Speter 1505251881Speter/* This is a helper function for the svn_io_set_file_read* functions 1506251881Speter that attempts to honor the users umask when dealing with 1507251881Speter permission changes. It is a no-op when invoked on a symlink. */ 1508251881Speterstatic svn_error_t * 1509251881Speterio_set_file_perms(const char *path, 1510251881Speter svn_boolean_t change_readwrite, 1511251881Speter svn_boolean_t enable_write, 1512251881Speter svn_boolean_t change_executable, 1513251881Speter svn_boolean_t executable, 1514251881Speter svn_boolean_t ignore_enoent, 1515251881Speter apr_pool_t *pool) 1516251881Speter{ 1517251881Speter apr_status_t status; 1518251881Speter const char *path_apr; 1519251881Speter apr_finfo_t finfo; 1520251881Speter apr_fileperms_t perms_to_set; 1521251881Speter 1522251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1523251881Speter 1524251881Speter /* Try to change only a minimal amount of the perms first 1525251881Speter by getting the current perms and adding bits 1526251881Speter only on where read perms are granted. If this fails 1527251881Speter fall through to just setting file attributes. */ 1528251881Speter status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool); 1529251881Speter if (status) 1530251881Speter { 1531251881Speter if (ignore_enoent && APR_STATUS_IS_ENOENT(status)) 1532251881Speter return SVN_NO_ERROR; 1533251881Speter else if (status != APR_ENOTIMPL) 1534251881Speter return svn_error_wrap_apr(status, 1535251881Speter _("Can't change perms of file '%s'"), 1536251881Speter svn_dirent_local_style(path, pool)); 1537251881Speter return SVN_NO_ERROR; 1538251881Speter } 1539251881Speter 1540251881Speter if (finfo.filetype == APR_LNK) 1541251881Speter return SVN_NO_ERROR; 1542251881Speter 1543251881Speter perms_to_set = finfo.protection; 1544251881Speter if (change_readwrite) 1545251881Speter { 1546251881Speter if (enable_write) /* Make read-write. */ 1547251881Speter { 1548262253Speter /* Tweak the owner bits only. The group/other bits aren't safe to 1549262253Speter * touch because we may end up setting them in undesired ways. */ 1550262253Speter perms_to_set |= (APR_UREAD|APR_UWRITE); 1551251881Speter } 1552251881Speter else 1553251881Speter { 1554251881Speter if (finfo.protection & APR_UREAD) 1555251881Speter perms_to_set &= ~APR_UWRITE; 1556251881Speter if (finfo.protection & APR_GREAD) 1557251881Speter perms_to_set &= ~APR_GWRITE; 1558251881Speter if (finfo.protection & APR_WREAD) 1559251881Speter perms_to_set &= ~APR_WWRITE; 1560251881Speter } 1561251881Speter } 1562251881Speter 1563251881Speter if (change_executable) 1564251881Speter { 1565251881Speter if (executable) 1566251881Speter { 1567251881Speter if (finfo.protection & APR_UREAD) 1568251881Speter perms_to_set |= APR_UEXECUTE; 1569251881Speter if (finfo.protection & APR_GREAD) 1570251881Speter perms_to_set |= APR_GEXECUTE; 1571251881Speter if (finfo.protection & APR_WREAD) 1572251881Speter perms_to_set |= APR_WEXECUTE; 1573251881Speter } 1574251881Speter else 1575251881Speter { 1576251881Speter if (finfo.protection & APR_UREAD) 1577251881Speter perms_to_set &= ~APR_UEXECUTE; 1578251881Speter if (finfo.protection & APR_GREAD) 1579251881Speter perms_to_set &= ~APR_GEXECUTE; 1580251881Speter if (finfo.protection & APR_WREAD) 1581251881Speter perms_to_set &= ~APR_WEXECUTE; 1582251881Speter } 1583251881Speter } 1584251881Speter 1585251881Speter /* If we aren't changing anything then just return, this saves 1586251881Speter some system calls and helps with shared working copies */ 1587251881Speter if (perms_to_set == finfo.protection) 1588251881Speter return SVN_NO_ERROR; 1589251881Speter 1590251881Speter status = apr_file_perms_set(path_apr, perms_to_set); 1591251881Speter if (!status) 1592251881Speter return SVN_NO_ERROR; 1593251881Speter 1594251881Speter if (APR_STATUS_IS_EPERM(status)) 1595251881Speter { 1596251881Speter /* We don't have permissions to change the 1597251881Speter permissions! Try a move, copy, and delete 1598251881Speter workaround to see if we can get the file owned by 1599251881Speter us. If these succeed, try the permissions set 1600251881Speter again. 1601251881Speter 1602251881Speter Note that we only attempt this in the 1603251881Speter stat-available path. This assumes that the 1604251881Speter move-copy workaround will only be helpful on 1605251881Speter platforms that implement apr_stat. */ 1606251881Speter SVN_ERR(reown_file(path, pool)); 1607251881Speter status = apr_file_perms_set(path_apr, perms_to_set); 1608251881Speter } 1609251881Speter 1610251881Speter if (!status) 1611251881Speter return SVN_NO_ERROR; 1612251881Speter 1613251881Speter if (ignore_enoent && APR_STATUS_IS_ENOENT(status)) 1614251881Speter return SVN_NO_ERROR; 1615251881Speter else if (status == APR_ENOTIMPL) 1616251881Speter { 1617251881Speter /* At least try to set the attributes. */ 1618251881Speter apr_fileattrs_t attrs = 0; 1619251881Speter apr_fileattrs_t attrs_values = 0; 1620251881Speter 1621251881Speter if (change_readwrite) 1622251881Speter { 1623251881Speter attrs = APR_FILE_ATTR_READONLY; 1624251881Speter if (!enable_write) 1625251881Speter attrs_values = APR_FILE_ATTR_READONLY; 1626251881Speter } 1627251881Speter if (change_executable) 1628251881Speter { 1629251881Speter attrs = APR_FILE_ATTR_EXECUTABLE; 1630251881Speter if (executable) 1631251881Speter attrs_values = APR_FILE_ATTR_EXECUTABLE; 1632251881Speter } 1633251881Speter status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool); 1634251881Speter } 1635251881Speter 1636251881Speter return svn_error_wrap_apr(status, 1637251881Speter _("Can't change perms of file '%s'"), 1638251881Speter svn_dirent_local_style(path, pool)); 1639251881Speter} 1640251881Speter#endif /* !WIN32 && !__OS2__ */ 1641251881Speter 1642251881Speter#ifdef WIN32 1643251881Speter#if APR_HAS_UNICODE_FS 1644251881Speter/* copy of the apr function utf8_to_unicode_path since apr doesn't export this one */ 1645251881Speterstatic apr_status_t io_utf8_to_unicode_path(apr_wchar_t* retstr, apr_size_t retlen, 1646251881Speter const char* srcstr) 1647251881Speter{ 1648251881Speter /* TODO: The computations could preconvert the string to determine 1649251881Speter * the true size of the retstr, but that's a memory over speed 1650251881Speter * tradeoff that isn't appropriate this early in development. 1651251881Speter * 1652251881Speter * Allocate the maximum string length based on leading 4 1653251881Speter * characters of \\?\ (allowing nearly unlimited path lengths) 1654251881Speter * plus the trailing null, then transform /'s into \\'s since 1655251881Speter * the \\?\ form doesn't allow '/' path separators. 1656251881Speter * 1657251881Speter * Note that the \\?\ form only works for local drive paths, and 1658251881Speter * \\?\UNC\ is needed UNC paths. 1659251881Speter */ 1660251881Speter apr_size_t srcremains = strlen(srcstr) + 1; 1661251881Speter apr_wchar_t *t = retstr; 1662251881Speter apr_status_t rv; 1663251881Speter 1664251881Speter /* This is correct, we don't twist the filename if it will 1665251881Speter * definitely be shorter than 248 characters. It merits some 1666251881Speter * performance testing to see if this has any effect, but there 1667251881Speter * seem to be applications that get confused by the resulting 1668251881Speter * Unicode \\?\ style file names, especially if they use argv[0] 1669251881Speter * or call the Win32 API functions such as GetModuleName, etc. 1670251881Speter * Not every application is prepared to handle such names. 1671251881Speter * 1672251881Speter * Note also this is shorter than MAX_PATH, as directory paths 1673251881Speter * are actually limited to 248 characters. 1674251881Speter * 1675251881Speter * Note that a utf-8 name can never result in more wide chars 1676251881Speter * than the original number of utf-8 narrow chars. 1677251881Speter */ 1678251881Speter if (srcremains > 248) { 1679251881Speter if (srcstr[1] == ':' && (srcstr[2] == '/' || srcstr[2] == '\\')) { 1680251881Speter wcscpy (retstr, L"\\\\?\\"); 1681251881Speter retlen -= 4; 1682251881Speter t += 4; 1683251881Speter } 1684251881Speter else if ((srcstr[0] == '/' || srcstr[0] == '\\') 1685251881Speter && (srcstr[1] == '/' || srcstr[1] == '\\') 1686251881Speter && (srcstr[2] != '?')) { 1687251881Speter /* Skip the slashes */ 1688251881Speter srcstr += 2; 1689251881Speter srcremains -= 2; 1690251881Speter wcscpy (retstr, L"\\\\?\\UNC\\"); 1691251881Speter retlen -= 8; 1692251881Speter t += 8; 1693251881Speter } 1694251881Speter } 1695251881Speter 1696251881Speter if (rv = apr_conv_utf8_to_ucs2(srcstr, &srcremains, t, &retlen)) { 1697251881Speter return (rv == APR_INCOMPLETE) ? APR_EINVAL : rv; 1698251881Speter } 1699251881Speter if (srcremains) { 1700251881Speter return APR_ENAMETOOLONG; 1701251881Speter } 1702251881Speter for (; *t; ++t) 1703251881Speter if (*t == L'/') 1704251881Speter *t = L'\\'; 1705251881Speter return APR_SUCCESS; 1706251881Speter} 1707251881Speter#endif 1708251881Speter 1709251881Speterstatic apr_status_t io_win_file_attrs_set(const char *fname, 1710251881Speter DWORD attributes, 1711251881Speter DWORD attr_mask, 1712251881Speter apr_pool_t *pool) 1713251881Speter{ 1714251881Speter /* this is an implementation of apr_file_attrs_set() but one 1715251881Speter that uses the proper Windows attributes instead of the apr 1716251881Speter attributes. This way, we can apply any Windows file and 1717251881Speter folder attributes even if apr doesn't implement them */ 1718251881Speter DWORD flags; 1719251881Speter apr_status_t rv; 1720251881Speter#if APR_HAS_UNICODE_FS 1721251881Speter apr_wchar_t wfname[APR_PATH_MAX]; 1722251881Speter#endif 1723251881Speter 1724251881Speter#if APR_HAS_UNICODE_FS 1725251881Speter IF_WIN_OS_IS_UNICODE 1726251881Speter { 1727251881Speter if (rv = io_utf8_to_unicode_path(wfname, 1728251881Speter sizeof(wfname) / sizeof(wfname[0]), 1729251881Speter fname)) 1730251881Speter return rv; 1731251881Speter flags = GetFileAttributesW(wfname); 1732251881Speter } 1733251881Speter#endif 1734251881Speter#if APR_HAS_ANSI_FS 1735251881Speter ELSE_WIN_OS_IS_ANSI 1736251881Speter { 1737251881Speter flags = GetFileAttributesA(fname); 1738251881Speter } 1739251881Speter#endif 1740251881Speter 1741251881Speter if (flags == 0xFFFFFFFF) 1742251881Speter return apr_get_os_error(); 1743251881Speter 1744251881Speter flags &= ~attr_mask; 1745251881Speter flags |= (attributes & attr_mask); 1746251881Speter 1747251881Speter#if APR_HAS_UNICODE_FS 1748251881Speter IF_WIN_OS_IS_UNICODE 1749251881Speter { 1750251881Speter rv = SetFileAttributesW(wfname, flags); 1751251881Speter } 1752251881Speter#endif 1753251881Speter#if APR_HAS_ANSI_FS 1754251881Speter ELSE_WIN_OS_IS_ANSI 1755251881Speter { 1756251881Speter rv = SetFileAttributesA(fname, flags); 1757251881Speter } 1758251881Speter#endif 1759251881Speter 1760251881Speter if (rv == 0) 1761251881Speter return apr_get_os_error(); 1762251881Speter 1763251881Speter return APR_SUCCESS; 1764251881Speter} 1765251881Speter 1766251881Speter#endif 1767251881Speter 1768251881Spetersvn_error_t * 1769251881Spetersvn_io_set_file_read_write_carefully(const char *path, 1770251881Speter svn_boolean_t enable_write, 1771251881Speter svn_boolean_t ignore_enoent, 1772251881Speter apr_pool_t *pool) 1773251881Speter{ 1774251881Speter if (enable_write) 1775251881Speter return svn_io_set_file_read_write(path, ignore_enoent, pool); 1776251881Speter return svn_io_set_file_read_only(path, ignore_enoent, pool); 1777251881Speter} 1778251881Speter 1779251881Spetersvn_error_t * 1780251881Spetersvn_io_set_file_read_only(const char *path, 1781251881Speter svn_boolean_t ignore_enoent, 1782251881Speter apr_pool_t *pool) 1783251881Speter{ 1784251881Speter /* On Windows and OS/2, just set the file attributes -- on unix call 1785251881Speter our internal function which attempts to honor the umask. */ 1786251881Speter#if !defined(WIN32) && !defined(__OS2__) 1787251881Speter return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE, 1788251881Speter ignore_enoent, pool); 1789251881Speter#else 1790251881Speter apr_status_t status; 1791251881Speter const char *path_apr; 1792251881Speter 1793251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1794251881Speter 1795251881Speter status = apr_file_attrs_set(path_apr, 1796251881Speter APR_FILE_ATTR_READONLY, 1797251881Speter APR_FILE_ATTR_READONLY, 1798251881Speter pool); 1799251881Speter 1800251881Speter if (status && status != APR_ENOTIMPL) 1801251881Speter if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status)) 1802251881Speter return svn_error_wrap_apr(status, 1803251881Speter _("Can't set file '%s' read-only"), 1804251881Speter svn_dirent_local_style(path, pool)); 1805251881Speter 1806251881Speter return SVN_NO_ERROR; 1807251881Speter#endif 1808251881Speter} 1809251881Speter 1810251881Speter 1811251881Spetersvn_error_t * 1812251881Spetersvn_io_set_file_read_write(const char *path, 1813251881Speter svn_boolean_t ignore_enoent, 1814251881Speter apr_pool_t *pool) 1815251881Speter{ 1816251881Speter /* On Windows and OS/2, just set the file attributes -- on unix call 1817251881Speter our internal function which attempts to honor the umask. */ 1818251881Speter#if !defined(WIN32) && !defined(__OS2__) 1819251881Speter return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE, 1820251881Speter ignore_enoent, pool); 1821251881Speter#else 1822251881Speter apr_status_t status; 1823251881Speter const char *path_apr; 1824251881Speter 1825251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1826251881Speter 1827251881Speter status = apr_file_attrs_set(path_apr, 1828251881Speter 0, 1829251881Speter APR_FILE_ATTR_READONLY, 1830251881Speter pool); 1831251881Speter 1832251881Speter if (status && status != APR_ENOTIMPL) 1833251881Speter if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status)) 1834251881Speter return svn_error_wrap_apr(status, 1835251881Speter _("Can't set file '%s' read-write"), 1836251881Speter svn_dirent_local_style(path, pool)); 1837251881Speter 1838251881Speter return SVN_NO_ERROR; 1839251881Speter#endif 1840251881Speter} 1841251881Speter 1842251881Spetersvn_error_t * 1843251881Spetersvn_io_set_file_executable(const char *path, 1844251881Speter svn_boolean_t executable, 1845251881Speter svn_boolean_t ignore_enoent, 1846251881Speter apr_pool_t *pool) 1847251881Speter{ 1848251881Speter /* On Windows and OS/2, just exit -- on unix call our internal function 1849251881Speter which attempts to honor the umask. */ 1850251881Speter#if (!defined(WIN32) && !defined(__OS2__)) 1851251881Speter return io_set_file_perms(path, FALSE, FALSE, TRUE, executable, 1852251881Speter ignore_enoent, pool); 1853251881Speter#else 1854251881Speter return SVN_NO_ERROR; 1855251881Speter#endif 1856251881Speter} 1857251881Speter 1858251881Speter 1859251881Spetersvn_error_t * 1860251881Spetersvn_io__is_finfo_read_only(svn_boolean_t *read_only, 1861251881Speter apr_finfo_t *file_info, 1862251881Speter apr_pool_t *pool) 1863251881Speter{ 1864251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 1865251881Speter apr_status_t apr_err; 1866251881Speter apr_uid_t uid; 1867251881Speter apr_gid_t gid; 1868251881Speter 1869251881Speter *read_only = FALSE; 1870251881Speter 1871251881Speter apr_err = apr_uid_current(&uid, &gid, pool); 1872251881Speter 1873251881Speter if (apr_err) 1874251881Speter return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); 1875251881Speter 1876251881Speter /* Check write bit for current user. */ 1877251881Speter if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) 1878251881Speter *read_only = !(file_info->protection & APR_UWRITE); 1879251881Speter 1880251881Speter else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) 1881251881Speter *read_only = !(file_info->protection & APR_GWRITE); 1882251881Speter 1883251881Speter else 1884251881Speter *read_only = !(file_info->protection & APR_WWRITE); 1885251881Speter 1886251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 1887251881Speter *read_only = (file_info->protection & APR_FREADONLY); 1888251881Speter#endif 1889251881Speter 1890251881Speter return SVN_NO_ERROR; 1891251881Speter} 1892251881Speter 1893251881Spetersvn_error_t * 1894251881Spetersvn_io__is_finfo_executable(svn_boolean_t *executable, 1895251881Speter apr_finfo_t *file_info, 1896251881Speter apr_pool_t *pool) 1897251881Speter{ 1898251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 1899251881Speter apr_status_t apr_err; 1900251881Speter apr_uid_t uid; 1901251881Speter apr_gid_t gid; 1902251881Speter 1903251881Speter *executable = FALSE; 1904251881Speter 1905251881Speter apr_err = apr_uid_current(&uid, &gid, pool); 1906251881Speter 1907251881Speter if (apr_err) 1908251881Speter return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); 1909251881Speter 1910251881Speter /* Check executable bit for current user. */ 1911251881Speter if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) 1912251881Speter *executable = (file_info->protection & APR_UEXECUTE); 1913251881Speter 1914251881Speter else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) 1915251881Speter *executable = (file_info->protection & APR_GEXECUTE); 1916251881Speter 1917251881Speter else 1918251881Speter *executable = (file_info->protection & APR_WEXECUTE); 1919251881Speter 1920251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 1921251881Speter *executable = FALSE; 1922251881Speter#endif 1923251881Speter 1924251881Speter return SVN_NO_ERROR; 1925251881Speter} 1926251881Speter 1927251881Spetersvn_error_t * 1928251881Spetersvn_io_is_file_executable(svn_boolean_t *executable, 1929251881Speter const char *path, 1930251881Speter apr_pool_t *pool) 1931251881Speter{ 1932251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 1933251881Speter apr_finfo_t file_info; 1934251881Speter 1935251881Speter SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER, 1936251881Speter pool)); 1937251881Speter SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool)); 1938251881Speter 1939251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 1940251881Speter *executable = FALSE; 1941251881Speter#endif 1942251881Speter 1943251881Speter return SVN_NO_ERROR; 1944251881Speter} 1945251881Speter 1946251881Speter 1947251881Speter/*** File locking. ***/ 1948251881Speter#if !defined(WIN32) && !defined(__OS2__) 1949251881Speter/* Clear all outstanding locks on ARG, an open apr_file_t *. */ 1950251881Speterstatic apr_status_t 1951251881Speterfile_clear_locks(void *arg) 1952251881Speter{ 1953251881Speter apr_status_t apr_err; 1954251881Speter apr_file_t *f = arg; 1955251881Speter 1956251881Speter /* Remove locks. */ 1957251881Speter apr_err = apr_file_unlock(f); 1958251881Speter if (apr_err) 1959251881Speter return apr_err; 1960251881Speter 1961251881Speter return 0; 1962251881Speter} 1963251881Speter#endif 1964251881Speter 1965251881Spetersvn_error_t * 1966251881Spetersvn_io_lock_open_file(apr_file_t *lockfile_handle, 1967251881Speter svn_boolean_t exclusive, 1968251881Speter svn_boolean_t nonblocking, 1969251881Speter apr_pool_t *pool) 1970251881Speter{ 1971251881Speter int locktype = APR_FLOCK_SHARED; 1972251881Speter apr_status_t apr_err; 1973251881Speter const char *fname; 1974251881Speter 1975251881Speter if (exclusive) 1976251881Speter locktype = APR_FLOCK_EXCLUSIVE; 1977251881Speter if (nonblocking) 1978251881Speter locktype |= APR_FLOCK_NONBLOCK; 1979251881Speter 1980251881Speter /* We need this only in case of an error but this is cheap to get - 1981251881Speter * so we do it here for clarity. */ 1982251881Speter apr_err = apr_file_name_get(&fname, lockfile_handle); 1983251881Speter if (apr_err) 1984251881Speter return svn_error_wrap_apr(apr_err, _("Can't get file name")); 1985251881Speter 1986251881Speter /* Get lock on the filehandle. */ 1987251881Speter apr_err = apr_file_lock(lockfile_handle, locktype); 1988251881Speter 1989251881Speter /* In deployments with two or more multithreaded servers running on 1990251881Speter the same system serving two or more fsfs repositories it is 1991251881Speter possible for a deadlock to occur when getting a write lock on 1992251881Speter db/txn-current-lock: 1993251881Speter 1994251881Speter Process 1 Process 2 1995251881Speter --------- --------- 1996251881Speter thread 1: get lock in repos A 1997251881Speter thread 1: get lock in repos B 1998251881Speter thread 2: block getting lock in repos A 1999251881Speter thread 2: try to get lock in B *** deadlock *** 2000251881Speter 2001251881Speter Retry for a while for the deadlock to clear. */ 2002251881Speter FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype)); 2003251881Speter 2004251881Speter if (apr_err) 2005251881Speter { 2006251881Speter switch (locktype & APR_FLOCK_TYPEMASK) 2007251881Speter { 2008251881Speter case APR_FLOCK_SHARED: 2009251881Speter return svn_error_wrap_apr(apr_err, 2010251881Speter _("Can't get shared lock on file '%s'"), 2011251881Speter try_utf8_from_internal_style(fname, pool)); 2012251881Speter case APR_FLOCK_EXCLUSIVE: 2013251881Speter return svn_error_wrap_apr(apr_err, 2014251881Speter _("Can't get exclusive lock on file '%s'"), 2015251881Speter try_utf8_from_internal_style(fname, pool)); 2016251881Speter default: 2017251881Speter SVN_ERR_MALFUNCTION(); 2018251881Speter } 2019251881Speter } 2020251881Speter 2021251881Speter/* On Windows and OS/2 file locks are automatically released when 2022251881Speter the file handle closes */ 2023251881Speter#if !defined(WIN32) && !defined(__OS2__) 2024251881Speter apr_pool_cleanup_register(pool, lockfile_handle, 2025251881Speter file_clear_locks, 2026251881Speter apr_pool_cleanup_null); 2027251881Speter#endif 2028251881Speter 2029251881Speter return SVN_NO_ERROR; 2030251881Speter} 2031251881Speter 2032251881Spetersvn_error_t * 2033251881Spetersvn_io_unlock_open_file(apr_file_t *lockfile_handle, 2034251881Speter apr_pool_t *pool) 2035251881Speter{ 2036251881Speter const char *fname; 2037251881Speter apr_status_t apr_err; 2038251881Speter 2039251881Speter /* We need this only in case of an error but this is cheap to get - 2040251881Speter * so we do it here for clarity. */ 2041251881Speter apr_err = apr_file_name_get(&fname, lockfile_handle); 2042251881Speter if (apr_err) 2043251881Speter return svn_error_wrap_apr(apr_err, _("Can't get file name")); 2044251881Speter 2045251881Speter /* The actual unlock attempt. */ 2046251881Speter apr_err = apr_file_unlock(lockfile_handle); 2047251881Speter if (apr_err) 2048251881Speter return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"), 2049251881Speter try_utf8_from_internal_style(fname, pool)); 2050251881Speter 2051251881Speter/* On Windows and OS/2 file locks are automatically released when 2052251881Speter the file handle closes */ 2053251881Speter#if !defined(WIN32) && !defined(__OS2__) 2054251881Speter apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks); 2055251881Speter#endif 2056251881Speter 2057251881Speter return SVN_NO_ERROR; 2058251881Speter} 2059251881Speter 2060251881Spetersvn_error_t * 2061251881Spetersvn_io_file_lock2(const char *lock_file, 2062251881Speter svn_boolean_t exclusive, 2063251881Speter svn_boolean_t nonblocking, 2064251881Speter apr_pool_t *pool) 2065251881Speter{ 2066251881Speter int locktype = APR_FLOCK_SHARED; 2067251881Speter apr_file_t *lockfile_handle; 2068251881Speter apr_int32_t flags; 2069251881Speter 2070251881Speter if (exclusive) 2071251881Speter locktype = APR_FLOCK_EXCLUSIVE; 2072251881Speter 2073251881Speter flags = APR_READ; 2074251881Speter if (locktype == APR_FLOCK_EXCLUSIVE) 2075251881Speter flags |= APR_WRITE; 2076251881Speter 2077251881Speter /* locktype is never read after this block, so we don't need to bother 2078251881Speter setting it. If that were to ever change, uncomment the following 2079251881Speter block. 2080251881Speter if (nonblocking) 2081251881Speter locktype |= APR_FLOCK_NONBLOCK; 2082251881Speter */ 2083251881Speter 2084251881Speter SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags, 2085251881Speter APR_OS_DEFAULT, 2086251881Speter pool)); 2087251881Speter 2088251881Speter /* Get lock on the filehandle. */ 2089251881Speter return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool); 2090251881Speter} 2091251881Speter 2092251881Speter 2093251881Speter 2094251881Speter/* Data consistency/coherency operations. */ 2095251881Speter 2096251881Spetersvn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, 2097251881Speter apr_pool_t *pool) 2098251881Speter{ 2099251881Speter apr_os_file_t filehand; 2100251881Speter 2101251881Speter /* First make sure that any user-space buffered data is flushed. */ 2102251881Speter SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file), 2103251881Speter N_("Can't flush file '%s'"), 2104251881Speter N_("Can't flush stream"), 2105251881Speter pool)); 2106251881Speter 2107251881Speter apr_os_file_get(&filehand, file); 2108251881Speter 2109251881Speter /* Call the operating system specific function to actually force the 2110251881Speter data to disk. */ 2111251881Speter { 2112251881Speter#ifdef WIN32 2113251881Speter 2114251881Speter if (! FlushFileBuffers(filehand)) 2115251881Speter return svn_error_wrap_apr(apr_get_os_error(), 2116251881Speter _("Can't flush file to disk")); 2117251881Speter 2118251881Speter#else 2119251881Speter int rv; 2120251881Speter 2121251881Speter do { 2122251881Speter rv = fsync(filehand); 2123251881Speter } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); 2124251881Speter 2125251881Speter /* If the file is in a memory filesystem, fsync() may return 2126251881Speter EINVAL. Presumably the user knows the risks, and we can just 2127251881Speter ignore the error. */ 2128251881Speter if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error())) 2129251881Speter return SVN_NO_ERROR; 2130251881Speter 2131251881Speter if (rv == -1) 2132251881Speter return svn_error_wrap_apr(apr_get_os_error(), 2133251881Speter _("Can't flush file to disk")); 2134251881Speter 2135251881Speter#endif 2136251881Speter } 2137251881Speter return SVN_NO_ERROR; 2138251881Speter} 2139251881Speter 2140251881Speter 2141251881Speter 2142251881Speter/* TODO write test for these two functions, then refactor. */ 2143251881Speter 2144251881Speter/* Set RESULT to an svn_stringbuf_t containing the contents of FILE. 2145251881Speter FILENAME is the FILE's on-disk APR-safe name, or NULL if that name 2146251881Speter isn't known. If CHECK_SIZE is TRUE, the function will attempt to 2147251881Speter first stat() the file to determine it's size before sucking its 2148251881Speter contents into the stringbuf. (Doing so can prevent unnecessary 2149251881Speter memory usage, an unwanted side effect of the stringbuf growth and 2150251881Speter reallocation mechanism.) */ 2151251881Speterstatic svn_error_t * 2152251881Speterstringbuf_from_aprfile(svn_stringbuf_t **result, 2153251881Speter const char *filename, 2154251881Speter apr_file_t *file, 2155251881Speter svn_boolean_t check_size, 2156251881Speter apr_pool_t *pool) 2157251881Speter{ 2158251881Speter apr_size_t len; 2159251881Speter svn_error_t *err; 2160251881Speter svn_stringbuf_t *res = NULL; 2161251881Speter apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE; 2162251881Speter char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 2163251881Speter 2164251881Speter /* If our caller wants us to check the size of the file for 2165251881Speter efficient memory handling, we'll try to do so. */ 2166251881Speter if (check_size) 2167251881Speter { 2168251881Speter apr_status_t status; 2169251881Speter 2170251881Speter /* If our caller didn't tell us the file's name, we'll ask APR 2171251881Speter if it knows the name. No problem if we can't figure it out. */ 2172251881Speter if (! filename) 2173251881Speter { 2174251881Speter const char *filename_apr; 2175251881Speter if (! (status = apr_file_name_get(&filename_apr, file))) 2176251881Speter filename = filename_apr; 2177251881Speter } 2178251881Speter 2179251881Speter /* If we now know the filename, try to stat(). If we succeed, 2180251881Speter we know how to allocate our stringbuf. */ 2181251881Speter if (filename) 2182251881Speter { 2183251881Speter apr_finfo_t finfo; 2184251881Speter if (! (status = apr_stat(&finfo, filename, APR_FINFO_MIN, pool))) 2185251881Speter res_initial_len = (apr_size_t)finfo.size; 2186251881Speter } 2187251881Speter } 2188251881Speter 2189251881Speter 2190251881Speter /* XXX: We should check the incoming data for being of type binary. */ 2191251881Speter 2192251881Speter res = svn_stringbuf_create_ensure(res_initial_len, pool); 2193251881Speter 2194251881Speter /* apr_file_read will not return data and eof in the same call. So this loop 2195251881Speter * is safe from missing read data. */ 2196251881Speter len = SVN__STREAM_CHUNK_SIZE; 2197251881Speter err = svn_io_file_read(file, buf, &len, pool); 2198251881Speter while (! err) 2199251881Speter { 2200251881Speter svn_stringbuf_appendbytes(res, buf, len); 2201251881Speter len = SVN__STREAM_CHUNK_SIZE; 2202251881Speter err = svn_io_file_read(file, buf, &len, pool); 2203251881Speter } 2204251881Speter 2205251881Speter /* Having read all the data we *expect* EOF */ 2206251881Speter if (err && !APR_STATUS_IS_EOF(err->apr_err)) 2207251881Speter return err; 2208251881Speter svn_error_clear(err); 2209251881Speter 2210251881Speter *result = res; 2211251881Speter return SVN_NO_ERROR; 2212251881Speter} 2213251881Speter 2214251881Spetersvn_error_t * 2215251881Spetersvn_stringbuf_from_file2(svn_stringbuf_t **result, 2216251881Speter const char *filename, 2217251881Speter apr_pool_t *pool) 2218251881Speter{ 2219251881Speter apr_file_t *f; 2220251881Speter 2221251881Speter if (filename[0] == '-' && filename[1] == '\0') 2222251881Speter { 2223251881Speter apr_status_t apr_err; 2224251881Speter if ((apr_err = apr_file_open_stdin(&f, pool))) 2225251881Speter return svn_error_wrap_apr(apr_err, _("Can't open stdin")); 2226251881Speter SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool)); 2227251881Speter } 2228251881Speter else 2229251881Speter { 2230251881Speter SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool)); 2231251881Speter SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool)); 2232251881Speter } 2233251881Speter return svn_io_file_close(f, pool); 2234251881Speter} 2235251881Speter 2236251881Speter 2237251881Spetersvn_error_t * 2238251881Spetersvn_stringbuf_from_file(svn_stringbuf_t **result, 2239251881Speter const char *filename, 2240251881Speter apr_pool_t *pool) 2241251881Speter{ 2242251881Speter if (filename[0] == '-' && filename[1] == '\0') 2243251881Speter return svn_error_create 2244251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2245251881Speter _("Reading from stdin is disallowed")); 2246251881Speter return svn_stringbuf_from_file2(result, filename, pool); 2247251881Speter} 2248251881Speter 2249251881Spetersvn_error_t * 2250251881Spetersvn_stringbuf_from_aprfile(svn_stringbuf_t **result, 2251251881Speter apr_file_t *file, 2252251881Speter apr_pool_t *pool) 2253251881Speter{ 2254251881Speter return stringbuf_from_aprfile(result, NULL, file, TRUE, pool); 2255251881Speter} 2256251881Speter 2257251881Speter 2258251881Speter 2259251881Speter/* Deletion. */ 2260251881Speter 2261251881Spetersvn_error_t * 2262251881Spetersvn_io_remove_file2(const char *path, 2263251881Speter svn_boolean_t ignore_enoent, 2264251881Speter apr_pool_t *scratch_pool) 2265251881Speter{ 2266251881Speter apr_status_t apr_err; 2267251881Speter const char *path_apr; 2268251881Speter 2269251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool)); 2270251881Speter 2271251881Speter apr_err = apr_file_remove(path_apr, scratch_pool); 2272251881Speter if (!apr_err 2273251881Speter || (ignore_enoent 2274251881Speter && (APR_STATUS_IS_ENOENT(apr_err) 2275251881Speter || SVN__APR_STATUS_IS_ENOTDIR(apr_err)))) 2276251881Speter return SVN_NO_ERROR; 2277251881Speter 2278251881Speter#ifdef WIN32 2279251881Speter /* If the target is read only NTFS reports EACCESS and FAT/FAT32 2280251881Speter reports EEXIST */ 2281251881Speter if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err)) 2282251881Speter { 2283251881Speter /* Set the destination file writable because Windows will not 2284251881Speter allow us to delete when path is read-only */ 2285251881Speter SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool)); 2286251881Speter apr_err = apr_file_remove(path_apr, scratch_pool); 2287251881Speter 2288251881Speter if (!apr_err) 2289251881Speter return SVN_NO_ERROR; 2290251881Speter } 2291251881Speter 2292251881Speter { 2293251881Speter apr_status_t os_err = APR_TO_OS_ERROR(apr_err); 2294251881Speter /* Check to make sure we aren't trying to delete a directory */ 2295251881Speter if (os_err == ERROR_ACCESS_DENIED || os_err == ERROR_SHARING_VIOLATION) 2296251881Speter { 2297251881Speter apr_finfo_t finfo; 2298251881Speter 2299251881Speter if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool) 2300251881Speter && finfo.filetype == APR_REG) 2301251881Speter { 2302251881Speter WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr, 2303251881Speter scratch_pool)); 2304251881Speter } 2305251881Speter } 2306251881Speter 2307251881Speter /* Just return the delete error */ 2308251881Speter } 2309251881Speter#endif 2310251881Speter 2311251881Speter if (apr_err) 2312251881Speter return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"), 2313251881Speter svn_dirent_local_style(path, scratch_pool)); 2314251881Speter 2315251881Speter return SVN_NO_ERROR; 2316251881Speter} 2317251881Speter 2318251881Speter 2319251881Spetersvn_error_t * 2320251881Spetersvn_io_remove_dir(const char *path, apr_pool_t *pool) 2321251881Speter{ 2322251881Speter return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool); 2323251881Speter} 2324251881Speter 2325251881Speter/* 2326251881Speter Mac OS X has a bug where if you're reading the contents of a 2327251881Speter directory via readdir in a loop, and you remove one of the entries in 2328251881Speter the directory and the directory has 338 or more files in it you will 2329251881Speter skip over some of the entries in the directory. Needless to say, 2330251881Speter this causes problems if you are using this kind of loop inside a 2331251881Speter function that is recursively deleting a directory, because when you 2332251881Speter get around to removing the directory it will still have something in 2333251881Speter it. A similar problem has been observed in other BSDs. This bug has 2334251881Speter since been fixed. See http://www.vnode.ch/fixing_seekdir for details. 2335251881Speter 2336251881Speter The workaround is to delete the files only _after_ the initial 2337251881Speter directory scan. A previous workaround involving rewinddir is 2338251881Speter problematic on Win32 and some NFS clients, notably NetBSD. 2339251881Speter 2340251881Speter See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and 2341251881Speter http://subversion.tigris.org/issues/show_bug.cgi?id=3501. 2342251881Speter*/ 2343251881Speter 2344251881Speter/* Neither windows nor unix allows us to delete a non-empty 2345251881Speter directory. 2346251881Speter 2347251881Speter This is a function to perform the equivalent of 'rm -rf'. */ 2348251881Spetersvn_error_t * 2349251881Spetersvn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent, 2350251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 2351251881Speter apr_pool_t *pool) 2352251881Speter{ 2353251881Speter svn_error_t *err; 2354251881Speter apr_pool_t *subpool; 2355251881Speter apr_hash_t *dirents; 2356251881Speter apr_hash_index_t *hi; 2357251881Speter 2358251881Speter /* Check for pending cancellation request. 2359251881Speter If we need to bail out, do so early. */ 2360251881Speter 2361251881Speter if (cancel_func) 2362251881Speter SVN_ERR((*cancel_func)(cancel_baton)); 2363251881Speter 2364251881Speter subpool = svn_pool_create(pool); 2365251881Speter 2366251881Speter err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool); 2367251881Speter if (err) 2368251881Speter { 2369251881Speter /* if the directory doesn't exist, our mission is accomplished */ 2370251881Speter if (ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err)) 2371251881Speter { 2372251881Speter svn_error_clear(err); 2373251881Speter return SVN_NO_ERROR; 2374251881Speter } 2375251881Speter return svn_error_trace(err); 2376251881Speter } 2377251881Speter 2378251881Speter for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi)) 2379251881Speter { 2380251881Speter const char *name = svn__apr_hash_index_key(hi); 2381251881Speter const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi); 2382251881Speter const char *fullpath; 2383251881Speter 2384251881Speter fullpath = svn_dirent_join(path, name, subpool); 2385251881Speter if (dirent->kind == svn_node_dir) 2386251881Speter { 2387251881Speter /* Don't check for cancellation, the callee will immediately do so */ 2388251881Speter SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func, 2389251881Speter cancel_baton, subpool)); 2390251881Speter } 2391251881Speter else 2392251881Speter { 2393251881Speter if (cancel_func) 2394251881Speter SVN_ERR((*cancel_func)(cancel_baton)); 2395251881Speter 2396251881Speter err = svn_io_remove_file2(fullpath, FALSE, subpool); 2397251881Speter if (err) 2398251881Speter return svn_error_createf 2399251881Speter (err->apr_err, err, _("Can't remove '%s'"), 2400251881Speter svn_dirent_local_style(fullpath, subpool)); 2401251881Speter } 2402251881Speter } 2403251881Speter 2404251881Speter svn_pool_destroy(subpool); 2405251881Speter 2406251881Speter return svn_io_dir_remove_nonrecursive(path, pool); 2407251881Speter} 2408251881Speter 2409251881Spetersvn_error_t * 2410251881Spetersvn_io_get_dir_filenames(apr_hash_t **dirents, 2411251881Speter const char *path, 2412251881Speter apr_pool_t *pool) 2413251881Speter{ 2414251881Speter return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE, 2415251881Speter pool, pool)); 2416251881Speter} 2417251881Speter 2418251881Spetersvn_io_dirent2_t * 2419251881Spetersvn_io_dirent2_create(apr_pool_t *result_pool) 2420251881Speter{ 2421251881Speter svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent)); 2422251881Speter 2423251881Speter /*dirent->kind = svn_node_none; 2424251881Speter dirent->special = FALSE;*/ 2425251881Speter dirent->filesize = SVN_INVALID_FILESIZE; 2426251881Speter /*dirent->mtime = 0;*/ 2427251881Speter 2428251881Speter return dirent; 2429251881Speter} 2430251881Speter 2431251881Spetersvn_io_dirent2_t * 2432251881Spetersvn_io_dirent2_dup(const svn_io_dirent2_t *item, 2433251881Speter apr_pool_t *result_pool) 2434251881Speter{ 2435251881Speter return apr_pmemdup(result_pool, 2436251881Speter item, 2437251881Speter sizeof(*item)); 2438251881Speter} 2439251881Speter 2440251881Spetersvn_error_t * 2441251881Spetersvn_io_get_dirents3(apr_hash_t **dirents, 2442251881Speter const char *path, 2443251881Speter svn_boolean_t only_check_type, 2444251881Speter apr_pool_t *result_pool, 2445251881Speter apr_pool_t *scratch_pool) 2446251881Speter{ 2447251881Speter apr_status_t status; 2448251881Speter apr_dir_t *this_dir; 2449251881Speter apr_finfo_t this_entry; 2450251881Speter apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; 2451251881Speter 2452251881Speter if (!only_check_type) 2453251881Speter flags |= APR_FINFO_SIZE | APR_FINFO_MTIME; 2454251881Speter 2455251881Speter *dirents = apr_hash_make(result_pool); 2456251881Speter 2457251881Speter SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool)); 2458251881Speter 2459251881Speter for (status = apr_dir_read(&this_entry, flags, this_dir); 2460251881Speter status == APR_SUCCESS; 2461251881Speter status = apr_dir_read(&this_entry, flags, this_dir)) 2462251881Speter { 2463251881Speter if ((this_entry.name[0] == '.') 2464251881Speter && ((this_entry.name[1] == '\0') 2465251881Speter || ((this_entry.name[1] == '.') 2466251881Speter && (this_entry.name[2] == '\0')))) 2467251881Speter { 2468251881Speter continue; 2469251881Speter } 2470251881Speter else 2471251881Speter { 2472251881Speter const char *name; 2473251881Speter svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool); 2474251881Speter 2475251881Speter SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool)); 2476251881Speter 2477251881Speter map_apr_finfo_to_node_kind(&(dirent->kind), 2478251881Speter &(dirent->special), 2479251881Speter &this_entry); 2480251881Speter 2481251881Speter if (!only_check_type) 2482251881Speter { 2483251881Speter dirent->filesize = this_entry.size; 2484251881Speter dirent->mtime = this_entry.mtime; 2485251881Speter } 2486251881Speter 2487251881Speter svn_hash_sets(*dirents, name, dirent); 2488251881Speter } 2489251881Speter } 2490251881Speter 2491251881Speter if (! (APR_STATUS_IS_ENOENT(status))) 2492251881Speter return svn_error_wrap_apr(status, _("Can't read directory '%s'"), 2493251881Speter svn_dirent_local_style(path, scratch_pool)); 2494251881Speter 2495251881Speter status = apr_dir_close(this_dir); 2496251881Speter if (status) 2497251881Speter return svn_error_wrap_apr(status, _("Error closing directory '%s'"), 2498251881Speter svn_dirent_local_style(path, scratch_pool)); 2499251881Speter 2500251881Speter return SVN_NO_ERROR; 2501251881Speter} 2502251881Speter 2503251881Spetersvn_error_t * 2504251881Spetersvn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p, 2505251881Speter const char *path, 2506251881Speter svn_boolean_t verify_truename, 2507251881Speter svn_boolean_t ignore_enoent, 2508251881Speter apr_pool_t *result_pool, 2509251881Speter apr_pool_t *scratch_pool) 2510251881Speter{ 2511251881Speter apr_finfo_t finfo; 2512251881Speter svn_io_dirent2_t *dirent; 2513251881Speter svn_error_t *err; 2514251881Speter apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK 2515251881Speter | APR_FINFO_SIZE | APR_FINFO_MTIME; 2516251881Speter 2517251881Speter#if defined(WIN32) || defined(__OS2__) 2518251881Speter if (verify_truename) 2519251881Speter wanted |= APR_FINFO_NAME; 2520251881Speter#endif 2521251881Speter 2522251881Speter err = svn_io_stat(&finfo, path, wanted, scratch_pool); 2523251881Speter 2524251881Speter if (err && ignore_enoent && 2525251881Speter (APR_STATUS_IS_ENOENT(err->apr_err) 2526251881Speter || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 2527251881Speter { 2528251881Speter svn_error_clear(err); 2529251881Speter dirent = svn_io_dirent2_create(result_pool); 2530251881Speter SVN_ERR_ASSERT(dirent->kind == svn_node_none); 2531251881Speter 2532251881Speter *dirent_p = dirent; 2533251881Speter return SVN_NO_ERROR; 2534251881Speter } 2535251881Speter SVN_ERR(err); 2536251881Speter 2537251881Speter#if defined(WIN32) || defined(__OS2__) || defined(DARWIN) 2538251881Speter if (verify_truename) 2539251881Speter { 2540251881Speter const char *requested_name = svn_dirent_basename(path, NULL); 2541251881Speter 2542251881Speter if (requested_name[0] == '\0') 2543251881Speter { 2544251881Speter /* No parent directory. No need to stat/verify */ 2545251881Speter } 2546251881Speter#if defined(WIN32) || defined(__OS2__) 2547251881Speter else if (finfo.name) 2548251881Speter { 2549251881Speter const char *name_on_disk; 2550251881Speter SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path, 2551251881Speter scratch_pool)); 2552251881Speter 2553251881Speter if (strcmp(name_on_disk, requested_name) /* != 0 */) 2554251881Speter { 2555251881Speter if (ignore_enoent) 2556251881Speter { 2557251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2558251881Speter return SVN_NO_ERROR; 2559251881Speter } 2560251881Speter else 2561251881Speter return svn_error_createf(APR_ENOENT, NULL, 2562251881Speter _("Path '%s' not found, case obstructed by '%s'"), 2563251881Speter svn_dirent_local_style(path, scratch_pool), 2564251881Speter name_on_disk); 2565251881Speter } 2566251881Speter } 2567251881Speter#elif defined(DARWIN) 2568251881Speter /* Currently apr doesn't set finfo.name on DARWIN, returning 2569251881Speter APR_INCOMPLETE. 2570251881Speter ### Can we optimize this in another way? */ 2571251881Speter else 2572251881Speter { 2573251881Speter apr_hash_t *dirents; 2574251881Speter 2575251881Speter err = svn_io_get_dirents3(&dirents, 2576251881Speter svn_dirent_dirname(path, scratch_pool), 2577251881Speter TRUE /* only_check_type */, 2578251881Speter scratch_pool, scratch_pool); 2579251881Speter 2580251881Speter if (err && ignore_enoent 2581251881Speter && (APR_STATUS_IS_ENOENT(err->apr_err) 2582251881Speter || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 2583251881Speter { 2584251881Speter svn_error_clear(err); 2585251881Speter 2586251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2587251881Speter return SVN_NO_ERROR; 2588251881Speter } 2589251881Speter else 2590251881Speter SVN_ERR(err); 2591251881Speter 2592251881Speter if (! svn_hash_gets(dirents, requested_name)) 2593251881Speter { 2594251881Speter if (ignore_enoent) 2595251881Speter { 2596251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2597251881Speter return SVN_NO_ERROR; 2598251881Speter } 2599251881Speter else 2600251881Speter return svn_error_createf(APR_ENOENT, NULL, 2601251881Speter _("Path '%s' not found"), 2602251881Speter svn_dirent_local_style(path, scratch_pool)); 2603251881Speter } 2604251881Speter } 2605251881Speter#endif 2606251881Speter } 2607251881Speter#endif 2608251881Speter 2609251881Speter dirent = svn_io_dirent2_create(result_pool); 2610251881Speter map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo); 2611251881Speter 2612251881Speter dirent->filesize = finfo.size; 2613251881Speter dirent->mtime = finfo.mtime; 2614251881Speter 2615251881Speter *dirent_p = dirent; 2616251881Speter 2617251881Speter return SVN_NO_ERROR; 2618251881Speter} 2619251881Speter 2620251881Speter/* Pool userdata key for the error file passed to svn_io_start_cmd(). */ 2621251881Speter#define ERRFILE_KEY "svn-io-start-cmd-errfile" 2622251881Speter 2623251881Speter/* Handle an error from the child process (before command execution) by 2624251881Speter printing DESC and the error string corresponding to STATUS to stderr. */ 2625251881Speterstatic void 2626251881Speterhandle_child_process_error(apr_pool_t *pool, apr_status_t status, 2627251881Speter const char *desc) 2628251881Speter{ 2629251881Speter char errbuf[256]; 2630251881Speter apr_file_t *errfile; 2631251881Speter void *p; 2632251881Speter 2633251881Speter /* We can't do anything if we get an error here, so just return. */ 2634251881Speter if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool)) 2635251881Speter return; 2636251881Speter errfile = p; 2637251881Speter 2638251881Speter if (errfile) 2639251881Speter /* What we get from APR is in native encoding. */ 2640251881Speter apr_file_printf(errfile, "%s: %s", 2641251881Speter desc, apr_strerror(status, errbuf, 2642251881Speter sizeof(errbuf))); 2643251881Speter} 2644251881Speter 2645251881Speter 2646251881Spetersvn_error_t * 2647251881Spetersvn_io_start_cmd3(apr_proc_t *cmd_proc, 2648251881Speter const char *path, 2649251881Speter const char *cmd, 2650251881Speter const char *const *args, 2651251881Speter const char *const *env, 2652251881Speter svn_boolean_t inherit, 2653251881Speter svn_boolean_t infile_pipe, 2654251881Speter apr_file_t *infile, 2655251881Speter svn_boolean_t outfile_pipe, 2656251881Speter apr_file_t *outfile, 2657251881Speter svn_boolean_t errfile_pipe, 2658251881Speter apr_file_t *errfile, 2659251881Speter apr_pool_t *pool) 2660251881Speter{ 2661251881Speter apr_status_t apr_err; 2662251881Speter apr_procattr_t *cmdproc_attr; 2663251881Speter int num_args; 2664251881Speter const char **args_native; 2665251881Speter const char *cmd_apr; 2666251881Speter 2667251881Speter SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe)); 2668251881Speter SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe)); 2669251881Speter SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe)); 2670251881Speter 2671251881Speter /* Create the process attributes. */ 2672251881Speter apr_err = apr_procattr_create(&cmdproc_attr, pool); 2673251881Speter if (apr_err) 2674251881Speter return svn_error_wrap_apr(apr_err, 2675251881Speter _("Can't create process '%s' attributes"), 2676251881Speter cmd); 2677251881Speter 2678251881Speter /* Make sure we invoke cmd directly, not through a shell. */ 2679251881Speter apr_err = apr_procattr_cmdtype_set(cmdproc_attr, 2680251881Speter inherit ? APR_PROGRAM_PATH : APR_PROGRAM); 2681251881Speter if (apr_err) 2682251881Speter return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"), 2683251881Speter cmd); 2684251881Speter 2685251881Speter /* Set the process's working directory. */ 2686251881Speter if (path) 2687251881Speter { 2688251881Speter const char *path_apr; 2689251881Speter 2690251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 2691251881Speter apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr); 2692251881Speter if (apr_err) 2693251881Speter return svn_error_wrap_apr(apr_err, 2694251881Speter _("Can't set process '%s' directory"), 2695251881Speter cmd); 2696251881Speter } 2697251881Speter 2698251881Speter /* Use requested inputs and outputs. 2699251881Speter 2700251881Speter ### Unfortunately each of these apr functions creates a pipe and then 2701251881Speter overwrites the pipe file descriptor with the descriptor we pass 2702251881Speter in. The pipes can then never be closed. This is an APR bug. */ 2703251881Speter if (infile) 2704251881Speter { 2705251881Speter apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL); 2706251881Speter if (apr_err) 2707251881Speter return svn_error_wrap_apr(apr_err, 2708251881Speter _("Can't set process '%s' child input"), 2709251881Speter cmd); 2710251881Speter } 2711251881Speter if (outfile) 2712251881Speter { 2713251881Speter apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL); 2714251881Speter if (apr_err) 2715251881Speter return svn_error_wrap_apr(apr_err, 2716251881Speter _("Can't set process '%s' child outfile"), 2717251881Speter cmd); 2718251881Speter } 2719251881Speter if (errfile) 2720251881Speter { 2721251881Speter apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL); 2722251881Speter if (apr_err) 2723251881Speter return svn_error_wrap_apr(apr_err, 2724251881Speter _("Can't set process '%s' child errfile"), 2725251881Speter cmd); 2726251881Speter } 2727251881Speter 2728251881Speter /* Forward request for pipes to APR. */ 2729251881Speter if (infile_pipe || outfile_pipe || errfile_pipe) 2730251881Speter { 2731251881Speter apr_err = apr_procattr_io_set(cmdproc_attr, 2732251881Speter infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE, 2733251881Speter outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE, 2734251881Speter errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE); 2735251881Speter 2736251881Speter if (apr_err) 2737251881Speter return svn_error_wrap_apr(apr_err, 2738251881Speter _("Can't set process '%s' stdio pipes"), 2739251881Speter cmd); 2740251881Speter } 2741251881Speter 2742251881Speter /* Have the child print any problems executing its program to errfile. */ 2743251881Speter apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool); 2744251881Speter if (apr_err) 2745251881Speter return svn_error_wrap_apr(apr_err, 2746251881Speter _("Can't set process '%s' child errfile for " 2747251881Speter "error handler"), 2748251881Speter cmd); 2749251881Speter apr_err = apr_procattr_child_errfn_set(cmdproc_attr, 2750251881Speter handle_child_process_error); 2751251881Speter if (apr_err) 2752251881Speter return svn_error_wrap_apr(apr_err, 2753251881Speter _("Can't set process '%s' error handler"), 2754251881Speter cmd); 2755251881Speter 2756251881Speter /* Convert cmd and args from UTF-8 */ 2757251881Speter SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool)); 2758251881Speter for (num_args = 0; args[num_args]; num_args++) 2759251881Speter ; 2760251881Speter args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *)); 2761251881Speter args_native[num_args] = NULL; 2762251881Speter while (num_args--) 2763251881Speter { 2764251881Speter /* ### Well, it turns out that on APR on Windows expects all 2765251881Speter program args to be in UTF-8. Callers of svn_io_run_cmd 2766251881Speter should be aware of that. */ 2767251881Speter SVN_ERR(cstring_from_utf8(&args_native[num_args], 2768251881Speter args[num_args], pool)); 2769251881Speter } 2770251881Speter 2771251881Speter 2772251881Speter /* Start the cmd command. */ 2773251881Speter apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native, 2774251881Speter inherit ? NULL : env, cmdproc_attr, pool); 2775251881Speter if (apr_err) 2776251881Speter return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd); 2777251881Speter 2778251881Speter return SVN_NO_ERROR; 2779251881Speter} 2780251881Speter 2781251881Speter#undef ERRFILE_KEY 2782251881Speter 2783251881Spetersvn_error_t * 2784251881Spetersvn_io_wait_for_cmd(apr_proc_t *cmd_proc, 2785251881Speter const char *cmd, 2786251881Speter int *exitcode, 2787251881Speter apr_exit_why_e *exitwhy, 2788251881Speter apr_pool_t *pool) 2789251881Speter{ 2790251881Speter apr_status_t apr_err; 2791251881Speter apr_exit_why_e exitwhy_val; 2792251881Speter int exitcode_val; 2793251881Speter 2794251881Speter /* The Win32 apr_proc_wait doesn't set this... */ 2795251881Speter exitwhy_val = APR_PROC_EXIT; 2796251881Speter 2797251881Speter /* Wait for the cmd command to finish. */ 2798251881Speter apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT); 2799251881Speter if (!APR_STATUS_IS_CHILD_DONE(apr_err)) 2800251881Speter return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"), 2801251881Speter cmd); 2802251881Speter 2803251881Speter if (exitwhy) 2804251881Speter *exitwhy = exitwhy_val; 2805251881Speter else if (APR_PROC_CHECK_SIGNALED(exitwhy_val) 2806251881Speter && APR_PROC_CHECK_CORE_DUMP(exitwhy_val)) 2807251881Speter return svn_error_createf 2808251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 2809251881Speter _("Process '%s' failed (signal %d, core dumped)"), 2810251881Speter cmd, exitcode_val); 2811251881Speter else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)) 2812251881Speter return svn_error_createf 2813251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 2814251881Speter _("Process '%s' failed (signal %d)"), 2815251881Speter cmd, exitcode_val); 2816251881Speter else if (! APR_PROC_CHECK_EXIT(exitwhy_val)) 2817251881Speter /* Don't really know what happened here. */ 2818251881Speter return svn_error_createf 2819251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 2820251881Speter _("Process '%s' failed (exitwhy %d, exitcode %d)"), 2821251881Speter cmd, exitwhy_val, exitcode_val); 2822251881Speter 2823251881Speter if (exitcode) 2824251881Speter *exitcode = exitcode_val; 2825251881Speter else if (exitcode_val != 0) 2826251881Speter return svn_error_createf 2827251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 2828251881Speter _("Process '%s' returned error exitcode %d"), cmd, exitcode_val); 2829251881Speter 2830251881Speter return SVN_NO_ERROR; 2831251881Speter} 2832251881Speter 2833251881Speter 2834251881Spetersvn_error_t * 2835251881Spetersvn_io_run_cmd(const char *path, 2836251881Speter const char *cmd, 2837251881Speter const char *const *args, 2838251881Speter int *exitcode, 2839251881Speter apr_exit_why_e *exitwhy, 2840251881Speter svn_boolean_t inherit, 2841251881Speter apr_file_t *infile, 2842251881Speter apr_file_t *outfile, 2843251881Speter apr_file_t *errfile, 2844251881Speter apr_pool_t *pool) 2845251881Speter{ 2846251881Speter apr_proc_t cmd_proc; 2847251881Speter 2848251881Speter SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit, 2849251881Speter FALSE, infile, FALSE, outfile, FALSE, errfile, 2850251881Speter pool)); 2851251881Speter 2852251881Speter return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool); 2853251881Speter} 2854251881Speter 2855251881Speter 2856251881Spetersvn_error_t * 2857251881Spetersvn_io_run_diff2(const char *dir, 2858251881Speter const char *const *user_args, 2859251881Speter int num_user_args, 2860251881Speter const char *label1, 2861251881Speter const char *label2, 2862251881Speter const char *from, 2863251881Speter const char *to, 2864251881Speter int *pexitcode, 2865251881Speter apr_file_t *outfile, 2866251881Speter apr_file_t *errfile, 2867251881Speter const char *diff_cmd, 2868251881Speter apr_pool_t *pool) 2869251881Speter{ 2870251881Speter const char **args; 2871251881Speter int i; 2872251881Speter int exitcode; 2873251881Speter int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */ 2874251881Speter apr_pool_t *subpool = svn_pool_create(pool); 2875251881Speter 2876251881Speter if (pexitcode == NULL) 2877251881Speter pexitcode = &exitcode; 2878251881Speter 2879251881Speter if (user_args != NULL) 2880251881Speter nargs += num_user_args; 2881251881Speter else 2882251881Speter nargs += 1; /* -u */ 2883251881Speter 2884251881Speter if (label1 != NULL) 2885251881Speter nargs += 2; /* the -L and the label itself */ 2886251881Speter if (label2 != NULL) 2887251881Speter nargs += 2; /* the -L and the label itself */ 2888251881Speter 2889251881Speter args = apr_palloc(subpool, nargs * sizeof(char *)); 2890251881Speter 2891251881Speter i = 0; 2892251881Speter args[i++] = diff_cmd; 2893251881Speter 2894251881Speter if (user_args != NULL) 2895251881Speter { 2896251881Speter int j; 2897251881Speter for (j = 0; j < num_user_args; ++j) 2898251881Speter args[i++] = user_args[j]; 2899251881Speter } 2900251881Speter else 2901251881Speter args[i++] = "-u"; /* assume -u if the user didn't give us any args */ 2902251881Speter 2903251881Speter if (label1 != NULL) 2904251881Speter { 2905251881Speter args[i++] = "-L"; 2906251881Speter args[i++] = label1; 2907251881Speter } 2908251881Speter if (label2 != NULL) 2909251881Speter { 2910251881Speter args[i++] = "-L"; 2911251881Speter args[i++] = label2; 2912251881Speter } 2913251881Speter 2914251881Speter args[i++] = svn_dirent_local_style(from, subpool); 2915251881Speter args[i++] = svn_dirent_local_style(to, subpool); 2916251881Speter args[i++] = NULL; 2917251881Speter 2918251881Speter SVN_ERR_ASSERT(i == nargs); 2919251881Speter 2920251881Speter SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE, 2921251881Speter NULL, outfile, errfile, subpool)); 2922251881Speter 2923251881Speter /* The man page for (GNU) diff describes the return value as: 2924251881Speter 2925251881Speter "An exit status of 0 means no differences were found, 1 means 2926251881Speter some differences were found, and 2 means trouble." 2927251881Speter 2928251881Speter A return value of 2 typically occurs when diff cannot read its input 2929251881Speter or write to its output, but in any case we probably ought to return an 2930251881Speter error for anything other than 0 or 1 as the output is likely to be 2931251881Speter corrupt. 2932251881Speter */ 2933251881Speter if (*pexitcode != 0 && *pexitcode != 1) 2934251881Speter return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 2935251881Speter _("'%s' returned %d"), 2936251881Speter svn_dirent_local_style(diff_cmd, pool), 2937251881Speter *pexitcode); 2938251881Speter 2939251881Speter svn_pool_destroy(subpool); 2940251881Speter 2941251881Speter return SVN_NO_ERROR; 2942251881Speter} 2943251881Speter 2944251881Speter 2945251881Spetersvn_error_t * 2946251881Spetersvn_io_run_diff3_3(int *exitcode, 2947251881Speter const char *dir, 2948251881Speter const char *mine, 2949251881Speter const char *older, 2950251881Speter const char *yours, 2951251881Speter const char *mine_label, 2952251881Speter const char *older_label, 2953251881Speter const char *yours_label, 2954251881Speter apr_file_t *merged, 2955251881Speter const char *diff3_cmd, 2956251881Speter const apr_array_header_t *user_args, 2957251881Speter apr_pool_t *pool) 2958251881Speter{ 2959251881Speter const char **args = apr_palloc(pool, 2960251881Speter sizeof(char*) * (13 2961251881Speter + (user_args 2962251881Speter ? user_args->nelts 2963251881Speter : 1))); 2964251881Speter#ifndef NDEBUG 2965251881Speter int nargs = 12; 2966251881Speter#endif 2967251881Speter int i = 0; 2968251881Speter 2969251881Speter /* Labels fall back to sensible defaults if not specified. */ 2970251881Speter if (mine_label == NULL) 2971251881Speter mine_label = ".working"; 2972251881Speter if (older_label == NULL) 2973251881Speter older_label = ".old"; 2974251881Speter if (yours_label == NULL) 2975251881Speter yours_label = ".new"; 2976251881Speter 2977251881Speter /* Set up diff3 command line. */ 2978251881Speter args[i++] = diff3_cmd; 2979251881Speter if (user_args) 2980251881Speter { 2981251881Speter int j; 2982251881Speter for (j = 0; j < user_args->nelts; ++j) 2983251881Speter args[i++] = APR_ARRAY_IDX(user_args, j, const char *); 2984251881Speter#ifndef NDEBUG 2985251881Speter nargs += user_args->nelts; 2986251881Speter#endif 2987251881Speter } 2988251881Speter else 2989251881Speter { 2990251881Speter args[i++] = "-E"; /* We tried "-A" here, but that caused 2991251881Speter overlapping identical changes to 2992251881Speter conflict. See issue #682. */ 2993251881Speter#ifndef NDEBUG 2994251881Speter ++nargs; 2995251881Speter#endif 2996251881Speter } 2997251881Speter args[i++] = "-m"; 2998251881Speter args[i++] = "-L"; 2999251881Speter args[i++] = mine_label; 3000251881Speter args[i++] = "-L"; 3001251881Speter args[i++] = older_label; /* note: this label is ignored if 3002251881Speter using 2-part markers, which is the 3003251881Speter case with "-E". */ 3004251881Speter args[i++] = "-L"; 3005251881Speter args[i++] = yours_label; 3006251881Speter#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG 3007251881Speter { 3008251881Speter svn_boolean_t has_arg; 3009251881Speter 3010251881Speter /* ### FIXME: we really shouldn't be reading the config here; 3011251881Speter instead, the necessary bits should be passed in by the caller. 3012251881Speter But should we add another parameter to this function, when the 3013251881Speter whole external diff3 thing might eventually go away? */ 3014251881Speter apr_hash_t *config; 3015251881Speter svn_config_t *cfg; 3016251881Speter 3017251881Speter SVN_ERR(svn_config_get_config(&config, pool)); 3018251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 3019251881Speter SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS, 3020251881Speter SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG, 3021251881Speter TRUE)); 3022251881Speter if (has_arg) 3023251881Speter { 3024251881Speter const char *diff_cmd, *diff_utf8; 3025251881Speter svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS, 3026251881Speter SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF); 3027251881Speter SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool)); 3028251881Speter args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL); 3029251881Speter#ifndef NDEBUG 3030251881Speter ++nargs; 3031251881Speter#endif 3032251881Speter } 3033251881Speter } 3034251881Speter#endif 3035251881Speter args[i++] = svn_dirent_local_style(mine, pool); 3036251881Speter args[i++] = svn_dirent_local_style(older, pool); 3037251881Speter args[i++] = svn_dirent_local_style(yours, pool); 3038251881Speter args[i++] = NULL; 3039251881Speter#ifndef NDEBUG 3040251881Speter SVN_ERR_ASSERT(i == nargs); 3041251881Speter#endif 3042251881Speter 3043251881Speter /* Run diff3, output the merged text into the scratch file. */ 3044251881Speter SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args, 3045251881Speter exitcode, NULL, 3046251881Speter TRUE, /* keep environment */ 3047251881Speter NULL, merged, NULL, 3048251881Speter pool)); 3049251881Speter 3050251881Speter /* According to the diff3 docs, a '0' means the merge was clean, and 3051251881Speter '1' means conflict markers were found. Anything else is real 3052251881Speter error. */ 3053251881Speter if ((*exitcode != 0) && (*exitcode != 1)) 3054251881Speter return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 3055251881Speter _("Error running '%s': exitcode was %d, " 3056251881Speter "args were:" 3057251881Speter "\nin directory '%s', basenames:\n%s\n%s\n%s"), 3058251881Speter svn_dirent_local_style(diff3_cmd, pool), 3059251881Speter *exitcode, 3060251881Speter svn_dirent_local_style(dir, pool), 3061251881Speter /* Don't call svn_path_local_style() on 3062251881Speter the basenames. We don't want them to 3063251881Speter be absolute, and we don't need the 3064251881Speter separator conversion. */ 3065251881Speter mine, older, yours); 3066251881Speter 3067251881Speter return SVN_NO_ERROR; 3068251881Speter} 3069251881Speter 3070251881Speter 3071251881Speter/* Canonicalize a string for hashing. Modifies KEY in place. */ 3072251881Speterstatic APR_INLINE char * 3073251881Speterfileext_tolower(char *key) 3074251881Speter{ 3075251881Speter register char *p; 3076251881Speter for (p = key; *p != 0; ++p) 3077251881Speter *p = (char)apr_tolower(*p); 3078251881Speter return key; 3079251881Speter} 3080251881Speter 3081251881Speter 3082251881Spetersvn_error_t * 3083251881Spetersvn_io_parse_mimetypes_file(apr_hash_t **type_map, 3084251881Speter const char *mimetypes_file, 3085251881Speter apr_pool_t *pool) 3086251881Speter{ 3087251881Speter svn_error_t *err = SVN_NO_ERROR; 3088251881Speter apr_hash_t *types = apr_hash_make(pool); 3089251881Speter svn_boolean_t eof = FALSE; 3090251881Speter svn_stringbuf_t *buf; 3091251881Speter apr_pool_t *subpool = svn_pool_create(pool); 3092251881Speter apr_file_t *types_file; 3093251881Speter svn_stream_t *mimetypes_stream; 3094251881Speter 3095251881Speter SVN_ERR(svn_io_file_open(&types_file, mimetypes_file, 3096251881Speter APR_READ, APR_OS_DEFAULT, pool)); 3097251881Speter mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool); 3098251881Speter 3099251881Speter while (1) 3100251881Speter { 3101251881Speter apr_array_header_t *tokens; 3102251881Speter const char *type; 3103251881Speter 3104251881Speter svn_pool_clear(subpool); 3105251881Speter 3106251881Speter /* Read a line. */ 3107251881Speter if ((err = svn_stream_readline(mimetypes_stream, &buf, 3108251881Speter APR_EOL_STR, &eof, subpool))) 3109251881Speter break; 3110251881Speter 3111251881Speter /* Only pay attention to non-empty, non-comment lines. */ 3112251881Speter if (buf->len) 3113251881Speter { 3114251881Speter int i; 3115251881Speter 3116251881Speter if (buf->data[0] == '#') 3117251881Speter continue; 3118251881Speter 3119251881Speter /* Tokenize (into our return pool). */ 3120251881Speter tokens = svn_cstring_split(buf->data, " \t", TRUE, pool); 3121251881Speter if (tokens->nelts < 2) 3122251881Speter continue; 3123251881Speter 3124251881Speter /* The first token in a multi-token line is the media type. 3125251881Speter Subsequent tokens are filename extensions associated with 3126251881Speter that media type. */ 3127251881Speter type = APR_ARRAY_IDX(tokens, 0, const char *); 3128251881Speter for (i = 1; i < tokens->nelts; i++) 3129251881Speter { 3130251881Speter /* We can safely address 'ext' as a non-const string because 3131251881Speter * we know svn_cstring_split() allocated it in 'pool' for us. */ 3132251881Speter char *ext = APR_ARRAY_IDX(tokens, i, char *); 3133251881Speter fileext_tolower(ext); 3134251881Speter svn_hash_sets(types, ext, type); 3135251881Speter } 3136251881Speter } 3137251881Speter if (eof) 3138251881Speter break; 3139251881Speter } 3140251881Speter svn_pool_destroy(subpool); 3141251881Speter 3142251881Speter /* If there was an error above, close the file (ignoring any error 3143251881Speter from *that*) and return the originally error. */ 3144251881Speter if (err) 3145251881Speter { 3146251881Speter svn_error_clear(svn_stream_close(mimetypes_stream)); 3147251881Speter return err; 3148251881Speter } 3149251881Speter 3150251881Speter /* Close the stream (which closes the underlying file, too). */ 3151251881Speter SVN_ERR(svn_stream_close(mimetypes_stream)); 3152251881Speter 3153251881Speter *type_map = types; 3154251881Speter return SVN_NO_ERROR; 3155251881Speter} 3156251881Speter 3157251881Speter 3158251881Spetersvn_error_t * 3159251881Spetersvn_io_detect_mimetype2(const char **mimetype, 3160251881Speter const char *file, 3161251881Speter apr_hash_t *mimetype_map, 3162251881Speter apr_pool_t *pool) 3163251881Speter{ 3164251881Speter static const char * const generic_binary = "application/octet-stream"; 3165251881Speter 3166251881Speter svn_node_kind_t kind; 3167251881Speter apr_file_t *fh; 3168251881Speter svn_error_t *err; 3169251881Speter unsigned char block[1024]; 3170251881Speter apr_size_t amt_read = sizeof(block); 3171251881Speter 3172251881Speter /* Default return value is NULL. */ 3173251881Speter *mimetype = NULL; 3174251881Speter 3175251881Speter /* If there is a mimetype_map provided, we'll first try to look up 3176251881Speter our file's extension in the map. Failing that, we'll run the 3177251881Speter heuristic. */ 3178251881Speter if (mimetype_map) 3179251881Speter { 3180251881Speter const char *type_from_map; 3181251881Speter char *path_ext; /* Can point to physical const memory but only when 3182251881Speter svn_path_splitext sets it to "". */ 3183251881Speter svn_path_splitext(NULL, (const char **)&path_ext, file, pool); 3184251881Speter fileext_tolower(path_ext); 3185251881Speter if ((type_from_map = svn_hash_gets(mimetype_map, path_ext))) 3186251881Speter { 3187251881Speter *mimetype = type_from_map; 3188251881Speter return SVN_NO_ERROR; 3189251881Speter } 3190251881Speter } 3191251881Speter 3192251881Speter /* See if this file even exists, and make sure it really is a file. */ 3193251881Speter SVN_ERR(svn_io_check_path(file, &kind, pool)); 3194251881Speter if (kind != svn_node_file) 3195251881Speter return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, 3196251881Speter _("Can't detect MIME type of non-file '%s'"), 3197251881Speter svn_dirent_local_style(file, pool)); 3198251881Speter 3199251881Speter SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool)); 3200251881Speter 3201251881Speter /* Read a block of data from FILE. */ 3202251881Speter err = svn_io_file_read(fh, block, &amt_read, pool); 3203251881Speter if (err && ! APR_STATUS_IS_EOF(err->apr_err)) 3204251881Speter return err; 3205251881Speter svn_error_clear(err); 3206251881Speter 3207251881Speter /* Now close the file. No use keeping it open any more. */ 3208251881Speter SVN_ERR(svn_io_file_close(fh, pool)); 3209251881Speter 3210251881Speter if (svn_io_is_binary_data(block, amt_read)) 3211251881Speter *mimetype = generic_binary; 3212251881Speter 3213251881Speter return SVN_NO_ERROR; 3214251881Speter} 3215251881Speter 3216251881Speter 3217251881Spetersvn_boolean_t 3218251881Spetersvn_io_is_binary_data(const void *data, apr_size_t len) 3219251881Speter{ 3220251881Speter const unsigned char *buf = data; 3221251881Speter 3222251881Speter if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) 3223251881Speter { 3224251881Speter /* This is an empty UTF-8 file which only contains the UTF-8 BOM. 3225251881Speter * Treat it as plain text. */ 3226251881Speter return FALSE; 3227251881Speter } 3228251881Speter 3229251881Speter /* Right now, this function is going to be really stupid. It's 3230251881Speter going to examine the block of data, and make sure that 15% 3231251881Speter of the bytes are such that their value is in the ranges 0x07-0x0D 3232251881Speter or 0x20-0x7F, and that none of those bytes is 0x00. If those 3233251881Speter criteria are not met, we're calling it binary. 3234251881Speter 3235251881Speter NOTE: Originally, I intended to target 85% of the bytes being in 3236251881Speter the specified ranges, but I flubbed the condition. At any rate, 3237251881Speter folks aren't complaining, so I'm not sure that it's worth 3238251881Speter adjusting this retroactively now. --cmpilato */ 3239251881Speter if (len > 0) 3240251881Speter { 3241251881Speter apr_size_t i; 3242251881Speter apr_size_t binary_count = 0; 3243251881Speter 3244251881Speter /* Run through the data we've read, counting the 'binary-ish' 3245251881Speter bytes. HINT: If we see a 0x00 byte, we'll set our count to its 3246251881Speter max and stop reading the file. */ 3247251881Speter for (i = 0; i < len; i++) 3248251881Speter { 3249251881Speter if (buf[i] == 0) 3250251881Speter { 3251251881Speter binary_count = len; 3252251881Speter break; 3253251881Speter } 3254251881Speter if ((buf[i] < 0x07) 3255251881Speter || ((buf[i] > 0x0D) && (buf[i] < 0x20)) 3256251881Speter || (buf[i] > 0x7F)) 3257251881Speter { 3258251881Speter binary_count++; 3259251881Speter } 3260251881Speter } 3261251881Speter 3262251881Speter return (((binary_count * 1000) / len) > 850); 3263251881Speter } 3264251881Speter 3265251881Speter return FALSE; 3266251881Speter} 3267251881Speter 3268251881Speter 3269251881Spetersvn_error_t * 3270251881Spetersvn_io_detect_mimetype(const char **mimetype, 3271251881Speter const char *file, 3272251881Speter apr_pool_t *pool) 3273251881Speter{ 3274251881Speter return svn_io_detect_mimetype2(mimetype, file, NULL, pool); 3275251881Speter} 3276251881Speter 3277251881Speter 3278251881Spetersvn_error_t * 3279251881Spetersvn_io_file_open(apr_file_t **new_file, const char *fname, 3280251881Speter apr_int32_t flag, apr_fileperms_t perm, 3281251881Speter apr_pool_t *pool) 3282251881Speter{ 3283251881Speter const char *fname_apr; 3284251881Speter apr_status_t status; 3285251881Speter 3286251881Speter SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); 3287251881Speter status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE, 3288251881Speter pool); 3289251881Speter 3290251881Speter if (status) 3291251881Speter return svn_error_wrap_apr(status, _("Can't open file '%s'"), 3292251881Speter svn_dirent_local_style(fname, pool)); 3293251881Speter else 3294251881Speter return SVN_NO_ERROR; 3295251881Speter} 3296251881Speter 3297251881Speter 3298251881Speterstatic APR_INLINE svn_error_t * 3299251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status, 3300251881Speter const char *msg, const char *msg_no_name, 3301251881Speter apr_pool_t *pool) 3302251881Speter{ 3303251881Speter const char *name; 3304251881Speter svn_error_t *err; 3305251881Speter 3306251881Speter if (! status) 3307251881Speter return SVN_NO_ERROR; 3308251881Speter 3309251881Speter err = svn_io_file_name_get(&name, file, pool); 3310251881Speter if (err) 3311251881Speter name = NULL; 3312251881Speter svn_error_clear(err); 3313251881Speter 3314251881Speter /* ### Issue #3014: Return a specific error for broken pipes, 3315251881Speter * ### with a single element in the error chain. */ 3316262253Speter if (SVN__APR_STATUS_IS_EPIPE(status)) 3317251881Speter return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); 3318251881Speter 3319251881Speter if (name) 3320251881Speter return svn_error_wrap_apr(status, _(msg), 3321251881Speter try_utf8_from_internal_style(name, pool)); 3322251881Speter else 3323251881Speter return svn_error_wrap_apr(status, "%s", _(msg_no_name)); 3324251881Speter} 3325251881Speter 3326251881Speter 3327251881Spetersvn_error_t * 3328251881Spetersvn_io_file_close(apr_file_t *file, apr_pool_t *pool) 3329251881Speter{ 3330251881Speter return do_io_file_wrapper_cleanup(file, apr_file_close(file), 3331251881Speter N_("Can't close file '%s'"), 3332251881Speter N_("Can't close stream"), 3333251881Speter pool); 3334251881Speter} 3335251881Speter 3336251881Speter 3337251881Spetersvn_error_t * 3338251881Spetersvn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool) 3339251881Speter{ 3340251881Speter return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file), 3341251881Speter N_("Can't read file '%s'"), 3342251881Speter N_("Can't read stream"), 3343251881Speter pool); 3344251881Speter} 3345251881Speter 3346251881Speter 3347251881Spetersvn_error_t * 3348251881Spetersvn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool) 3349251881Speter{ 3350251881Speter return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file), 3351251881Speter N_("Can't write file '%s'"), 3352251881Speter N_("Can't write stream"), 3353251881Speter pool); 3354251881Speter} 3355251881Speter 3356251881Speter 3357251881Spetersvn_error_t * 3358251881Spetersvn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted, 3359251881Speter apr_file_t *file, apr_pool_t *pool) 3360251881Speter{ 3361251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 3362251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 3363251881Speter 3364251881Speter return do_io_file_wrapper_cleanup( 3365251881Speter file, apr_file_info_get(finfo, wanted, file), 3366251881Speter N_("Can't get attribute information from file '%s'"), 3367251881Speter N_("Can't get attribute information from stream"), 3368251881Speter pool); 3369251881Speter} 3370251881Speter 3371251881Speter 3372251881Spetersvn_error_t * 3373251881Spetersvn_io_file_read(apr_file_t *file, void *buf, 3374251881Speter apr_size_t *nbytes, apr_pool_t *pool) 3375251881Speter{ 3376251881Speter return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes), 3377251881Speter N_("Can't read file '%s'"), 3378251881Speter N_("Can't read stream"), 3379251881Speter pool); 3380251881Speter} 3381251881Speter 3382251881Speter 3383251881Spetersvn_error_t * 3384251881Spetersvn_io_file_read_full2(apr_file_t *file, void *buf, 3385251881Speter apr_size_t nbytes, apr_size_t *bytes_read, 3386251881Speter svn_boolean_t *hit_eof, 3387251881Speter apr_pool_t *pool) 3388251881Speter{ 3389251881Speter apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read); 3390251881Speter if (hit_eof) 3391251881Speter { 3392251881Speter if (APR_STATUS_IS_EOF(status)) 3393251881Speter { 3394251881Speter *hit_eof = TRUE; 3395251881Speter return SVN_NO_ERROR; 3396251881Speter } 3397251881Speter else 3398251881Speter *hit_eof = FALSE; 3399251881Speter } 3400251881Speter 3401251881Speter return do_io_file_wrapper_cleanup(file, status, 3402251881Speter N_("Can't read file '%s'"), 3403251881Speter N_("Can't read stream"), 3404251881Speter pool); 3405251881Speter} 3406251881Speter 3407251881Speter 3408251881Spetersvn_error_t * 3409251881Spetersvn_io_file_seek(apr_file_t *file, apr_seek_where_t where, 3410251881Speter apr_off_t *offset, apr_pool_t *pool) 3411251881Speter{ 3412251881Speter return do_io_file_wrapper_cleanup( 3413251881Speter file, apr_file_seek(file, where, offset), 3414251881Speter N_("Can't set position pointer in file '%s'"), 3415251881Speter N_("Can't set position pointer in stream"), 3416251881Speter pool); 3417251881Speter} 3418251881Speter 3419251881Speter 3420251881Spetersvn_error_t * 3421251881Spetersvn_io_file_write(apr_file_t *file, const void *buf, 3422251881Speter apr_size_t *nbytes, apr_pool_t *pool) 3423251881Speter{ 3424251881Speter return svn_error_trace(do_io_file_wrapper_cleanup( 3425251881Speter file, apr_file_write(file, buf, nbytes), 3426251881Speter N_("Can't write to file '%s'"), 3427251881Speter N_("Can't write to stream"), 3428251881Speter pool)); 3429251881Speter} 3430251881Speter 3431251881Speter 3432251881Spetersvn_error_t * 3433251881Spetersvn_io_file_write_full(apr_file_t *file, const void *buf, 3434251881Speter apr_size_t nbytes, apr_size_t *bytes_written, 3435251881Speter apr_pool_t *pool) 3436251881Speter{ 3437251881Speter /* We cannot simply call apr_file_write_full on Win32 as it may fail 3438251881Speter for larger values of NBYTES. In that case, we have to emulate the 3439251881Speter "_full" part here. Thus, always call apr_file_write directly on 3440251881Speter Win32 as this minimizes overhead for small data buffers. */ 3441251881Speter#ifdef WIN32 3442251881Speter#define MAXBUFSIZE 30*1024 3443251881Speter apr_size_t bw = nbytes; 3444251881Speter apr_size_t to_write = nbytes; 3445251881Speter 3446251881Speter /* try a simple "write everything at once" first */ 3447251881Speter apr_status_t rv = apr_file_write(file, buf, &bw); 3448251881Speter buf = (char *)buf + bw; 3449251881Speter to_write -= bw; 3450251881Speter 3451251881Speter /* if the OS cannot handle that, use smaller chunks */ 3452251881Speter if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY) 3453251881Speter && nbytes > MAXBUFSIZE) 3454251881Speter { 3455251881Speter do { 3456251881Speter bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write; 3457251881Speter rv = apr_file_write(file, buf, &bw); 3458251881Speter buf = (char *)buf + bw; 3459251881Speter to_write -= bw; 3460251881Speter } while (rv == APR_SUCCESS && to_write > 0); 3461251881Speter } 3462251881Speter 3463251881Speter /* bytes_written may actually be NULL */ 3464251881Speter if (bytes_written) 3465251881Speter *bytes_written = nbytes - to_write; 3466251881Speter#undef MAXBUFSIZE 3467251881Speter#else 3468251881Speter apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written); 3469251881Speter#endif 3470251881Speter 3471251881Speter return svn_error_trace(do_io_file_wrapper_cleanup( 3472251881Speter file, rv, 3473251881Speter N_("Can't write to file '%s'"), 3474251881Speter N_("Can't write to stream"), 3475251881Speter pool)); 3476251881Speter} 3477251881Speter 3478251881Speter 3479251881Spetersvn_error_t * 3480251881Spetersvn_io_write_unique(const char **tmp_path, 3481251881Speter const char *dirpath, 3482251881Speter const void *buf, 3483251881Speter apr_size_t nbytes, 3484251881Speter svn_io_file_del_t delete_when, 3485251881Speter apr_pool_t *pool) 3486251881Speter{ 3487251881Speter apr_file_t *new_file; 3488251881Speter svn_error_t *err; 3489251881Speter 3490251881Speter SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath, 3491251881Speter delete_when, pool, pool)); 3492251881Speter 3493251881Speter err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool); 3494251881Speter 3495251881Speter if (!err) 3496251881Speter err = svn_io_file_flush_to_disk(new_file, pool); 3497251881Speter 3498251881Speter return svn_error_trace( 3499251881Speter svn_error_compose_create(err, 3500251881Speter svn_io_file_close(new_file, pool))); 3501251881Speter} 3502251881Speter 3503251881Speter 3504251881Spetersvn_error_t * 3505251881Spetersvn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool) 3506251881Speter{ 3507251881Speter /* This is a work-around. APR would flush the write buffer 3508251881Speter _after_ truncating the file causing now invalid buffered 3509251881Speter data to be written behind OFFSET. */ 3510251881Speter SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file), 3511251881Speter N_("Can't flush file '%s'"), 3512251881Speter N_("Can't flush stream"), 3513251881Speter pool)); 3514251881Speter 3515251881Speter return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset), 3516251881Speter N_("Can't truncate file '%s'"), 3517251881Speter N_("Can't truncate stream"), 3518251881Speter pool); 3519251881Speter} 3520251881Speter 3521251881Speter 3522251881Spetersvn_error_t * 3523251881Spetersvn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit, 3524251881Speter apr_pool_t *pool) 3525251881Speter{ 3526251881Speter /* variables */ 3527251881Speter apr_size_t total_read = 0; 3528251881Speter svn_boolean_t eof = FALSE; 3529251881Speter const char *name; 3530251881Speter svn_error_t *err; 3531251881Speter apr_size_t buf_size = *limit; 3532251881Speter 3533251881Speter while (buf_size > 0) 3534251881Speter { 3535251881Speter /* read a fair chunk of data at once. But don't get too ambitious 3536251881Speter * as that would result in too much waste. Also make sure we can 3537251881Speter * put a NUL after the last byte read. 3538251881Speter */ 3539251881Speter apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128; 3540251881Speter apr_size_t bytes_read = 0; 3541251881Speter char *eol; 3542251881Speter 3543253734Speter if (to_read == 0) 3544253734Speter break; 3545253734Speter 3546251881Speter /* read data block (or just a part of it) */ 3547251881Speter SVN_ERR(svn_io_file_read_full2(file, buf, to_read, 3548251881Speter &bytes_read, &eof, pool)); 3549251881Speter 3550251881Speter /* look or a newline char */ 3551251881Speter buf[bytes_read] = 0; 3552251881Speter eol = strchr(buf, '\n'); 3553251881Speter if (eol) 3554251881Speter { 3555251881Speter apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read; 3556251881Speter 3557251881Speter *eol = 0; 3558251881Speter *limit = total_read + (eol - buf); 3559251881Speter 3560251881Speter /* correct the file pointer: 3561251881Speter * appear as though we just had read the newline char 3562251881Speter */ 3563251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool)); 3564251881Speter 3565251881Speter return SVN_NO_ERROR; 3566251881Speter } 3567251881Speter else if (eof) 3568251881Speter { 3569251881Speter /* no EOL found but we hit the end of the file. 3570251881Speter * Generate a nice EOF error object and return it. 3571251881Speter */ 3572251881Speter char dummy; 3573251881Speter SVN_ERR(svn_io_file_getc(&dummy, file, pool)); 3574251881Speter } 3575251881Speter 3576251881Speter /* next data chunk */ 3577251881Speter buf_size -= bytes_read; 3578251881Speter buf += bytes_read; 3579251881Speter total_read += bytes_read; 3580251881Speter } 3581251881Speter 3582251881Speter /* buffer limit has been exceeded without finding the EOL */ 3583251881Speter err = svn_io_file_name_get(&name, file, pool); 3584251881Speter if (err) 3585251881Speter name = NULL; 3586251881Speter svn_error_clear(err); 3587251881Speter 3588251881Speter if (name) 3589251881Speter return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, 3590251881Speter _("Can't read length line in file '%s'"), 3591251881Speter svn_dirent_local_style(name, pool)); 3592251881Speter else 3593251881Speter return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, 3594251881Speter _("Can't read length line in stream")); 3595251881Speter} 3596251881Speter 3597251881Speter 3598251881Spetersvn_error_t * 3599251881Spetersvn_io_stat(apr_finfo_t *finfo, const char *fname, 3600251881Speter apr_int32_t wanted, apr_pool_t *pool) 3601251881Speter{ 3602251881Speter apr_status_t status; 3603251881Speter const char *fname_apr; 3604251881Speter 3605251881Speter /* APR doesn't like "" directories */ 3606251881Speter if (fname[0] == '\0') 3607251881Speter fname = "."; 3608251881Speter 3609251881Speter SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); 3610251881Speter 3611251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 3612251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 3613251881Speter 3614251881Speter status = apr_stat(finfo, fname_apr, wanted, pool); 3615251881Speter if (status) 3616251881Speter return svn_error_wrap_apr(status, _("Can't stat '%s'"), 3617251881Speter svn_dirent_local_style(fname, pool)); 3618251881Speter 3619251881Speter return SVN_NO_ERROR; 3620251881Speter} 3621251881Speter 3622251881Speter 3623251881Spetersvn_error_t * 3624251881Spetersvn_io_file_rename(const char *from_path, const char *to_path, 3625251881Speter apr_pool_t *pool) 3626251881Speter{ 3627251881Speter apr_status_t status = APR_SUCCESS; 3628251881Speter const char *from_path_apr, *to_path_apr; 3629251881Speter 3630251881Speter SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool)); 3631251881Speter SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool)); 3632251881Speter 3633251881Speter status = apr_file_rename(from_path_apr, to_path_apr, pool); 3634251881Speter 3635251881Speter#if defined(WIN32) || defined(__OS2__) 3636251881Speter /* If the target file is read only NTFS reports EACCESS and 3637251881Speter FAT/FAT32 reports EEXIST */ 3638251881Speter if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status)) 3639251881Speter { 3640251881Speter /* Set the destination file writable because Windows will not 3641251881Speter allow us to rename when to_path is read-only, but will 3642251881Speter allow renaming when from_path is read only. */ 3643251881Speter SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool)); 3644251881Speter 3645251881Speter status = apr_file_rename(from_path_apr, to_path_apr, pool); 3646251881Speter } 3647251881Speter WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool)); 3648251881Speter#endif /* WIN32 || __OS2__ */ 3649251881Speter 3650251881Speter if (status) 3651251881Speter return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"), 3652251881Speter svn_dirent_local_style(from_path, pool), 3653251881Speter svn_dirent_local_style(to_path, pool)); 3654251881Speter 3655251881Speter return SVN_NO_ERROR; 3656251881Speter} 3657251881Speter 3658251881Speter 3659251881Spetersvn_error_t * 3660251881Spetersvn_io_file_move(const char *from_path, const char *to_path, 3661251881Speter apr_pool_t *pool) 3662251881Speter{ 3663251881Speter svn_error_t *err = svn_io_file_rename(from_path, to_path, pool); 3664251881Speter 3665251881Speter if (err && APR_STATUS_IS_EXDEV(err->apr_err)) 3666251881Speter { 3667251881Speter const char *tmp_to_path; 3668251881Speter 3669251881Speter svn_error_clear(err); 3670251881Speter 3671251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path, 3672251881Speter svn_dirent_dirname(to_path, pool), 3673251881Speter svn_io_file_del_none, 3674251881Speter pool, pool)); 3675251881Speter 3676251881Speter err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool); 3677251881Speter if (err) 3678251881Speter goto failed_tmp; 3679251881Speter 3680251881Speter err = svn_io_file_rename(tmp_to_path, to_path, pool); 3681251881Speter if (err) 3682251881Speter goto failed_tmp; 3683251881Speter 3684251881Speter err = svn_io_remove_file2(from_path, FALSE, pool); 3685251881Speter if (! err) 3686251881Speter return SVN_NO_ERROR; 3687251881Speter 3688251881Speter svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool)); 3689251881Speter 3690251881Speter return err; 3691251881Speter 3692251881Speter failed_tmp: 3693251881Speter svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool)); 3694251881Speter } 3695251881Speter 3696251881Speter return err; 3697251881Speter} 3698251881Speter 3699251881Speter/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden. 3700251881Speter HIDDEN determines if the hidden attribute 3701251881Speter should be set on the newly created directory. */ 3702251881Speterstatic svn_error_t * 3703251881Speterdir_make(const char *path, apr_fileperms_t perm, 3704251881Speter svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool) 3705251881Speter{ 3706251881Speter apr_status_t status; 3707251881Speter const char *path_apr; 3708251881Speter 3709251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 3710251881Speter 3711251881Speter /* APR doesn't like "" directories */ 3712251881Speter if (path_apr[0] == '\0') 3713251881Speter path_apr = "."; 3714251881Speter 3715251881Speter#if (APR_OS_DEFAULT & APR_WSTICKY) 3716251881Speter /* The APR shipped with httpd 2.0.50 contains a bug where 3717251881Speter APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits. 3718251881Speter There is a special case for file creation, but not directory 3719251881Speter creation, so directories wind up getting created with the sticky 3720251881Speter bit set. (There is no such thing as a setuid directory, and the 3721251881Speter setgid bit is apparently ignored at mkdir() time.) If we detect 3722251881Speter this problem, work around it by unsetting those bits if we are 3723251881Speter passed APR_OS_DEFAULT. */ 3724251881Speter if (perm == APR_OS_DEFAULT) 3725251881Speter perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY); 3726251881Speter#endif 3727251881Speter 3728251881Speter status = apr_dir_make(path_apr, perm, pool); 3729251881Speter WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool)); 3730251881Speter 3731251881Speter if (status) 3732251881Speter return svn_error_wrap_apr(status, _("Can't create directory '%s'"), 3733251881Speter svn_dirent_local_style(path, pool)); 3734251881Speter 3735251881Speter#ifdef APR_FILE_ATTR_HIDDEN 3736251881Speter if (hidden) 3737251881Speter { 3738251881Speter#ifndef WIN32 3739251881Speter status = apr_file_attrs_set(path_apr, 3740251881Speter APR_FILE_ATTR_HIDDEN, 3741251881Speter APR_FILE_ATTR_HIDDEN, 3742251881Speter pool); 3743251881Speter#else 3744251881Speter /* on Windows, use our wrapper so we can also set the 3745251881Speter FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */ 3746251881Speter status = io_win_file_attrs_set(path_apr, 3747251881Speter FILE_ATTRIBUTE_HIDDEN | 3748251881Speter FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 3749251881Speter FILE_ATTRIBUTE_HIDDEN | 3750251881Speter FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 3751251881Speter pool); 3752251881Speter 3753251881Speter#endif 3754251881Speter if (status) 3755251881Speter return svn_error_wrap_apr(status, _("Can't hide directory '%s'"), 3756251881Speter svn_dirent_local_style(path, pool)); 3757251881Speter } 3758251881Speter#endif 3759251881Speter 3760251881Speter/* Windows does not implement sgid. Skip here because retrieving 3761251881Speter the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented 3762251881Speter to be 'incredibly expensive'. */ 3763251881Speter#ifndef WIN32 3764251881Speter if (sgid) 3765251881Speter { 3766251881Speter apr_finfo_t finfo; 3767251881Speter 3768251881Speter /* Per our contract, don't do error-checking. Some filesystems 3769251881Speter * don't support the sgid bit, and that's okay. */ 3770251881Speter status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool); 3771251881Speter 3772251881Speter if (!status) 3773251881Speter apr_file_perms_set(path_apr, finfo.protection | APR_GSETID); 3774251881Speter } 3775251881Speter#endif 3776251881Speter 3777251881Speter return SVN_NO_ERROR; 3778251881Speter} 3779251881Speter 3780251881Spetersvn_error_t * 3781251881Spetersvn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool) 3782251881Speter{ 3783251881Speter return dir_make(path, perm, FALSE, FALSE, pool); 3784251881Speter} 3785251881Speter 3786251881Spetersvn_error_t * 3787251881Spetersvn_io_dir_make_hidden(const char *path, apr_fileperms_t perm, 3788251881Speter apr_pool_t *pool) 3789251881Speter{ 3790251881Speter return dir_make(path, perm, TRUE, FALSE, pool); 3791251881Speter} 3792251881Speter 3793251881Spetersvn_error_t * 3794251881Spetersvn_io_dir_make_sgid(const char *path, apr_fileperms_t perm, 3795251881Speter apr_pool_t *pool) 3796251881Speter{ 3797251881Speter return dir_make(path, perm, FALSE, TRUE, pool); 3798251881Speter} 3799251881Speter 3800251881Speter 3801251881Spetersvn_error_t * 3802251881Spetersvn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool) 3803251881Speter{ 3804251881Speter apr_status_t status; 3805251881Speter const char *dirname_apr; 3806251881Speter 3807251881Speter /* APR doesn't like "" directories */ 3808251881Speter if (dirname[0] == '\0') 3809251881Speter dirname = "."; 3810251881Speter 3811251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 3812251881Speter 3813251881Speter status = apr_dir_open(new_dir, dirname_apr, pool); 3814251881Speter if (status) 3815251881Speter return svn_error_wrap_apr(status, _("Can't open directory '%s'"), 3816251881Speter svn_dirent_local_style(dirname, pool)); 3817251881Speter 3818251881Speter return SVN_NO_ERROR; 3819251881Speter} 3820251881Speter 3821251881Spetersvn_error_t * 3822251881Spetersvn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool) 3823251881Speter{ 3824251881Speter apr_status_t status; 3825251881Speter const char *dirname_apr; 3826251881Speter 3827251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 3828251881Speter 3829251881Speter status = apr_dir_remove(dirname_apr, pool); 3830251881Speter 3831251881Speter#ifdef WIN32 3832251881Speter { 3833251881Speter svn_boolean_t retry = TRUE; 3834251881Speter 3835251881Speter if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY) 3836251881Speter { 3837251881Speter apr_status_t empty_status = dir_is_empty(dirname_apr, pool); 3838251881Speter 3839251881Speter if (APR_STATUS_IS_ENOTEMPTY(empty_status)) 3840251881Speter retry = FALSE; 3841251881Speter } 3842251881Speter 3843251881Speter if (retry) 3844251881Speter { 3845251881Speter WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool)); 3846251881Speter } 3847251881Speter } 3848251881Speter#endif 3849251881Speter if (status) 3850251881Speter return svn_error_wrap_apr(status, _("Can't remove directory '%s'"), 3851251881Speter svn_dirent_local_style(dirname, pool)); 3852251881Speter 3853251881Speter return SVN_NO_ERROR; 3854251881Speter} 3855251881Speter 3856251881Speter 3857251881Spetersvn_error_t * 3858251881Spetersvn_io_dir_read(apr_finfo_t *finfo, 3859251881Speter apr_int32_t wanted, 3860251881Speter apr_dir_t *thedir, 3861251881Speter apr_pool_t *pool) 3862251881Speter{ 3863251881Speter apr_status_t status; 3864251881Speter 3865251881Speter status = apr_dir_read(finfo, wanted, thedir); 3866251881Speter 3867251881Speter if (status) 3868251881Speter return svn_error_wrap_apr(status, _("Can't read directory")); 3869251881Speter 3870251881Speter /* It would be nice to use entry_name_to_utf8() below, but can we 3871251881Speter get the dir's path out of an apr_dir_t? I don't see a reliable 3872251881Speter way to do it. */ 3873251881Speter 3874251881Speter if (finfo->fname) 3875251881Speter SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool)); 3876251881Speter 3877251881Speter if (finfo->name) 3878251881Speter SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool)); 3879251881Speter 3880251881Speter return SVN_NO_ERROR; 3881251881Speter} 3882251881Speter 3883251881Spetersvn_error_t * 3884251881Spetersvn_io_dir_close(apr_dir_t *thedir) 3885251881Speter{ 3886251881Speter apr_status_t apr_err = apr_dir_close(thedir); 3887251881Speter if (apr_err) 3888251881Speter return svn_error_wrap_apr(apr_err, _("Error closing directory")); 3889251881Speter 3890251881Speter return SVN_NO_ERROR; 3891251881Speter} 3892251881Speter 3893251881Spetersvn_error_t * 3894251881Spetersvn_io_dir_walk2(const char *dirname, 3895251881Speter apr_int32_t wanted, 3896251881Speter svn_io_walk_func_t walk_func, 3897251881Speter void *walk_baton, 3898251881Speter apr_pool_t *pool) 3899251881Speter{ 3900251881Speter apr_status_t apr_err; 3901251881Speter apr_dir_t *handle; 3902251881Speter apr_pool_t *subpool; 3903251881Speter const char *dirname_apr; 3904251881Speter apr_finfo_t finfo; 3905251881Speter 3906251881Speter wanted |= APR_FINFO_TYPE | APR_FINFO_NAME; 3907251881Speter 3908251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 3909251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 3910251881Speter 3911251881Speter /* The documentation for apr_dir_read used to state that "." and ".." 3912251881Speter will be returned as the first two files, but it doesn't 3913251881Speter work that way in practice, in particular ext3 on Linux-2.6 doesn't 3914251881Speter follow the rules. For details see 3915251881Speter http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666 3916251881Speter 3917251881Speter If APR ever does implement "dot-first" then it would be possible to 3918251881Speter remove the svn_io_stat and walk_func calls and use the walk_func 3919251881Speter inside the loop. 3920251881Speter 3921251881Speter Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is 3922251881Speter documented to provide it, so we have to do a bit extra. */ 3923251881Speter SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool)); 3924251881Speter SVN_ERR(cstring_from_utf8(&finfo.name, 3925251881Speter svn_dirent_basename(dirname, pool), 3926251881Speter pool)); 3927251881Speter finfo.valid |= APR_FINFO_NAME; 3928251881Speter SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool)); 3929251881Speter 3930251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 3931251881Speter 3932251881Speter /* APR doesn't like "" directories */ 3933251881Speter if (dirname_apr[0] == '\0') 3934251881Speter dirname_apr = "."; 3935251881Speter 3936251881Speter apr_err = apr_dir_open(&handle, dirname_apr, pool); 3937251881Speter if (apr_err) 3938251881Speter return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"), 3939251881Speter svn_dirent_local_style(dirname, pool)); 3940251881Speter 3941251881Speter /* iteration subpool */ 3942251881Speter subpool = svn_pool_create(pool); 3943251881Speter 3944251881Speter while (1) 3945251881Speter { 3946251881Speter const char *name_utf8; 3947251881Speter const char *full_path; 3948251881Speter 3949251881Speter svn_pool_clear(subpool); 3950251881Speter 3951251881Speter apr_err = apr_dir_read(&finfo, wanted, handle); 3952251881Speter if (APR_STATUS_IS_ENOENT(apr_err)) 3953251881Speter break; 3954251881Speter else if (apr_err) 3955251881Speter { 3956251881Speter return svn_error_wrap_apr(apr_err, 3957251881Speter _("Can't read directory entry in '%s'"), 3958251881Speter svn_dirent_local_style(dirname, pool)); 3959251881Speter } 3960251881Speter 3961251881Speter if (finfo.filetype == APR_DIR) 3962251881Speter { 3963251881Speter if (finfo.name[0] == '.' 3964251881Speter && (finfo.name[1] == '\0' 3965251881Speter || (finfo.name[1] == '.' && finfo.name[2] == '\0'))) 3966251881Speter /* skip "." and ".." */ 3967251881Speter continue; 3968251881Speter 3969251881Speter /* some other directory. recurse. it will be passed to the 3970251881Speter callback inside the recursion. */ 3971251881Speter SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname, 3972251881Speter subpool)); 3973251881Speter full_path = svn_dirent_join(dirname, name_utf8, subpool); 3974251881Speter SVN_ERR(svn_io_dir_walk2(full_path, 3975251881Speter wanted, 3976251881Speter walk_func, 3977251881Speter walk_baton, 3978251881Speter subpool)); 3979251881Speter } 3980251881Speter else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) 3981251881Speter { 3982251881Speter /* some other directory. pass it to the callback. */ 3983251881Speter SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname, 3984251881Speter subpool)); 3985251881Speter full_path = svn_dirent_join(dirname, name_utf8, subpool); 3986251881Speter SVN_ERR((*walk_func)(walk_baton, 3987251881Speter full_path, 3988251881Speter &finfo, 3989251881Speter subpool)); 3990251881Speter } 3991251881Speter /* else: 3992251881Speter Some other type of file; skip it for now. We've reserved the 3993251881Speter right to expand our coverage here in the future, though, 3994251881Speter without revving this API. 3995251881Speter */ 3996251881Speter } 3997251881Speter 3998251881Speter svn_pool_destroy(subpool); 3999251881Speter 4000251881Speter apr_err = apr_dir_close(handle); 4001251881Speter if (apr_err) 4002251881Speter return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"), 4003251881Speter svn_dirent_local_style(dirname, pool)); 4004251881Speter 4005251881Speter return SVN_NO_ERROR; 4006251881Speter} 4007251881Speter 4008251881Speter 4009251881Speter 4010251881Speter/** 4011251881Speter * Determine if a directory is empty or not. 4012251881Speter * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not. 4013251881Speter * @param path The directory. 4014251881Speter * @param pool Used for temporary allocation. 4015251881Speter * @remark If path is not a directory, or some other error occurs, 4016251881Speter * then return the appropriate apr status code. 4017251881Speter * 4018251881Speter * (This function is written in APR style, in anticipation of 4019251881Speter * perhaps someday being moved to APR as 'apr_dir_is_empty'.) 4020251881Speter */ 4021251881Speterstatic apr_status_t 4022251881Speterdir_is_empty(const char *dir, apr_pool_t *pool) 4023251881Speter{ 4024251881Speter apr_status_t apr_err; 4025251881Speter apr_dir_t *dir_handle; 4026251881Speter apr_finfo_t finfo; 4027251881Speter apr_status_t retval = APR_SUCCESS; 4028251881Speter 4029251881Speter /* APR doesn't like "" directories */ 4030251881Speter if (dir[0] == '\0') 4031251881Speter dir = "."; 4032251881Speter 4033251881Speter apr_err = apr_dir_open(&dir_handle, dir, pool); 4034251881Speter if (apr_err != APR_SUCCESS) 4035251881Speter return apr_err; 4036251881Speter 4037251881Speter for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle); 4038251881Speter apr_err == APR_SUCCESS; 4039251881Speter apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle)) 4040251881Speter { 4041251881Speter /* Ignore entries for this dir and its parent, robustly. 4042251881Speter (APR promises that they'll come first, so technically 4043251881Speter this guard could be moved outside the loop. But Ryan Bloom 4044251881Speter says he doesn't believe it, and I believe him. */ 4045251881Speter if (! (finfo.name[0] == '.' 4046251881Speter && (finfo.name[1] == '\0' 4047251881Speter || (finfo.name[1] == '.' && finfo.name[2] == '\0')))) 4048251881Speter { 4049251881Speter retval = APR_ENOTEMPTY; 4050251881Speter break; 4051251881Speter } 4052251881Speter } 4053251881Speter 4054251881Speter /* Make sure we broke out of the loop for the right reason. */ 4055251881Speter if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err)) 4056251881Speter return apr_err; 4057251881Speter 4058251881Speter apr_err = apr_dir_close(dir_handle); 4059251881Speter if (apr_err != APR_SUCCESS) 4060251881Speter return apr_err; 4061251881Speter 4062251881Speter return retval; 4063251881Speter} 4064251881Speter 4065251881Speter 4066251881Spetersvn_error_t * 4067251881Spetersvn_io_dir_empty(svn_boolean_t *is_empty_p, 4068251881Speter const char *path, 4069251881Speter apr_pool_t *pool) 4070251881Speter{ 4071251881Speter apr_status_t status; 4072251881Speter const char *path_apr; 4073251881Speter 4074251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 4075251881Speter 4076251881Speter status = dir_is_empty(path_apr, pool); 4077251881Speter 4078251881Speter if (!status) 4079251881Speter *is_empty_p = TRUE; 4080251881Speter else if (APR_STATUS_IS_ENOTEMPTY(status)) 4081251881Speter *is_empty_p = FALSE; 4082251881Speter else 4083251881Speter return svn_error_wrap_apr(status, _("Can't check directory '%s'"), 4084251881Speter svn_dirent_local_style(path, pool)); 4085251881Speter 4086251881Speter return SVN_NO_ERROR; 4087251881Speter} 4088251881Speter 4089251881Speter 4090251881Speter 4091251881Speter/*** Version/format files ***/ 4092251881Speter 4093251881Spetersvn_error_t * 4094251881Spetersvn_io_write_version_file(const char *path, 4095251881Speter int version, 4096251881Speter apr_pool_t *pool) 4097251881Speter{ 4098251881Speter const char *path_tmp; 4099251881Speter const char *format_contents = apr_psprintf(pool, "%d\n", version); 4100251881Speter 4101251881Speter SVN_ERR_ASSERT(version >= 0); 4102251881Speter 4103251881Speter SVN_ERR(svn_io_write_unique(&path_tmp, 4104251881Speter svn_dirent_dirname(path, pool), 4105251881Speter format_contents, strlen(format_contents), 4106251881Speter svn_io_file_del_none, pool)); 4107251881Speter 4108251881Speter#if defined(WIN32) || defined(__OS2__) 4109251881Speter /* make the destination writable, but only on Windows, because 4110251881Speter Windows does not let us replace read-only files. */ 4111251881Speter SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool)); 4112251881Speter#endif /* WIN32 || __OS2__ */ 4113251881Speter 4114251881Speter /* rename the temp file as the real destination */ 4115251881Speter SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); 4116251881Speter 4117251881Speter /* And finally remove the perms to make it read only */ 4118251881Speter return svn_io_set_file_read_only(path, FALSE, pool); 4119251881Speter} 4120251881Speter 4121251881Speter 4122251881Spetersvn_error_t * 4123251881Spetersvn_io_read_version_file(int *version, 4124251881Speter const char *path, 4125251881Speter apr_pool_t *pool) 4126251881Speter{ 4127251881Speter apr_file_t *format_file; 4128251881Speter char buf[80]; 4129251881Speter apr_size_t len; 4130251881Speter svn_error_t *err; 4131251881Speter 4132251881Speter /* Read a chunk of data from PATH */ 4133251881Speter SVN_ERR(svn_io_file_open(&format_file, path, APR_READ, 4134251881Speter APR_OS_DEFAULT, pool)); 4135251881Speter len = sizeof(buf); 4136251881Speter err = svn_io_file_read(format_file, buf, &len, pool); 4137251881Speter 4138251881Speter /* Close the file. */ 4139251881Speter SVN_ERR(svn_error_compose_create(err, 4140251881Speter svn_io_file_close(format_file, pool))); 4141251881Speter 4142251881Speter /* If there was no data in PATH, return an error. */ 4143251881Speter if (len == 0) 4144251881Speter return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, 4145251881Speter _("Reading '%s'"), 4146251881Speter svn_dirent_local_style(path, pool)); 4147251881Speter 4148251881Speter /* Check that the first line contains only digits. */ 4149251881Speter { 4150251881Speter apr_size_t i; 4151251881Speter 4152251881Speter for (i = 0; i < len; ++i) 4153251881Speter { 4154251881Speter char c = buf[i]; 4155251881Speter 4156251881Speter if (i > 0 && (c == '\r' || c == '\n')) 4157251881Speter { 4158251881Speter buf[i] = '\0'; 4159251881Speter break; 4160251881Speter } 4161251881Speter if (! svn_ctype_isdigit(c)) 4162251881Speter return svn_error_createf 4163251881Speter (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 4164251881Speter _("First line of '%s' contains non-digit"), 4165251881Speter svn_dirent_local_style(path, pool)); 4166251881Speter } 4167251881Speter } 4168251881Speter 4169251881Speter /* Convert to integer. */ 4170251881Speter SVN_ERR(svn_cstring_atoi(version, buf)); 4171251881Speter 4172251881Speter return SVN_NO_ERROR; 4173251881Speter} 4174251881Speter 4175251881Speter 4176251881Speter 4177251881Speter/* Do a byte-for-byte comparison of FILE1 and FILE2. */ 4178251881Speterstatic svn_error_t * 4179251881Spetercontents_identical_p(svn_boolean_t *identical_p, 4180251881Speter const char *file1, 4181251881Speter const char *file2, 4182251881Speter apr_pool_t *pool) 4183251881Speter{ 4184251881Speter svn_error_t *err; 4185251881Speter apr_size_t bytes_read1, bytes_read2; 4186251881Speter char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 4187251881Speter char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 4188251881Speter apr_file_t *file1_h; 4189251881Speter apr_file_t *file2_h; 4190251881Speter svn_boolean_t eof1 = FALSE; 4191251881Speter svn_boolean_t eof2 = FALSE; 4192251881Speter 4193251881Speter SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT, 4194251881Speter pool)); 4195251881Speter 4196251881Speter err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT, 4197251881Speter pool); 4198251881Speter 4199251881Speter if (err) 4200251881Speter return svn_error_trace( 4201251881Speter svn_error_compose_create(err, 4202251881Speter svn_io_file_close(file1_h, pool))); 4203251881Speter 4204251881Speter *identical_p = TRUE; /* assume TRUE, until disproved below */ 4205251881Speter while (!err && !eof1 && !eof2) 4206251881Speter { 4207251881Speter err = svn_io_file_read_full2(file1_h, buf1, 4208251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read1, 4209251881Speter &eof1, pool); 4210251881Speter if (err) 4211251881Speter break; 4212251881Speter 4213251881Speter err = svn_io_file_read_full2(file2_h, buf2, 4214251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read2, 4215251881Speter &eof2, pool); 4216251881Speter if (err) 4217251881Speter break; 4218251881Speter 4219251881Speter if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1)) 4220251881Speter { 4221251881Speter *identical_p = FALSE; 4222251881Speter break; 4223251881Speter } 4224251881Speter } 4225251881Speter 4226251881Speter /* Special case: one file being a prefix of the other and the shorter 4227251881Speter * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */ 4228251881Speter if (!err && (eof1 != eof2)) 4229251881Speter *identical_p = FALSE; 4230251881Speter 4231251881Speter return svn_error_trace( 4232251881Speter svn_error_compose_create( 4233251881Speter err, 4234251881Speter svn_error_compose_create(svn_io_file_close(file1_h, pool), 4235251881Speter svn_io_file_close(file2_h, pool)))); 4236251881Speter} 4237251881Speter 4238251881Speter 4239251881Speter 4240251881Speter/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */ 4241251881Speterstatic svn_error_t * 4242251881Spetercontents_three_identical_p(svn_boolean_t *identical_p12, 4243251881Speter svn_boolean_t *identical_p23, 4244251881Speter svn_boolean_t *identical_p13, 4245251881Speter const char *file1, 4246251881Speter const char *file2, 4247251881Speter const char *file3, 4248251881Speter apr_pool_t *scratch_pool) 4249251881Speter{ 4250251881Speter svn_error_t *err; 4251251881Speter apr_size_t bytes_read1, bytes_read2, bytes_read3; 4252251881Speter char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4253251881Speter char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4254251881Speter char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4255251881Speter apr_file_t *file1_h; 4256251881Speter apr_file_t *file2_h; 4257251881Speter apr_file_t *file3_h; 4258251881Speter svn_boolean_t eof1 = FALSE; 4259251881Speter svn_boolean_t eof2 = FALSE; 4260251881Speter svn_boolean_t eof3 = FALSE; 4261251881Speter svn_boolean_t read_1, read_2, read_3; 4262251881Speter 4263251881Speter SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT, 4264251881Speter scratch_pool)); 4265251881Speter 4266251881Speter err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT, 4267251881Speter scratch_pool); 4268251881Speter 4269251881Speter if (err) 4270251881Speter return svn_error_trace( 4271251881Speter svn_error_compose_create(err, 4272251881Speter svn_io_file_close(file1_h, scratch_pool))); 4273251881Speter 4274251881Speter err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT, 4275251881Speter scratch_pool); 4276251881Speter 4277251881Speter if (err) 4278251881Speter return svn_error_trace( 4279251881Speter svn_error_compose_create( 4280251881Speter err, 4281251881Speter svn_error_compose_create(svn_io_file_close(file1_h, 4282251881Speter scratch_pool), 4283251881Speter svn_io_file_close(file2_h, 4284251881Speter scratch_pool)))); 4285251881Speter 4286251881Speter /* assume TRUE, until disproved below */ 4287251881Speter *identical_p12 = *identical_p23 = *identical_p13 = TRUE; 4288251881Speter /* We need to read as long as no error occurs, and as long as one of the 4289251881Speter * flags could still change due to a read operation */ 4290251881Speter while (!err 4291251881Speter && ((*identical_p12 && !eof1 && !eof2) 4292251881Speter || (*identical_p23 && !eof2 && !eof3) 4293251881Speter || (*identical_p13 && !eof1 && !eof3))) 4294251881Speter { 4295251881Speter read_1 = read_2 = read_3 = FALSE; 4296251881Speter 4297251881Speter /* As long as a file is not at the end yet, and it is still 4298251881Speter * potentially identical to another file, we read the next chunk.*/ 4299262253Speter if (!eof1 && (*identical_p12 || *identical_p13)) 4300251881Speter { 4301251881Speter err = svn_io_file_read_full2(file1_h, buf1, 4302251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read1, 4303251881Speter &eof1, scratch_pool); 4304251881Speter if (err) 4305251881Speter break; 4306251881Speter read_1 = TRUE; 4307251881Speter } 4308251881Speter 4309262253Speter if (!eof2 && (*identical_p12 || *identical_p23)) 4310251881Speter { 4311251881Speter err = svn_io_file_read_full2(file2_h, buf2, 4312251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read2, 4313251881Speter &eof2, scratch_pool); 4314251881Speter if (err) 4315251881Speter break; 4316251881Speter read_2 = TRUE; 4317251881Speter } 4318251881Speter 4319262253Speter if (!eof3 && (*identical_p13 || *identical_p23)) 4320251881Speter { 4321251881Speter err = svn_io_file_read_full2(file3_h, buf3, 4322251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read3, 4323251881Speter &eof3, scratch_pool); 4324251881Speter if (err) 4325251881Speter break; 4326251881Speter read_3 = TRUE; 4327251881Speter } 4328251881Speter 4329251881Speter /* If the files are still marked identical, and at least one of them 4330251881Speter * is not at the end of file, we check whether they differ, and set 4331251881Speter * their flag to false then. */ 4332251881Speter if (*identical_p12 4333251881Speter && (read_1 || read_2) 4334251881Speter && ((eof1 != eof2) 4335251881Speter || (bytes_read1 != bytes_read2) 4336251881Speter || memcmp(buf1, buf2, bytes_read1))) 4337251881Speter { 4338251881Speter *identical_p12 = FALSE; 4339251881Speter } 4340251881Speter 4341251881Speter if (*identical_p23 4342251881Speter && (read_2 || read_3) 4343251881Speter && ((eof2 != eof3) 4344251881Speter || (bytes_read2 != bytes_read3) 4345251881Speter || memcmp(buf2, buf3, bytes_read2))) 4346251881Speter { 4347251881Speter *identical_p23 = FALSE; 4348251881Speter } 4349251881Speter 4350251881Speter if (*identical_p13 4351251881Speter && (read_1 || read_3) 4352251881Speter && ((eof1 != eof3) 4353251881Speter || (bytes_read1 != bytes_read3) 4354251881Speter || memcmp(buf1, buf3, bytes_read3))) 4355251881Speter { 4356251881Speter *identical_p13 = FALSE; 4357251881Speter } 4358251881Speter } 4359251881Speter 4360251881Speter return svn_error_trace( 4361251881Speter svn_error_compose_create( 4362251881Speter err, 4363251881Speter svn_error_compose_create( 4364251881Speter svn_io_file_close(file1_h, scratch_pool), 4365251881Speter svn_error_compose_create( 4366251881Speter svn_io_file_close(file2_h, scratch_pool), 4367251881Speter svn_io_file_close(file3_h, scratch_pool))))); 4368251881Speter} 4369251881Speter 4370251881Speter 4371251881Speter 4372251881Spetersvn_error_t * 4373251881Spetersvn_io_files_contents_same_p(svn_boolean_t *same, 4374251881Speter const char *file1, 4375251881Speter const char *file2, 4376251881Speter apr_pool_t *pool) 4377251881Speter{ 4378251881Speter svn_boolean_t q; 4379251881Speter 4380251881Speter SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool)); 4381251881Speter 4382251881Speter if (q) 4383251881Speter { 4384251881Speter *same = FALSE; 4385251881Speter return SVN_NO_ERROR; 4386251881Speter } 4387251881Speter 4388251881Speter SVN_ERR(contents_identical_p(&q, file1, file2, pool)); 4389251881Speter 4390251881Speter if (q) 4391251881Speter *same = TRUE; 4392251881Speter else 4393251881Speter *same = FALSE; 4394251881Speter 4395251881Speter return SVN_NO_ERROR; 4396251881Speter} 4397251881Speter 4398251881Spetersvn_error_t * 4399251881Spetersvn_io_files_contents_three_same_p(svn_boolean_t *same12, 4400251881Speter svn_boolean_t *same23, 4401251881Speter svn_boolean_t *same13, 4402251881Speter const char *file1, 4403251881Speter const char *file2, 4404251881Speter const char *file3, 4405251881Speter apr_pool_t *scratch_pool) 4406251881Speter{ 4407251881Speter svn_boolean_t diff_size12, diff_size23, diff_size13; 4408251881Speter 4409251881Speter SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12, 4410251881Speter &diff_size23, 4411251881Speter &diff_size13, 4412251881Speter file1, 4413251881Speter file2, 4414251881Speter file3, 4415251881Speter scratch_pool)); 4416251881Speter 4417251881Speter if (diff_size12 && diff_size23 && diff_size13) 4418251881Speter { 4419251881Speter *same12 = *same23 = *same13 = FALSE; 4420251881Speter } 4421251881Speter else if (diff_size12 && diff_size23) 4422251881Speter { 4423251881Speter *same12 = *same23 = FALSE; 4424251881Speter SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool)); 4425251881Speter } 4426251881Speter else if (diff_size23 && diff_size13) 4427251881Speter { 4428251881Speter *same23 = *same13 = FALSE; 4429251881Speter SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool)); 4430251881Speter } 4431251881Speter else if (diff_size12 && diff_size13) 4432251881Speter { 4433251881Speter *same12 = *same13 = FALSE; 4434251881Speter SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool)); 4435251881Speter } 4436251881Speter else 4437251881Speter { 4438251881Speter SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13); 4439251881Speter SVN_ERR(contents_three_identical_p(same12, same23, same13, 4440251881Speter file1, file2, file3, 4441251881Speter scratch_pool)); 4442251881Speter } 4443251881Speter 4444251881Speter return SVN_NO_ERROR; 4445251881Speter} 4446251881Speter 4447251881Speter#ifdef WIN32 4448251881Speter/* Counter value of file_mktemp request (used in a threadsafe way), to make 4449251881Speter sure that a single process normally never generates the same tempname 4450251881Speter twice */ 4451251881Speterstatic volatile apr_uint32_t tempname_counter = 0; 4452251881Speter#endif 4453251881Speter 4454251881Speter/* Creates a new temporary file in DIRECTORY with apr flags FLAGS. 4455251881Speter Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name. 4456251881Speter Perform temporary allocations in SCRATCH_POOL and the result in 4457251881Speter RESULT_POOL. */ 4458251881Speterstatic svn_error_t * 4459251881Spetertemp_file_create(apr_file_t **new_file, 4460251881Speter const char **new_file_name, 4461251881Speter const char *directory, 4462251881Speter apr_int32_t flags, 4463251881Speter apr_pool_t *result_pool, 4464251881Speter apr_pool_t *scratch_pool) 4465251881Speter{ 4466251881Speter#ifndef WIN32 4467251881Speter const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool); 4468251881Speter const char *templ_apr; 4469251881Speter apr_status_t status; 4470251881Speter 4471251881Speter SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool)); 4472251881Speter 4473251881Speter /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the 4474251881Speter data available in POOL and we need a non-const pointer here, 4475251881Speter as apr changes the template to return the new filename. */ 4476251881Speter status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool); 4477251881Speter 4478251881Speter if (status) 4479251881Speter return svn_error_wrap_apr(status, _("Can't create temporary file from " 4480251881Speter "template '%s'"), templ); 4481251881Speter 4482251881Speter /* Translate the returned path back to utf-8 before returning it */ 4483251881Speter return svn_error_trace(svn_path_cstring_to_utf8(new_file_name, 4484251881Speter templ_apr, 4485251881Speter result_pool)); 4486251881Speter#else 4487251881Speter /* The Windows implementation of apr_file_mktemp doesn't handle access 4488251881Speter denied errors correctly. Therefore we implement our own temp file 4489251881Speter creation function here. */ 4490251881Speter 4491251881Speter /* ### Most of this is borrowed from the svn_io_open_uniquely_named(), 4492251881Speter ### the function we used before. But we try to guess a more unique 4493251881Speter ### name before trying if it exists. */ 4494251881Speter 4495251881Speter /* Offset by some time value and a unique request nr to make the number 4496251881Speter +- unique for both this process and on the computer */ 4497251881Speter int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter) 4498251881Speter + GetCurrentProcessId(); 4499251881Speter int i; 4500251881Speter 4501251881Speter /* ### Maybe use an iterpool? */ 4502251881Speter for (i = 0; i <= 99999; i++) 4503251881Speter { 4504251881Speter apr_uint32_t unique_nr; 4505251881Speter const char *unique_name; 4506251881Speter const char *unique_name_apr; 4507251881Speter apr_file_t *try_file; 4508251881Speter apr_status_t apr_err; 4509251881Speter 4510251881Speter /* Generate a number that should be unique for this application and 4511251881Speter usually for the entire computer to reduce the number of cycles 4512251881Speter through this loop. (A bit of calculation is much cheaper then 4513251881Speter disk io) */ 4514251881Speter unique_nr = baseNr + 3 * i; 4515251881Speter 4516251881Speter unique_name = svn_dirent_join(directory, 4517251881Speter apr_psprintf(scratch_pool, "svn-%X", 4518251881Speter unique_nr), 4519251881Speter scratch_pool); 4520251881Speter 4521251881Speter SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool)); 4522251881Speter 4523251881Speter apr_err = file_open(&try_file, unique_name_apr, flags, 4524251881Speter APR_OS_DEFAULT, FALSE, scratch_pool); 4525251881Speter 4526251881Speter if (APR_STATUS_IS_EEXIST(apr_err)) 4527251881Speter continue; 4528251881Speter else if (apr_err) 4529251881Speter { 4530251881Speter /* On Win32, CreateFile fails with an "Access Denied" error 4531251881Speter code, rather than "File Already Exists", if the colliding 4532251881Speter name belongs to a directory. */ 4533251881Speter 4534251881Speter if (APR_STATUS_IS_EACCES(apr_err)) 4535251881Speter { 4536251881Speter apr_finfo_t finfo; 4537251881Speter apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, 4538251881Speter APR_FINFO_TYPE, scratch_pool); 4539251881Speter 4540251881Speter if (!apr_err_2 && finfo.filetype == APR_DIR) 4541251881Speter continue; 4542251881Speter 4543251881Speter apr_err_2 = APR_TO_OS_ERROR(apr_err); 4544251881Speter 4545251881Speter if (apr_err_2 == ERROR_ACCESS_DENIED || 4546251881Speter apr_err_2 == ERROR_SHARING_VIOLATION) 4547251881Speter { 4548251881Speter /* The file is in use by another process or is hidden; 4549251881Speter create a new name, but don't do this 99999 times in 4550251881Speter case the folder is not writable */ 4551251881Speter i += 797; 4552251881Speter continue; 4553251881Speter } 4554251881Speter 4555251881Speter /* Else fall through and return the original error. */ 4556251881Speter } 4557251881Speter 4558251881Speter return svn_error_wrap_apr(apr_err, _("Can't open '%s'"), 4559251881Speter svn_dirent_local_style(unique_name, 4560251881Speter scratch_pool)); 4561251881Speter } 4562251881Speter else 4563251881Speter { 4564251881Speter /* Move file to the right pool */ 4565251881Speter apr_err = apr_file_setaside(new_file, try_file, result_pool); 4566251881Speter 4567251881Speter if (apr_err) 4568251881Speter return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"), 4569251881Speter svn_dirent_local_style(unique_name, 4570251881Speter scratch_pool)); 4571251881Speter 4572251881Speter *new_file_name = apr_pstrdup(result_pool, unique_name); 4573251881Speter 4574251881Speter return SVN_NO_ERROR; 4575251881Speter } 4576251881Speter } 4577251881Speter 4578251881Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 4579251881Speter NULL, 4580251881Speter _("Unable to make name in '%s'"), 4581251881Speter svn_dirent_local_style(directory, scratch_pool)); 4582251881Speter#endif 4583251881Speter} 4584251881Speter 4585251881Speter/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */ 4586251881Spetersvn_error_t * 4587251881Spetersvn_io_file_name_get(const char **filename, 4588251881Speter apr_file_t *file, 4589251881Speter apr_pool_t *pool) 4590251881Speter{ 4591251881Speter const char *fname_apr; 4592251881Speter apr_status_t status; 4593251881Speter 4594251881Speter status = apr_file_name_get(&fname_apr, file); 4595251881Speter if (status) 4596251881Speter return svn_error_wrap_apr(status, _("Can't get file name")); 4597251881Speter 4598251881Speter if (fname_apr) 4599251881Speter SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool)); 4600251881Speter else 4601251881Speter *filename = NULL; 4602251881Speter 4603251881Speter return SVN_NO_ERROR; 4604251881Speter} 4605251881Speter 4606251881Speter 4607251881Spetersvn_error_t * 4608251881Spetersvn_io_open_unique_file3(apr_file_t **file, 4609251881Speter const char **unique_path, 4610251881Speter const char *dirpath, 4611251881Speter svn_io_file_del_t delete_when, 4612251881Speter apr_pool_t *result_pool, 4613251881Speter apr_pool_t *scratch_pool) 4614251881Speter{ 4615251881Speter apr_file_t *tempfile; 4616251881Speter const char *tempname; 4617251881Speter struct temp_file_cleanup_s *baton = NULL; 4618251881Speter apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL | 4619251881Speter APR_BUFFERED | APR_BINARY); 4620251881Speter#if !defined(WIN32) && !defined(__OS2__) 4621251881Speter apr_fileperms_t perms; 4622251881Speter svn_boolean_t using_system_temp_dir = FALSE; 4623251881Speter#endif 4624251881Speter 4625251881Speter SVN_ERR_ASSERT(file || unique_path); 4626251881Speter if (file) 4627251881Speter *file = NULL; 4628251881Speter if (unique_path) 4629251881Speter *unique_path = NULL; 4630251881Speter 4631251881Speter if (dirpath == NULL) 4632251881Speter { 4633251881Speter#if !defined(WIN32) && !defined(__OS2__) 4634251881Speter using_system_temp_dir = TRUE; 4635251881Speter#endif 4636251881Speter SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool)); 4637251881Speter } 4638251881Speter 4639251881Speter switch (delete_when) 4640251881Speter { 4641251881Speter case svn_io_file_del_on_pool_cleanup: 4642251881Speter baton = apr_palloc(result_pool, sizeof(*baton)); 4643251881Speter baton->pool = result_pool; 4644251881Speter baton->fname_apr = NULL; 4645251881Speter 4646251881Speter /* Because cleanups are run LIFO, we need to make sure to register 4647251881Speter our cleanup before the apr_file_close cleanup: 4648251881Speter 4649251881Speter On Windows, you can't remove an open file. 4650251881Speter */ 4651251881Speter apr_pool_cleanup_register(result_pool, baton, 4652251881Speter temp_file_plain_cleanup_handler, 4653251881Speter temp_file_child_cleanup_handler); 4654251881Speter 4655251881Speter break; 4656251881Speter case svn_io_file_del_on_close: 4657251881Speter flags |= APR_DELONCLOSE; 4658251881Speter break; 4659251881Speter default: 4660251881Speter break; 4661251881Speter } 4662251881Speter 4663251881Speter SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags, 4664251881Speter result_pool, scratch_pool)); 4665251881Speter 4666251881Speter#if !defined(WIN32) && !defined(__OS2__) 4667251881Speter /* apr_file_mktemp() creates files with mode 0600. 4668251881Speter * This is appropriate if we're using a system temp dir since we don't 4669251881Speter * want to leak sensitive data into temp files other users can read. 4670251881Speter * If we're not using a system temp dir we're probably using the 4671251881Speter * .svn/tmp area and it's likely that the tempfile will end up being 4672251881Speter * copied or renamed into the working copy. 4673251881Speter * This would cause working files having mode 0600 while users might 4674251881Speter * expect to see 0644 or 0664. So we tweak perms of the tempfile in this 4675251881Speter * case, but only if the umask allows it. */ 4676251881Speter if (!using_system_temp_dir) 4677251881Speter { 4678289166Speter svn_error_t *err; 4679289166Speter 4680251881Speter SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool)); 4681289166Speter err = file_perms_set2(tempfile, perms, scratch_pool); 4682289166Speter if (err) 4683289166Speter { 4684289166Speter if (APR_STATUS_IS_INCOMPLETE(err->apr_err) || 4685289166Speter APR_STATUS_IS_ENOTIMPL(err->apr_err)) 4686289166Speter svn_error_clear(err); 4687289166Speter else 4688289166Speter { 4689289166Speter const char *message; 4690289166Speter message = apr_psprintf(scratch_pool, 4691289166Speter _("Can't set permissions on '%s'"), 4692289166Speter svn_dirent_local_style(tempname, 4693289166Speter scratch_pool)); 4694289166Speter return svn_error_quick_wrap(err, message); 4695289166Speter } 4696289166Speter } 4697251881Speter } 4698251881Speter#endif 4699251881Speter 4700251881Speter if (file) 4701251881Speter *file = tempfile; 4702251881Speter else 4703251881Speter SVN_ERR(svn_io_file_close(tempfile, scratch_pool)); 4704251881Speter 4705251881Speter if (unique_path) 4706251881Speter *unique_path = tempname; /* Was allocated in result_pool */ 4707251881Speter 4708251881Speter if (baton) 4709251881Speter SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool)); 4710251881Speter 4711251881Speter return SVN_NO_ERROR; 4712251881Speter} 4713251881Speter 4714251881Spetersvn_error_t * 4715251881Spetersvn_io_file_readline(apr_file_t *file, 4716251881Speter svn_stringbuf_t **stringbuf, 4717251881Speter const char **eol, 4718251881Speter svn_boolean_t *eof, 4719251881Speter apr_size_t max_len, 4720251881Speter apr_pool_t *result_pool, 4721251881Speter apr_pool_t *scratch_pool) 4722251881Speter{ 4723251881Speter svn_stringbuf_t *str; 4724251881Speter const char *eol_str; 4725251881Speter apr_size_t numbytes; 4726251881Speter char c; 4727251881Speter apr_size_t len; 4728251881Speter svn_boolean_t found_eof; 4729251881Speter 4730251881Speter str = svn_stringbuf_create_ensure(80, result_pool); 4731251881Speter 4732251881Speter /* Read bytes into STR up to and including, but not storing, 4733251881Speter * the next EOL sequence. */ 4734251881Speter eol_str = NULL; 4735251881Speter numbytes = 1; 4736251881Speter len = 0; 4737251881Speter found_eof = FALSE; 4738251881Speter while (!found_eof) 4739251881Speter { 4740251881Speter if (len < max_len) 4741251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 4742251881Speter &found_eof, scratch_pool)); 4743251881Speter len++; 4744251881Speter if (numbytes != 1 || len > max_len) 4745251881Speter { 4746251881Speter found_eof = TRUE; 4747251881Speter break; 4748251881Speter } 4749251881Speter 4750251881Speter if (c == '\n') 4751251881Speter { 4752251881Speter eol_str = "\n"; 4753251881Speter } 4754251881Speter else if (c == '\r') 4755251881Speter { 4756251881Speter eol_str = "\r"; 4757251881Speter 4758251881Speter if (!found_eof && len < max_len) 4759251881Speter { 4760251881Speter apr_off_t pos; 4761251881Speter 4762251881Speter /* Check for "\r\n" by peeking at the next byte. */ 4763251881Speter pos = 0; 4764251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); 4765251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 4766251881Speter &found_eof, scratch_pool)); 4767251881Speter if (numbytes == 1 && c == '\n') 4768251881Speter { 4769251881Speter eol_str = "\r\n"; 4770251881Speter len++; 4771251881Speter } 4772251881Speter else 4773251881Speter { 4774251881Speter /* Pretend we never peeked. */ 4775251881Speter SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); 4776251881Speter found_eof = FALSE; 4777251881Speter numbytes = 1; 4778251881Speter } 4779251881Speter } 4780251881Speter } 4781251881Speter else 4782251881Speter svn_stringbuf_appendbyte(str, c); 4783251881Speter 4784251881Speter if (eol_str) 4785251881Speter break; 4786251881Speter } 4787251881Speter 4788251881Speter if (eol) 4789251881Speter *eol = eol_str; 4790251881Speter if (eof) 4791251881Speter *eof = found_eof; 4792251881Speter *stringbuf = str; 4793251881Speter 4794251881Speter return SVN_NO_ERROR; 4795251881Speter} 4796