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 1246251881Speter that the filesystem has <= millisecond precision. 1247251881Speter 1248251881Speter ## Perhaps find a better algorithm here. This will fail once 1249251881Speter in every 1000 cases on a millisecond precision filesystem. 1250251881Speter 1251251881Speter But better to fail once in every thousand cases than every 1252251881Speter time, like we did before. 1253251881Speter (All tested filesystems I know have at least microsecond precision.) 1254251881Speter 1255251881Speter Note for further research on algorithm: 1256251881Speter FAT32 has < 1 sec precision on ctime, but 2 sec on mtime */ 1257251881Speter 1258251881Speter /* Sleep for at least 1 millisecond. 1259251881Speter (t < 1000 will be round to 0 in apr) */ 1260251881Speter apr_sleep(1000); 1261251881Speter 1262251881Speter return; 1263251881Speter } 1264251881Speter 1265251881Speter now = apr_time_now(); /* Extract the time used for the path stat */ 1266251881Speter 1267251881Speter if (now >= then) 1268251881Speter return; /* Passing negative values may suspend indefinitely (Windows) */ 1269251881Speter } 1270251881Speter 1271251881Speter apr_sleep(then - now); 1272251881Speter} 1273251881Speter 1274251881Speter 1275251881Spetersvn_error_t * 1276251881Spetersvn_io_filesizes_different_p(svn_boolean_t *different_p, 1277251881Speter const char *file1, 1278251881Speter const char *file2, 1279251881Speter apr_pool_t *pool) 1280251881Speter{ 1281251881Speter apr_finfo_t finfo1; 1282251881Speter apr_finfo_t finfo2; 1283251881Speter apr_status_t status; 1284251881Speter const char *file1_apr, *file2_apr; 1285251881Speter 1286251881Speter /* Not using svn_io_stat() because don't want to generate 1287251881Speter svn_error_t objects for non-error conditions. */ 1288251881Speter 1289251881Speter SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool)); 1290251881Speter SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool)); 1291251881Speter 1292251881Speter /* Stat both files */ 1293251881Speter status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool); 1294251881Speter if (status) 1295251881Speter { 1296251881Speter /* If we got an error stat'ing a file, it could be because the 1297251881Speter file was removed... or who knows. Whatever the case, we 1298251881Speter don't know if the filesizes are definitely different, so 1299251881Speter assume that they're not. */ 1300251881Speter *different_p = FALSE; 1301251881Speter return SVN_NO_ERROR; 1302251881Speter } 1303251881Speter 1304251881Speter status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool); 1305251881Speter if (status) 1306251881Speter { 1307251881Speter /* See previous comment. */ 1308251881Speter *different_p = FALSE; 1309251881Speter return SVN_NO_ERROR; 1310251881Speter } 1311251881Speter 1312251881Speter /* Examine file sizes */ 1313251881Speter if (finfo1.size == finfo2.size) 1314251881Speter *different_p = FALSE; 1315251881Speter else 1316251881Speter *different_p = TRUE; 1317251881Speter 1318251881Speter return SVN_NO_ERROR; 1319251881Speter} 1320251881Speter 1321251881Speter 1322251881Spetersvn_error_t * 1323251881Spetersvn_io_filesizes_three_different_p(svn_boolean_t *different_p12, 1324251881Speter svn_boolean_t *different_p23, 1325251881Speter svn_boolean_t *different_p13, 1326251881Speter const char *file1, 1327251881Speter const char *file2, 1328251881Speter const char *file3, 1329251881Speter apr_pool_t *scratch_pool) 1330251881Speter{ 1331251881Speter apr_finfo_t finfo1, finfo2, finfo3; 1332251881Speter apr_status_t status1, status2, status3; 1333251881Speter const char *file1_apr, *file2_apr, *file3_apr; 1334251881Speter 1335251881Speter /* Not using svn_io_stat() because don't want to generate 1336251881Speter svn_error_t objects for non-error conditions. */ 1337251881Speter 1338251881Speter SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool)); 1339251881Speter SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool)); 1340251881Speter SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool)); 1341251881Speter 1342251881Speter /* Stat all three files */ 1343251881Speter status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool); 1344251881Speter status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool); 1345251881Speter status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool); 1346251881Speter 1347251881Speter /* If we got an error stat'ing a file, it could be because the 1348251881Speter file was removed... or who knows. Whatever the case, we 1349251881Speter don't know if the filesizes are definitely different, so 1350251881Speter assume that they're not. */ 1351251881Speter *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size; 1352251881Speter *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size; 1353251881Speter *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size; 1354251881Speter 1355251881Speter return SVN_NO_ERROR; 1356251881Speter} 1357251881Speter 1358251881Speter 1359251881Spetersvn_error_t * 1360251881Spetersvn_io_file_checksum2(svn_checksum_t **checksum, 1361251881Speter const char *file, 1362251881Speter svn_checksum_kind_t kind, 1363251881Speter apr_pool_t *pool) 1364251881Speter{ 1365251881Speter svn_stream_t *file_stream; 1366251881Speter svn_stream_t *checksum_stream; 1367251881Speter apr_file_t* f; 1368251881Speter 1369251881Speter SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool)); 1370251881Speter file_stream = svn_stream_from_aprfile2(f, FALSE, pool); 1371251881Speter checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind, 1372251881Speter TRUE, pool); 1373251881Speter 1374251881Speter /* Because the checksummed stream will force the reading (and 1375251881Speter checksumming) of all the file's bytes, we can just close the stream 1376251881Speter and let its magic work. */ 1377251881Speter return svn_stream_close(checksum_stream); 1378251881Speter} 1379251881Speter 1380251881Speter 1381251881Spetersvn_error_t * 1382251881Spetersvn_io_file_checksum(unsigned char digest[], 1383251881Speter const char *file, 1384251881Speter apr_pool_t *pool) 1385251881Speter{ 1386251881Speter svn_checksum_t *checksum; 1387251881Speter 1388251881Speter SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool)); 1389251881Speter memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE); 1390251881Speter 1391251881Speter return SVN_NO_ERROR; 1392251881Speter} 1393251881Speter 1394251881Speter 1395251881Speter 1396251881Speter/*** Permissions and modes. ***/ 1397251881Speter 1398251881Speter#if !defined(WIN32) && !defined(__OS2__) 1399251881Speter/* Given the file specified by PATH, attempt to create an 1400251881Speter identical version of it owned by the current user. This is done by 1401251881Speter moving it to a temporary location, copying the file back to its old 1402251881Speter path, then deleting the temporarily moved version. All temporary 1403251881Speter allocations are done in POOL. */ 1404251881Speterstatic svn_error_t * 1405251881Speterreown_file(const char *path, 1406251881Speter apr_pool_t *pool) 1407251881Speter{ 1408251881Speter const char *unique_name; 1409251881Speter 1410251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name, 1411251881Speter svn_dirent_dirname(path, pool), 1412251881Speter svn_io_file_del_none, pool, pool)); 1413251881Speter SVN_ERR(svn_io_file_rename(path, unique_name, pool)); 1414251881Speter SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool)); 1415251881Speter return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool)); 1416251881Speter} 1417251881Speter 1418251881Speter/* Determine what the PERMS for a new file should be by looking at the 1419251881Speter permissions of a temporary file that we create. 1420251881Speter Unfortunately, umask() as defined in POSIX provides no thread-safe way 1421251881Speter to get at the current value of the umask, so what we're doing here is 1422251881Speter the only way we have to determine which combination of write bits 1423251881Speter (User/Group/World) should be set by default. 1424251881Speter Make temporary allocations in SCRATCH_POOL. */ 1425251881Speterstatic svn_error_t * 1426251881Speterget_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool) 1427251881Speter{ 1428251881Speter /* the default permissions as read from the temp folder */ 1429251881Speter static apr_fileperms_t default_perms = 0; 1430251881Speter 1431251881Speter /* Technically, this "racy": Multiple threads may use enter here and 1432251881Speter try to figure out the default permission concurrently. That's fine 1433251881Speter since they will end up with the same results. Even more technical, 1434251881Speter apr_fileperms_t is an atomic type on 32+ bit machines. 1435251881Speter */ 1436251881Speter if (default_perms == 0) 1437251881Speter { 1438251881Speter apr_finfo_t finfo; 1439251881Speter apr_file_t *fd; 1440251881Speter const char *fname_base, *fname; 1441251881Speter apr_uint32_t randomish; 1442251881Speter svn_error_t *err; 1443251881Speter 1444251881Speter /* Get the perms for a newly created file to find out what bits 1445251881Speter should be set. 1446251881Speter 1447251881Speter Explictly delete the file because we want this file to be as 1448251881Speter short-lived as possible since its presence means other 1449251881Speter processes may have to try multiple names. 1450251881Speter 1451251881Speter Using svn_io_open_uniquely_named() here because other tempfile 1452251881Speter creation functions tweak the permission bits of files they create. 1453251881Speter */ 1454251881Speter randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool 1455251881Speter + (apr_uint32_t)apr_time_now()); 1456251881Speter fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish); 1457251881Speter 1458251881Speter SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base, 1459251881Speter NULL, svn_io_file_del_none, 1460251881Speter scratch_pool, scratch_pool)); 1461251881Speter err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool); 1462251881Speter err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool)); 1463251881Speter err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE, 1464251881Speter scratch_pool)); 1465251881Speter SVN_ERR(err); 1466251881Speter *perms = finfo.protection; 1467251881Speter default_perms = finfo.protection; 1468251881Speter } 1469251881Speter else 1470251881Speter *perms = default_perms; 1471251881Speter 1472251881Speter return SVN_NO_ERROR; 1473251881Speter} 1474251881Speter 1475251881Speter/* OR together permission bits of the file FD and the default permissions 1476251881Speter of a file as determined by get_default_file_perms(). Do temporary 1477251881Speter allocations in SCRATCH_POOL. */ 1478251881Speterstatic svn_error_t * 1479251881Spetermerge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms, 1480251881Speter apr_pool_t *scratch_pool) 1481251881Speter{ 1482251881Speter apr_finfo_t finfo; 1483251881Speter apr_fileperms_t default_perms; 1484251881Speter 1485251881Speter SVN_ERR(get_default_file_perms(&default_perms, scratch_pool)); 1486251881Speter SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool)); 1487251881Speter 1488251881Speter /* Glom the perms together. */ 1489251881Speter *perms = default_perms | finfo.protection; 1490251881Speter return SVN_NO_ERROR; 1491251881Speter} 1492251881Speter 1493251881Speter/* This is a helper function for the svn_io_set_file_read* functions 1494251881Speter that attempts to honor the users umask when dealing with 1495251881Speter permission changes. It is a no-op when invoked on a symlink. */ 1496251881Speterstatic svn_error_t * 1497251881Speterio_set_file_perms(const char *path, 1498251881Speter svn_boolean_t change_readwrite, 1499251881Speter svn_boolean_t enable_write, 1500251881Speter svn_boolean_t change_executable, 1501251881Speter svn_boolean_t executable, 1502251881Speter svn_boolean_t ignore_enoent, 1503251881Speter apr_pool_t *pool) 1504251881Speter{ 1505251881Speter apr_status_t status; 1506251881Speter const char *path_apr; 1507251881Speter apr_finfo_t finfo; 1508251881Speter apr_fileperms_t perms_to_set; 1509251881Speter 1510251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1511251881Speter 1512251881Speter /* Try to change only a minimal amount of the perms first 1513251881Speter by getting the current perms and adding bits 1514251881Speter only on where read perms are granted. If this fails 1515251881Speter fall through to just setting file attributes. */ 1516251881Speter status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool); 1517251881Speter if (status) 1518251881Speter { 1519251881Speter if (ignore_enoent && APR_STATUS_IS_ENOENT(status)) 1520251881Speter return SVN_NO_ERROR; 1521251881Speter else if (status != APR_ENOTIMPL) 1522251881Speter return svn_error_wrap_apr(status, 1523251881Speter _("Can't change perms of file '%s'"), 1524251881Speter svn_dirent_local_style(path, pool)); 1525251881Speter return SVN_NO_ERROR; 1526251881Speter } 1527251881Speter 1528251881Speter if (finfo.filetype == APR_LNK) 1529251881Speter return SVN_NO_ERROR; 1530251881Speter 1531251881Speter perms_to_set = finfo.protection; 1532251881Speter if (change_readwrite) 1533251881Speter { 1534251881Speter if (enable_write) /* Make read-write. */ 1535251881Speter { 1536251881Speter apr_file_t *fd; 1537251881Speter 1538251881Speter /* Get the perms for the original file so we'll have any other bits 1539251881Speter * that were already set (like the execute bits, for example). */ 1540251881Speter SVN_ERR(svn_io_file_open(&fd, path, APR_READ, 1541251881Speter APR_OS_DEFAULT, pool)); 1542251881Speter SVN_ERR(merge_default_file_perms(fd, &perms_to_set, pool)); 1543251881Speter SVN_ERR(svn_io_file_close(fd, pool)); 1544251881Speter } 1545251881Speter else 1546251881Speter { 1547251881Speter if (finfo.protection & APR_UREAD) 1548251881Speter perms_to_set &= ~APR_UWRITE; 1549251881Speter if (finfo.protection & APR_GREAD) 1550251881Speter perms_to_set &= ~APR_GWRITE; 1551251881Speter if (finfo.protection & APR_WREAD) 1552251881Speter perms_to_set &= ~APR_WWRITE; 1553251881Speter } 1554251881Speter } 1555251881Speter 1556251881Speter if (change_executable) 1557251881Speter { 1558251881Speter if (executable) 1559251881Speter { 1560251881Speter if (finfo.protection & APR_UREAD) 1561251881Speter perms_to_set |= APR_UEXECUTE; 1562251881Speter if (finfo.protection & APR_GREAD) 1563251881Speter perms_to_set |= APR_GEXECUTE; 1564251881Speter if (finfo.protection & APR_WREAD) 1565251881Speter perms_to_set |= APR_WEXECUTE; 1566251881Speter } 1567251881Speter else 1568251881Speter { 1569251881Speter if (finfo.protection & APR_UREAD) 1570251881Speter perms_to_set &= ~APR_UEXECUTE; 1571251881Speter if (finfo.protection & APR_GREAD) 1572251881Speter perms_to_set &= ~APR_GEXECUTE; 1573251881Speter if (finfo.protection & APR_WREAD) 1574251881Speter perms_to_set &= ~APR_WEXECUTE; 1575251881Speter } 1576251881Speter } 1577251881Speter 1578251881Speter /* If we aren't changing anything then just return, this saves 1579251881Speter some system calls and helps with shared working copies */ 1580251881Speter if (perms_to_set == finfo.protection) 1581251881Speter return SVN_NO_ERROR; 1582251881Speter 1583251881Speter status = apr_file_perms_set(path_apr, perms_to_set); 1584251881Speter if (!status) 1585251881Speter return SVN_NO_ERROR; 1586251881Speter 1587251881Speter if (APR_STATUS_IS_EPERM(status)) 1588251881Speter { 1589251881Speter /* We don't have permissions to change the 1590251881Speter permissions! Try a move, copy, and delete 1591251881Speter workaround to see if we can get the file owned by 1592251881Speter us. If these succeed, try the permissions set 1593251881Speter again. 1594251881Speter 1595251881Speter Note that we only attempt this in the 1596251881Speter stat-available path. This assumes that the 1597251881Speter move-copy workaround will only be helpful on 1598251881Speter platforms that implement apr_stat. */ 1599251881Speter SVN_ERR(reown_file(path, pool)); 1600251881Speter status = apr_file_perms_set(path_apr, perms_to_set); 1601251881Speter } 1602251881Speter 1603251881Speter if (!status) 1604251881Speter return SVN_NO_ERROR; 1605251881Speter 1606251881Speter if (ignore_enoent && APR_STATUS_IS_ENOENT(status)) 1607251881Speter return SVN_NO_ERROR; 1608251881Speter else if (status == APR_ENOTIMPL) 1609251881Speter { 1610251881Speter /* At least try to set the attributes. */ 1611251881Speter apr_fileattrs_t attrs = 0; 1612251881Speter apr_fileattrs_t attrs_values = 0; 1613251881Speter 1614251881Speter if (change_readwrite) 1615251881Speter { 1616251881Speter attrs = APR_FILE_ATTR_READONLY; 1617251881Speter if (!enable_write) 1618251881Speter attrs_values = APR_FILE_ATTR_READONLY; 1619251881Speter } 1620251881Speter if (change_executable) 1621251881Speter { 1622251881Speter attrs = APR_FILE_ATTR_EXECUTABLE; 1623251881Speter if (executable) 1624251881Speter attrs_values = APR_FILE_ATTR_EXECUTABLE; 1625251881Speter } 1626251881Speter status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool); 1627251881Speter } 1628251881Speter 1629251881Speter return svn_error_wrap_apr(status, 1630251881Speter _("Can't change perms of file '%s'"), 1631251881Speter svn_dirent_local_style(path, pool)); 1632251881Speter} 1633251881Speter#endif /* !WIN32 && !__OS2__ */ 1634251881Speter 1635251881Speter#ifdef WIN32 1636251881Speter#if APR_HAS_UNICODE_FS 1637251881Speter/* copy of the apr function utf8_to_unicode_path since apr doesn't export this one */ 1638251881Speterstatic apr_status_t io_utf8_to_unicode_path(apr_wchar_t* retstr, apr_size_t retlen, 1639251881Speter const char* srcstr) 1640251881Speter{ 1641251881Speter /* TODO: The computations could preconvert the string to determine 1642251881Speter * the true size of the retstr, but that's a memory over speed 1643251881Speter * tradeoff that isn't appropriate this early in development. 1644251881Speter * 1645251881Speter * Allocate the maximum string length based on leading 4 1646251881Speter * characters of \\?\ (allowing nearly unlimited path lengths) 1647251881Speter * plus the trailing null, then transform /'s into \\'s since 1648251881Speter * the \\?\ form doesn't allow '/' path separators. 1649251881Speter * 1650251881Speter * Note that the \\?\ form only works for local drive paths, and 1651251881Speter * \\?\UNC\ is needed UNC paths. 1652251881Speter */ 1653251881Speter apr_size_t srcremains = strlen(srcstr) + 1; 1654251881Speter apr_wchar_t *t = retstr; 1655251881Speter apr_status_t rv; 1656251881Speter 1657251881Speter /* This is correct, we don't twist the filename if it will 1658251881Speter * definitely be shorter than 248 characters. It merits some 1659251881Speter * performance testing to see if this has any effect, but there 1660251881Speter * seem to be applications that get confused by the resulting 1661251881Speter * Unicode \\?\ style file names, especially if they use argv[0] 1662251881Speter * or call the Win32 API functions such as GetModuleName, etc. 1663251881Speter * Not every application is prepared to handle such names. 1664251881Speter * 1665251881Speter * Note also this is shorter than MAX_PATH, as directory paths 1666251881Speter * are actually limited to 248 characters. 1667251881Speter * 1668251881Speter * Note that a utf-8 name can never result in more wide chars 1669251881Speter * than the original number of utf-8 narrow chars. 1670251881Speter */ 1671251881Speter if (srcremains > 248) { 1672251881Speter if (srcstr[1] == ':' && (srcstr[2] == '/' || srcstr[2] == '\\')) { 1673251881Speter wcscpy (retstr, L"\\\\?\\"); 1674251881Speter retlen -= 4; 1675251881Speter t += 4; 1676251881Speter } 1677251881Speter else if ((srcstr[0] == '/' || srcstr[0] == '\\') 1678251881Speter && (srcstr[1] == '/' || srcstr[1] == '\\') 1679251881Speter && (srcstr[2] != '?')) { 1680251881Speter /* Skip the slashes */ 1681251881Speter srcstr += 2; 1682251881Speter srcremains -= 2; 1683251881Speter wcscpy (retstr, L"\\\\?\\UNC\\"); 1684251881Speter retlen -= 8; 1685251881Speter t += 8; 1686251881Speter } 1687251881Speter } 1688251881Speter 1689251881Speter if (rv = apr_conv_utf8_to_ucs2(srcstr, &srcremains, t, &retlen)) { 1690251881Speter return (rv == APR_INCOMPLETE) ? APR_EINVAL : rv; 1691251881Speter } 1692251881Speter if (srcremains) { 1693251881Speter return APR_ENAMETOOLONG; 1694251881Speter } 1695251881Speter for (; *t; ++t) 1696251881Speter if (*t == L'/') 1697251881Speter *t = L'\\'; 1698251881Speter return APR_SUCCESS; 1699251881Speter} 1700251881Speter#endif 1701251881Speter 1702251881Speterstatic apr_status_t io_win_file_attrs_set(const char *fname, 1703251881Speter DWORD attributes, 1704251881Speter DWORD attr_mask, 1705251881Speter apr_pool_t *pool) 1706251881Speter{ 1707251881Speter /* this is an implementation of apr_file_attrs_set() but one 1708251881Speter that uses the proper Windows attributes instead of the apr 1709251881Speter attributes. This way, we can apply any Windows file and 1710251881Speter folder attributes even if apr doesn't implement them */ 1711251881Speter DWORD flags; 1712251881Speter apr_status_t rv; 1713251881Speter#if APR_HAS_UNICODE_FS 1714251881Speter apr_wchar_t wfname[APR_PATH_MAX]; 1715251881Speter#endif 1716251881Speter 1717251881Speter#if APR_HAS_UNICODE_FS 1718251881Speter IF_WIN_OS_IS_UNICODE 1719251881Speter { 1720251881Speter if (rv = io_utf8_to_unicode_path(wfname, 1721251881Speter sizeof(wfname) / sizeof(wfname[0]), 1722251881Speter fname)) 1723251881Speter return rv; 1724251881Speter flags = GetFileAttributesW(wfname); 1725251881Speter } 1726251881Speter#endif 1727251881Speter#if APR_HAS_ANSI_FS 1728251881Speter ELSE_WIN_OS_IS_ANSI 1729251881Speter { 1730251881Speter flags = GetFileAttributesA(fname); 1731251881Speter } 1732251881Speter#endif 1733251881Speter 1734251881Speter if (flags == 0xFFFFFFFF) 1735251881Speter return apr_get_os_error(); 1736251881Speter 1737251881Speter flags &= ~attr_mask; 1738251881Speter flags |= (attributes & attr_mask); 1739251881Speter 1740251881Speter#if APR_HAS_UNICODE_FS 1741251881Speter IF_WIN_OS_IS_UNICODE 1742251881Speter { 1743251881Speter rv = SetFileAttributesW(wfname, flags); 1744251881Speter } 1745251881Speter#endif 1746251881Speter#if APR_HAS_ANSI_FS 1747251881Speter ELSE_WIN_OS_IS_ANSI 1748251881Speter { 1749251881Speter rv = SetFileAttributesA(fname, flags); 1750251881Speter } 1751251881Speter#endif 1752251881Speter 1753251881Speter if (rv == 0) 1754251881Speter return apr_get_os_error(); 1755251881Speter 1756251881Speter return APR_SUCCESS; 1757251881Speter} 1758251881Speter 1759251881Speter#endif 1760251881Speter 1761251881Spetersvn_error_t * 1762251881Spetersvn_io_set_file_read_write_carefully(const char *path, 1763251881Speter svn_boolean_t enable_write, 1764251881Speter svn_boolean_t ignore_enoent, 1765251881Speter apr_pool_t *pool) 1766251881Speter{ 1767251881Speter if (enable_write) 1768251881Speter return svn_io_set_file_read_write(path, ignore_enoent, pool); 1769251881Speter return svn_io_set_file_read_only(path, ignore_enoent, pool); 1770251881Speter} 1771251881Speter 1772251881Spetersvn_error_t * 1773251881Spetersvn_io_set_file_read_only(const char *path, 1774251881Speter svn_boolean_t ignore_enoent, 1775251881Speter apr_pool_t *pool) 1776251881Speter{ 1777251881Speter /* On Windows and OS/2, just set the file attributes -- on unix call 1778251881Speter our internal function which attempts to honor the umask. */ 1779251881Speter#if !defined(WIN32) && !defined(__OS2__) 1780251881Speter return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE, 1781251881Speter ignore_enoent, pool); 1782251881Speter#else 1783251881Speter apr_status_t status; 1784251881Speter const char *path_apr; 1785251881Speter 1786251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1787251881Speter 1788251881Speter status = apr_file_attrs_set(path_apr, 1789251881Speter APR_FILE_ATTR_READONLY, 1790251881Speter APR_FILE_ATTR_READONLY, 1791251881Speter pool); 1792251881Speter 1793251881Speter if (status && status != APR_ENOTIMPL) 1794251881Speter if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status)) 1795251881Speter return svn_error_wrap_apr(status, 1796251881Speter _("Can't set file '%s' read-only"), 1797251881Speter svn_dirent_local_style(path, pool)); 1798251881Speter 1799251881Speter return SVN_NO_ERROR; 1800251881Speter#endif 1801251881Speter} 1802251881Speter 1803251881Speter 1804251881Spetersvn_error_t * 1805251881Spetersvn_io_set_file_read_write(const char *path, 1806251881Speter svn_boolean_t ignore_enoent, 1807251881Speter apr_pool_t *pool) 1808251881Speter{ 1809251881Speter /* On Windows and OS/2, just set the file attributes -- on unix call 1810251881Speter our internal function which attempts to honor the umask. */ 1811251881Speter#if !defined(WIN32) && !defined(__OS2__) 1812251881Speter return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE, 1813251881Speter ignore_enoent, pool); 1814251881Speter#else 1815251881Speter apr_status_t status; 1816251881Speter const char *path_apr; 1817251881Speter 1818251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1819251881Speter 1820251881Speter status = apr_file_attrs_set(path_apr, 1821251881Speter 0, 1822251881Speter APR_FILE_ATTR_READONLY, 1823251881Speter pool); 1824251881Speter 1825251881Speter if (status && status != APR_ENOTIMPL) 1826251881Speter if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status)) 1827251881Speter return svn_error_wrap_apr(status, 1828251881Speter _("Can't set file '%s' read-write"), 1829251881Speter svn_dirent_local_style(path, pool)); 1830251881Speter 1831251881Speter return SVN_NO_ERROR; 1832251881Speter#endif 1833251881Speter} 1834251881Speter 1835251881Spetersvn_error_t * 1836251881Spetersvn_io_set_file_executable(const char *path, 1837251881Speter svn_boolean_t executable, 1838251881Speter svn_boolean_t ignore_enoent, 1839251881Speter apr_pool_t *pool) 1840251881Speter{ 1841251881Speter /* On Windows and OS/2, just exit -- on unix call our internal function 1842251881Speter which attempts to honor the umask. */ 1843251881Speter#if (!defined(WIN32) && !defined(__OS2__)) 1844251881Speter return io_set_file_perms(path, FALSE, FALSE, TRUE, executable, 1845251881Speter ignore_enoent, pool); 1846251881Speter#else 1847251881Speter return SVN_NO_ERROR; 1848251881Speter#endif 1849251881Speter} 1850251881Speter 1851251881Speter 1852251881Spetersvn_error_t * 1853251881Spetersvn_io__is_finfo_read_only(svn_boolean_t *read_only, 1854251881Speter apr_finfo_t *file_info, 1855251881Speter apr_pool_t *pool) 1856251881Speter{ 1857251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 1858251881Speter apr_status_t apr_err; 1859251881Speter apr_uid_t uid; 1860251881Speter apr_gid_t gid; 1861251881Speter 1862251881Speter *read_only = FALSE; 1863251881Speter 1864251881Speter apr_err = apr_uid_current(&uid, &gid, pool); 1865251881Speter 1866251881Speter if (apr_err) 1867251881Speter return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); 1868251881Speter 1869251881Speter /* Check write bit for current user. */ 1870251881Speter if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) 1871251881Speter *read_only = !(file_info->protection & APR_UWRITE); 1872251881Speter 1873251881Speter else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) 1874251881Speter *read_only = !(file_info->protection & APR_GWRITE); 1875251881Speter 1876251881Speter else 1877251881Speter *read_only = !(file_info->protection & APR_WWRITE); 1878251881Speter 1879251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 1880251881Speter *read_only = (file_info->protection & APR_FREADONLY); 1881251881Speter#endif 1882251881Speter 1883251881Speter return SVN_NO_ERROR; 1884251881Speter} 1885251881Speter 1886251881Spetersvn_error_t * 1887251881Spetersvn_io__is_finfo_executable(svn_boolean_t *executable, 1888251881Speter apr_finfo_t *file_info, 1889251881Speter apr_pool_t *pool) 1890251881Speter{ 1891251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 1892251881Speter apr_status_t apr_err; 1893251881Speter apr_uid_t uid; 1894251881Speter apr_gid_t gid; 1895251881Speter 1896251881Speter *executable = FALSE; 1897251881Speter 1898251881Speter apr_err = apr_uid_current(&uid, &gid, pool); 1899251881Speter 1900251881Speter if (apr_err) 1901251881Speter return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); 1902251881Speter 1903251881Speter /* Check executable bit for current user. */ 1904251881Speter if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) 1905251881Speter *executable = (file_info->protection & APR_UEXECUTE); 1906251881Speter 1907251881Speter else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) 1908251881Speter *executable = (file_info->protection & APR_GEXECUTE); 1909251881Speter 1910251881Speter else 1911251881Speter *executable = (file_info->protection & APR_WEXECUTE); 1912251881Speter 1913251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 1914251881Speter *executable = FALSE; 1915251881Speter#endif 1916251881Speter 1917251881Speter return SVN_NO_ERROR; 1918251881Speter} 1919251881Speter 1920251881Spetersvn_error_t * 1921251881Spetersvn_io_is_file_executable(svn_boolean_t *executable, 1922251881Speter const char *path, 1923251881Speter apr_pool_t *pool) 1924251881Speter{ 1925251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 1926251881Speter apr_finfo_t file_info; 1927251881Speter 1928251881Speter SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER, 1929251881Speter pool)); 1930251881Speter SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool)); 1931251881Speter 1932251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 1933251881Speter *executable = FALSE; 1934251881Speter#endif 1935251881Speter 1936251881Speter return SVN_NO_ERROR; 1937251881Speter} 1938251881Speter 1939251881Speter 1940251881Speter/*** File locking. ***/ 1941251881Speter#if !defined(WIN32) && !defined(__OS2__) 1942251881Speter/* Clear all outstanding locks on ARG, an open apr_file_t *. */ 1943251881Speterstatic apr_status_t 1944251881Speterfile_clear_locks(void *arg) 1945251881Speter{ 1946251881Speter apr_status_t apr_err; 1947251881Speter apr_file_t *f = arg; 1948251881Speter 1949251881Speter /* Remove locks. */ 1950251881Speter apr_err = apr_file_unlock(f); 1951251881Speter if (apr_err) 1952251881Speter return apr_err; 1953251881Speter 1954251881Speter return 0; 1955251881Speter} 1956251881Speter#endif 1957251881Speter 1958251881Spetersvn_error_t * 1959251881Spetersvn_io_lock_open_file(apr_file_t *lockfile_handle, 1960251881Speter svn_boolean_t exclusive, 1961251881Speter svn_boolean_t nonblocking, 1962251881Speter apr_pool_t *pool) 1963251881Speter{ 1964251881Speter int locktype = APR_FLOCK_SHARED; 1965251881Speter apr_status_t apr_err; 1966251881Speter const char *fname; 1967251881Speter 1968251881Speter if (exclusive) 1969251881Speter locktype = APR_FLOCK_EXCLUSIVE; 1970251881Speter if (nonblocking) 1971251881Speter locktype |= APR_FLOCK_NONBLOCK; 1972251881Speter 1973251881Speter /* We need this only in case of an error but this is cheap to get - 1974251881Speter * so we do it here for clarity. */ 1975251881Speter apr_err = apr_file_name_get(&fname, lockfile_handle); 1976251881Speter if (apr_err) 1977251881Speter return svn_error_wrap_apr(apr_err, _("Can't get file name")); 1978251881Speter 1979251881Speter /* Get lock on the filehandle. */ 1980251881Speter apr_err = apr_file_lock(lockfile_handle, locktype); 1981251881Speter 1982251881Speter /* In deployments with two or more multithreaded servers running on 1983251881Speter the same system serving two or more fsfs repositories it is 1984251881Speter possible for a deadlock to occur when getting a write lock on 1985251881Speter db/txn-current-lock: 1986251881Speter 1987251881Speter Process 1 Process 2 1988251881Speter --------- --------- 1989251881Speter thread 1: get lock in repos A 1990251881Speter thread 1: get lock in repos B 1991251881Speter thread 2: block getting lock in repos A 1992251881Speter thread 2: try to get lock in B *** deadlock *** 1993251881Speter 1994251881Speter Retry for a while for the deadlock to clear. */ 1995251881Speter FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype)); 1996251881Speter 1997251881Speter if (apr_err) 1998251881Speter { 1999251881Speter switch (locktype & APR_FLOCK_TYPEMASK) 2000251881Speter { 2001251881Speter case APR_FLOCK_SHARED: 2002251881Speter return svn_error_wrap_apr(apr_err, 2003251881Speter _("Can't get shared lock on file '%s'"), 2004251881Speter try_utf8_from_internal_style(fname, pool)); 2005251881Speter case APR_FLOCK_EXCLUSIVE: 2006251881Speter return svn_error_wrap_apr(apr_err, 2007251881Speter _("Can't get exclusive lock on file '%s'"), 2008251881Speter try_utf8_from_internal_style(fname, pool)); 2009251881Speter default: 2010251881Speter SVN_ERR_MALFUNCTION(); 2011251881Speter } 2012251881Speter } 2013251881Speter 2014251881Speter/* On Windows and OS/2 file locks are automatically released when 2015251881Speter the file handle closes */ 2016251881Speter#if !defined(WIN32) && !defined(__OS2__) 2017251881Speter apr_pool_cleanup_register(pool, lockfile_handle, 2018251881Speter file_clear_locks, 2019251881Speter apr_pool_cleanup_null); 2020251881Speter#endif 2021251881Speter 2022251881Speter return SVN_NO_ERROR; 2023251881Speter} 2024251881Speter 2025251881Spetersvn_error_t * 2026251881Spetersvn_io_unlock_open_file(apr_file_t *lockfile_handle, 2027251881Speter apr_pool_t *pool) 2028251881Speter{ 2029251881Speter const char *fname; 2030251881Speter apr_status_t apr_err; 2031251881Speter 2032251881Speter /* We need this only in case of an error but this is cheap to get - 2033251881Speter * so we do it here for clarity. */ 2034251881Speter apr_err = apr_file_name_get(&fname, lockfile_handle); 2035251881Speter if (apr_err) 2036251881Speter return svn_error_wrap_apr(apr_err, _("Can't get file name")); 2037251881Speter 2038251881Speter /* The actual unlock attempt. */ 2039251881Speter apr_err = apr_file_unlock(lockfile_handle); 2040251881Speter if (apr_err) 2041251881Speter return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"), 2042251881Speter try_utf8_from_internal_style(fname, pool)); 2043251881Speter 2044251881Speter/* On Windows and OS/2 file locks are automatically released when 2045251881Speter the file handle closes */ 2046251881Speter#if !defined(WIN32) && !defined(__OS2__) 2047251881Speter apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks); 2048251881Speter#endif 2049251881Speter 2050251881Speter return SVN_NO_ERROR; 2051251881Speter} 2052251881Speter 2053251881Spetersvn_error_t * 2054251881Spetersvn_io_file_lock2(const char *lock_file, 2055251881Speter svn_boolean_t exclusive, 2056251881Speter svn_boolean_t nonblocking, 2057251881Speter apr_pool_t *pool) 2058251881Speter{ 2059251881Speter int locktype = APR_FLOCK_SHARED; 2060251881Speter apr_file_t *lockfile_handle; 2061251881Speter apr_int32_t flags; 2062251881Speter 2063251881Speter if (exclusive) 2064251881Speter locktype = APR_FLOCK_EXCLUSIVE; 2065251881Speter 2066251881Speter flags = APR_READ; 2067251881Speter if (locktype == APR_FLOCK_EXCLUSIVE) 2068251881Speter flags |= APR_WRITE; 2069251881Speter 2070251881Speter /* locktype is never read after this block, so we don't need to bother 2071251881Speter setting it. If that were to ever change, uncomment the following 2072251881Speter block. 2073251881Speter if (nonblocking) 2074251881Speter locktype |= APR_FLOCK_NONBLOCK; 2075251881Speter */ 2076251881Speter 2077251881Speter SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags, 2078251881Speter APR_OS_DEFAULT, 2079251881Speter pool)); 2080251881Speter 2081251881Speter /* Get lock on the filehandle. */ 2082251881Speter return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool); 2083251881Speter} 2084251881Speter 2085251881Speter 2086251881Speter 2087251881Speter/* Data consistency/coherency operations. */ 2088251881Speter 2089251881Spetersvn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, 2090251881Speter apr_pool_t *pool) 2091251881Speter{ 2092251881Speter apr_os_file_t filehand; 2093251881Speter 2094251881Speter /* First make sure that any user-space buffered data is flushed. */ 2095251881Speter SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file), 2096251881Speter N_("Can't flush file '%s'"), 2097251881Speter N_("Can't flush stream"), 2098251881Speter pool)); 2099251881Speter 2100251881Speter apr_os_file_get(&filehand, file); 2101251881Speter 2102251881Speter /* Call the operating system specific function to actually force the 2103251881Speter data to disk. */ 2104251881Speter { 2105251881Speter#ifdef WIN32 2106251881Speter 2107251881Speter if (! FlushFileBuffers(filehand)) 2108251881Speter return svn_error_wrap_apr(apr_get_os_error(), 2109251881Speter _("Can't flush file to disk")); 2110251881Speter 2111251881Speter#else 2112251881Speter int rv; 2113251881Speter 2114251881Speter do { 2115251881Speter rv = fsync(filehand); 2116251881Speter } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); 2117251881Speter 2118251881Speter /* If the file is in a memory filesystem, fsync() may return 2119251881Speter EINVAL. Presumably the user knows the risks, and we can just 2120251881Speter ignore the error. */ 2121251881Speter if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error())) 2122251881Speter return SVN_NO_ERROR; 2123251881Speter 2124251881Speter if (rv == -1) 2125251881Speter return svn_error_wrap_apr(apr_get_os_error(), 2126251881Speter _("Can't flush file to disk")); 2127251881Speter 2128251881Speter#endif 2129251881Speter } 2130251881Speter return SVN_NO_ERROR; 2131251881Speter} 2132251881Speter 2133251881Speter 2134251881Speter 2135251881Speter/* TODO write test for these two functions, then refactor. */ 2136251881Speter 2137251881Speter/* Set RESULT to an svn_stringbuf_t containing the contents of FILE. 2138251881Speter FILENAME is the FILE's on-disk APR-safe name, or NULL if that name 2139251881Speter isn't known. If CHECK_SIZE is TRUE, the function will attempt to 2140251881Speter first stat() the file to determine it's size before sucking its 2141251881Speter contents into the stringbuf. (Doing so can prevent unnecessary 2142251881Speter memory usage, an unwanted side effect of the stringbuf growth and 2143251881Speter reallocation mechanism.) */ 2144251881Speterstatic svn_error_t * 2145251881Speterstringbuf_from_aprfile(svn_stringbuf_t **result, 2146251881Speter const char *filename, 2147251881Speter apr_file_t *file, 2148251881Speter svn_boolean_t check_size, 2149251881Speter apr_pool_t *pool) 2150251881Speter{ 2151251881Speter apr_size_t len; 2152251881Speter svn_error_t *err; 2153251881Speter svn_stringbuf_t *res = NULL; 2154251881Speter apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE; 2155251881Speter char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 2156251881Speter 2157251881Speter /* If our caller wants us to check the size of the file for 2158251881Speter efficient memory handling, we'll try to do so. */ 2159251881Speter if (check_size) 2160251881Speter { 2161251881Speter apr_status_t status; 2162251881Speter 2163251881Speter /* If our caller didn't tell us the file's name, we'll ask APR 2164251881Speter if it knows the name. No problem if we can't figure it out. */ 2165251881Speter if (! filename) 2166251881Speter { 2167251881Speter const char *filename_apr; 2168251881Speter if (! (status = apr_file_name_get(&filename_apr, file))) 2169251881Speter filename = filename_apr; 2170251881Speter } 2171251881Speter 2172251881Speter /* If we now know the filename, try to stat(). If we succeed, 2173251881Speter we know how to allocate our stringbuf. */ 2174251881Speter if (filename) 2175251881Speter { 2176251881Speter apr_finfo_t finfo; 2177251881Speter if (! (status = apr_stat(&finfo, filename, APR_FINFO_MIN, pool))) 2178251881Speter res_initial_len = (apr_size_t)finfo.size; 2179251881Speter } 2180251881Speter } 2181251881Speter 2182251881Speter 2183251881Speter /* XXX: We should check the incoming data for being of type binary. */ 2184251881Speter 2185251881Speter res = svn_stringbuf_create_ensure(res_initial_len, pool); 2186251881Speter 2187251881Speter /* apr_file_read will not return data and eof in the same call. So this loop 2188251881Speter * is safe from missing read data. */ 2189251881Speter len = SVN__STREAM_CHUNK_SIZE; 2190251881Speter err = svn_io_file_read(file, buf, &len, pool); 2191251881Speter while (! err) 2192251881Speter { 2193251881Speter svn_stringbuf_appendbytes(res, buf, len); 2194251881Speter len = SVN__STREAM_CHUNK_SIZE; 2195251881Speter err = svn_io_file_read(file, buf, &len, pool); 2196251881Speter } 2197251881Speter 2198251881Speter /* Having read all the data we *expect* EOF */ 2199251881Speter if (err && !APR_STATUS_IS_EOF(err->apr_err)) 2200251881Speter return err; 2201251881Speter svn_error_clear(err); 2202251881Speter 2203251881Speter *result = res; 2204251881Speter return SVN_NO_ERROR; 2205251881Speter} 2206251881Speter 2207251881Spetersvn_error_t * 2208251881Spetersvn_stringbuf_from_file2(svn_stringbuf_t **result, 2209251881Speter const char *filename, 2210251881Speter apr_pool_t *pool) 2211251881Speter{ 2212251881Speter apr_file_t *f; 2213251881Speter 2214251881Speter if (filename[0] == '-' && filename[1] == '\0') 2215251881Speter { 2216251881Speter apr_status_t apr_err; 2217251881Speter if ((apr_err = apr_file_open_stdin(&f, pool))) 2218251881Speter return svn_error_wrap_apr(apr_err, _("Can't open stdin")); 2219251881Speter SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool)); 2220251881Speter } 2221251881Speter else 2222251881Speter { 2223251881Speter SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool)); 2224251881Speter SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool)); 2225251881Speter } 2226251881Speter return svn_io_file_close(f, pool); 2227251881Speter} 2228251881Speter 2229251881Speter 2230251881Spetersvn_error_t * 2231251881Spetersvn_stringbuf_from_file(svn_stringbuf_t **result, 2232251881Speter const char *filename, 2233251881Speter apr_pool_t *pool) 2234251881Speter{ 2235251881Speter if (filename[0] == '-' && filename[1] == '\0') 2236251881Speter return svn_error_create 2237251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2238251881Speter _("Reading from stdin is disallowed")); 2239251881Speter return svn_stringbuf_from_file2(result, filename, pool); 2240251881Speter} 2241251881Speter 2242251881Spetersvn_error_t * 2243251881Spetersvn_stringbuf_from_aprfile(svn_stringbuf_t **result, 2244251881Speter apr_file_t *file, 2245251881Speter apr_pool_t *pool) 2246251881Speter{ 2247251881Speter return stringbuf_from_aprfile(result, NULL, file, TRUE, pool); 2248251881Speter} 2249251881Speter 2250251881Speter 2251251881Speter 2252251881Speter/* Deletion. */ 2253251881Speter 2254251881Spetersvn_error_t * 2255251881Spetersvn_io_remove_file2(const char *path, 2256251881Speter svn_boolean_t ignore_enoent, 2257251881Speter apr_pool_t *scratch_pool) 2258251881Speter{ 2259251881Speter apr_status_t apr_err; 2260251881Speter const char *path_apr; 2261251881Speter 2262251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool)); 2263251881Speter 2264251881Speter apr_err = apr_file_remove(path_apr, scratch_pool); 2265251881Speter if (!apr_err 2266251881Speter || (ignore_enoent 2267251881Speter && (APR_STATUS_IS_ENOENT(apr_err) 2268251881Speter || SVN__APR_STATUS_IS_ENOTDIR(apr_err)))) 2269251881Speter return SVN_NO_ERROR; 2270251881Speter 2271251881Speter#ifdef WIN32 2272251881Speter /* If the target is read only NTFS reports EACCESS and FAT/FAT32 2273251881Speter reports EEXIST */ 2274251881Speter if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err)) 2275251881Speter { 2276251881Speter /* Set the destination file writable because Windows will not 2277251881Speter allow us to delete when path is read-only */ 2278251881Speter SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool)); 2279251881Speter apr_err = apr_file_remove(path_apr, scratch_pool); 2280251881Speter 2281251881Speter if (!apr_err) 2282251881Speter return SVN_NO_ERROR; 2283251881Speter } 2284251881Speter 2285251881Speter { 2286251881Speter apr_status_t os_err = APR_TO_OS_ERROR(apr_err); 2287251881Speter /* Check to make sure we aren't trying to delete a directory */ 2288251881Speter if (os_err == ERROR_ACCESS_DENIED || os_err == ERROR_SHARING_VIOLATION) 2289251881Speter { 2290251881Speter apr_finfo_t finfo; 2291251881Speter 2292251881Speter if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool) 2293251881Speter && finfo.filetype == APR_REG) 2294251881Speter { 2295251881Speter WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr, 2296251881Speter scratch_pool)); 2297251881Speter } 2298251881Speter } 2299251881Speter 2300251881Speter /* Just return the delete error */ 2301251881Speter } 2302251881Speter#endif 2303251881Speter 2304251881Speter if (apr_err) 2305251881Speter return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"), 2306251881Speter svn_dirent_local_style(path, scratch_pool)); 2307251881Speter 2308251881Speter return SVN_NO_ERROR; 2309251881Speter} 2310251881Speter 2311251881Speter 2312251881Spetersvn_error_t * 2313251881Spetersvn_io_remove_dir(const char *path, apr_pool_t *pool) 2314251881Speter{ 2315251881Speter return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool); 2316251881Speter} 2317251881Speter 2318251881Speter/* 2319251881Speter Mac OS X has a bug where if you're reading the contents of a 2320251881Speter directory via readdir in a loop, and you remove one of the entries in 2321251881Speter the directory and the directory has 338 or more files in it you will 2322251881Speter skip over some of the entries in the directory. Needless to say, 2323251881Speter this causes problems if you are using this kind of loop inside a 2324251881Speter function that is recursively deleting a directory, because when you 2325251881Speter get around to removing the directory it will still have something in 2326251881Speter it. A similar problem has been observed in other BSDs. This bug has 2327251881Speter since been fixed. See http://www.vnode.ch/fixing_seekdir for details. 2328251881Speter 2329251881Speter The workaround is to delete the files only _after_ the initial 2330251881Speter directory scan. A previous workaround involving rewinddir is 2331251881Speter problematic on Win32 and some NFS clients, notably NetBSD. 2332251881Speter 2333251881Speter See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and 2334251881Speter http://subversion.tigris.org/issues/show_bug.cgi?id=3501. 2335251881Speter*/ 2336251881Speter 2337251881Speter/* Neither windows nor unix allows us to delete a non-empty 2338251881Speter directory. 2339251881Speter 2340251881Speter This is a function to perform the equivalent of 'rm -rf'. */ 2341251881Spetersvn_error_t * 2342251881Spetersvn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent, 2343251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 2344251881Speter apr_pool_t *pool) 2345251881Speter{ 2346251881Speter svn_error_t *err; 2347251881Speter apr_pool_t *subpool; 2348251881Speter apr_hash_t *dirents; 2349251881Speter apr_hash_index_t *hi; 2350251881Speter 2351251881Speter /* Check for pending cancellation request. 2352251881Speter If we need to bail out, do so early. */ 2353251881Speter 2354251881Speter if (cancel_func) 2355251881Speter SVN_ERR((*cancel_func)(cancel_baton)); 2356251881Speter 2357251881Speter subpool = svn_pool_create(pool); 2358251881Speter 2359251881Speter err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool); 2360251881Speter if (err) 2361251881Speter { 2362251881Speter /* if the directory doesn't exist, our mission is accomplished */ 2363251881Speter if (ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err)) 2364251881Speter { 2365251881Speter svn_error_clear(err); 2366251881Speter return SVN_NO_ERROR; 2367251881Speter } 2368251881Speter return svn_error_trace(err); 2369251881Speter } 2370251881Speter 2371251881Speter for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi)) 2372251881Speter { 2373251881Speter const char *name = svn__apr_hash_index_key(hi); 2374251881Speter const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi); 2375251881Speter const char *fullpath; 2376251881Speter 2377251881Speter fullpath = svn_dirent_join(path, name, subpool); 2378251881Speter if (dirent->kind == svn_node_dir) 2379251881Speter { 2380251881Speter /* Don't check for cancellation, the callee will immediately do so */ 2381251881Speter SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func, 2382251881Speter cancel_baton, subpool)); 2383251881Speter } 2384251881Speter else 2385251881Speter { 2386251881Speter if (cancel_func) 2387251881Speter SVN_ERR((*cancel_func)(cancel_baton)); 2388251881Speter 2389251881Speter err = svn_io_remove_file2(fullpath, FALSE, subpool); 2390251881Speter if (err) 2391251881Speter return svn_error_createf 2392251881Speter (err->apr_err, err, _("Can't remove '%s'"), 2393251881Speter svn_dirent_local_style(fullpath, subpool)); 2394251881Speter } 2395251881Speter } 2396251881Speter 2397251881Speter svn_pool_destroy(subpool); 2398251881Speter 2399251881Speter return svn_io_dir_remove_nonrecursive(path, pool); 2400251881Speter} 2401251881Speter 2402251881Spetersvn_error_t * 2403251881Spetersvn_io_get_dir_filenames(apr_hash_t **dirents, 2404251881Speter const char *path, 2405251881Speter apr_pool_t *pool) 2406251881Speter{ 2407251881Speter return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE, 2408251881Speter pool, pool)); 2409251881Speter} 2410251881Speter 2411251881Spetersvn_io_dirent2_t * 2412251881Spetersvn_io_dirent2_create(apr_pool_t *result_pool) 2413251881Speter{ 2414251881Speter svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent)); 2415251881Speter 2416251881Speter /*dirent->kind = svn_node_none; 2417251881Speter dirent->special = FALSE;*/ 2418251881Speter dirent->filesize = SVN_INVALID_FILESIZE; 2419251881Speter /*dirent->mtime = 0;*/ 2420251881Speter 2421251881Speter return dirent; 2422251881Speter} 2423251881Speter 2424251881Spetersvn_io_dirent2_t * 2425251881Spetersvn_io_dirent2_dup(const svn_io_dirent2_t *item, 2426251881Speter apr_pool_t *result_pool) 2427251881Speter{ 2428251881Speter return apr_pmemdup(result_pool, 2429251881Speter item, 2430251881Speter sizeof(*item)); 2431251881Speter} 2432251881Speter 2433251881Spetersvn_error_t * 2434251881Spetersvn_io_get_dirents3(apr_hash_t **dirents, 2435251881Speter const char *path, 2436251881Speter svn_boolean_t only_check_type, 2437251881Speter apr_pool_t *result_pool, 2438251881Speter apr_pool_t *scratch_pool) 2439251881Speter{ 2440251881Speter apr_status_t status; 2441251881Speter apr_dir_t *this_dir; 2442251881Speter apr_finfo_t this_entry; 2443251881Speter apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; 2444251881Speter 2445251881Speter if (!only_check_type) 2446251881Speter flags |= APR_FINFO_SIZE | APR_FINFO_MTIME; 2447251881Speter 2448251881Speter *dirents = apr_hash_make(result_pool); 2449251881Speter 2450251881Speter SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool)); 2451251881Speter 2452251881Speter for (status = apr_dir_read(&this_entry, flags, this_dir); 2453251881Speter status == APR_SUCCESS; 2454251881Speter status = apr_dir_read(&this_entry, flags, this_dir)) 2455251881Speter { 2456251881Speter if ((this_entry.name[0] == '.') 2457251881Speter && ((this_entry.name[1] == '\0') 2458251881Speter || ((this_entry.name[1] == '.') 2459251881Speter && (this_entry.name[2] == '\0')))) 2460251881Speter { 2461251881Speter continue; 2462251881Speter } 2463251881Speter else 2464251881Speter { 2465251881Speter const char *name; 2466251881Speter svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool); 2467251881Speter 2468251881Speter SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool)); 2469251881Speter 2470251881Speter map_apr_finfo_to_node_kind(&(dirent->kind), 2471251881Speter &(dirent->special), 2472251881Speter &this_entry); 2473251881Speter 2474251881Speter if (!only_check_type) 2475251881Speter { 2476251881Speter dirent->filesize = this_entry.size; 2477251881Speter dirent->mtime = this_entry.mtime; 2478251881Speter } 2479251881Speter 2480251881Speter svn_hash_sets(*dirents, name, dirent); 2481251881Speter } 2482251881Speter } 2483251881Speter 2484251881Speter if (! (APR_STATUS_IS_ENOENT(status))) 2485251881Speter return svn_error_wrap_apr(status, _("Can't read directory '%s'"), 2486251881Speter svn_dirent_local_style(path, scratch_pool)); 2487251881Speter 2488251881Speter status = apr_dir_close(this_dir); 2489251881Speter if (status) 2490251881Speter return svn_error_wrap_apr(status, _("Error closing directory '%s'"), 2491251881Speter svn_dirent_local_style(path, scratch_pool)); 2492251881Speter 2493251881Speter return SVN_NO_ERROR; 2494251881Speter} 2495251881Speter 2496251881Spetersvn_error_t * 2497251881Spetersvn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p, 2498251881Speter const char *path, 2499251881Speter svn_boolean_t verify_truename, 2500251881Speter svn_boolean_t ignore_enoent, 2501251881Speter apr_pool_t *result_pool, 2502251881Speter apr_pool_t *scratch_pool) 2503251881Speter{ 2504251881Speter apr_finfo_t finfo; 2505251881Speter svn_io_dirent2_t *dirent; 2506251881Speter svn_error_t *err; 2507251881Speter apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK 2508251881Speter | APR_FINFO_SIZE | APR_FINFO_MTIME; 2509251881Speter 2510251881Speter#if defined(WIN32) || defined(__OS2__) 2511251881Speter if (verify_truename) 2512251881Speter wanted |= APR_FINFO_NAME; 2513251881Speter#endif 2514251881Speter 2515251881Speter err = svn_io_stat(&finfo, path, wanted, scratch_pool); 2516251881Speter 2517251881Speter if (err && ignore_enoent && 2518251881Speter (APR_STATUS_IS_ENOENT(err->apr_err) 2519251881Speter || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 2520251881Speter { 2521251881Speter svn_error_clear(err); 2522251881Speter dirent = svn_io_dirent2_create(result_pool); 2523251881Speter SVN_ERR_ASSERT(dirent->kind == svn_node_none); 2524251881Speter 2525251881Speter *dirent_p = dirent; 2526251881Speter return SVN_NO_ERROR; 2527251881Speter } 2528251881Speter SVN_ERR(err); 2529251881Speter 2530251881Speter#if defined(WIN32) || defined(__OS2__) || defined(DARWIN) 2531251881Speter if (verify_truename) 2532251881Speter { 2533251881Speter const char *requested_name = svn_dirent_basename(path, NULL); 2534251881Speter 2535251881Speter if (requested_name[0] == '\0') 2536251881Speter { 2537251881Speter /* No parent directory. No need to stat/verify */ 2538251881Speter } 2539251881Speter#if defined(WIN32) || defined(__OS2__) 2540251881Speter else if (finfo.name) 2541251881Speter { 2542251881Speter const char *name_on_disk; 2543251881Speter SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path, 2544251881Speter scratch_pool)); 2545251881Speter 2546251881Speter if (strcmp(name_on_disk, requested_name) /* != 0 */) 2547251881Speter { 2548251881Speter if (ignore_enoent) 2549251881Speter { 2550251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2551251881Speter return SVN_NO_ERROR; 2552251881Speter } 2553251881Speter else 2554251881Speter return svn_error_createf(APR_ENOENT, NULL, 2555251881Speter _("Path '%s' not found, case obstructed by '%s'"), 2556251881Speter svn_dirent_local_style(path, scratch_pool), 2557251881Speter name_on_disk); 2558251881Speter } 2559251881Speter } 2560251881Speter#elif defined(DARWIN) 2561251881Speter /* Currently apr doesn't set finfo.name on DARWIN, returning 2562251881Speter APR_INCOMPLETE. 2563251881Speter ### Can we optimize this in another way? */ 2564251881Speter else 2565251881Speter { 2566251881Speter apr_hash_t *dirents; 2567251881Speter 2568251881Speter err = svn_io_get_dirents3(&dirents, 2569251881Speter svn_dirent_dirname(path, scratch_pool), 2570251881Speter TRUE /* only_check_type */, 2571251881Speter scratch_pool, scratch_pool); 2572251881Speter 2573251881Speter if (err && ignore_enoent 2574251881Speter && (APR_STATUS_IS_ENOENT(err->apr_err) 2575251881Speter || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 2576251881Speter { 2577251881Speter svn_error_clear(err); 2578251881Speter 2579251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2580251881Speter return SVN_NO_ERROR; 2581251881Speter } 2582251881Speter else 2583251881Speter SVN_ERR(err); 2584251881Speter 2585251881Speter if (! svn_hash_gets(dirents, requested_name)) 2586251881Speter { 2587251881Speter if (ignore_enoent) 2588251881Speter { 2589251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2590251881Speter return SVN_NO_ERROR; 2591251881Speter } 2592251881Speter else 2593251881Speter return svn_error_createf(APR_ENOENT, NULL, 2594251881Speter _("Path '%s' not found"), 2595251881Speter svn_dirent_local_style(path, scratch_pool)); 2596251881Speter } 2597251881Speter } 2598251881Speter#endif 2599251881Speter } 2600251881Speter#endif 2601251881Speter 2602251881Speter dirent = svn_io_dirent2_create(result_pool); 2603251881Speter map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo); 2604251881Speter 2605251881Speter dirent->filesize = finfo.size; 2606251881Speter dirent->mtime = finfo.mtime; 2607251881Speter 2608251881Speter *dirent_p = dirent; 2609251881Speter 2610251881Speter return SVN_NO_ERROR; 2611251881Speter} 2612251881Speter 2613251881Speter/* Pool userdata key for the error file passed to svn_io_start_cmd(). */ 2614251881Speter#define ERRFILE_KEY "svn-io-start-cmd-errfile" 2615251881Speter 2616251881Speter/* Handle an error from the child process (before command execution) by 2617251881Speter printing DESC and the error string corresponding to STATUS to stderr. */ 2618251881Speterstatic void 2619251881Speterhandle_child_process_error(apr_pool_t *pool, apr_status_t status, 2620251881Speter const char *desc) 2621251881Speter{ 2622251881Speter char errbuf[256]; 2623251881Speter apr_file_t *errfile; 2624251881Speter void *p; 2625251881Speter 2626251881Speter /* We can't do anything if we get an error here, so just return. */ 2627251881Speter if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool)) 2628251881Speter return; 2629251881Speter errfile = p; 2630251881Speter 2631251881Speter if (errfile) 2632251881Speter /* What we get from APR is in native encoding. */ 2633251881Speter apr_file_printf(errfile, "%s: %s", 2634251881Speter desc, apr_strerror(status, errbuf, 2635251881Speter sizeof(errbuf))); 2636251881Speter} 2637251881Speter 2638251881Speter 2639251881Spetersvn_error_t * 2640251881Spetersvn_io_start_cmd3(apr_proc_t *cmd_proc, 2641251881Speter const char *path, 2642251881Speter const char *cmd, 2643251881Speter const char *const *args, 2644251881Speter const char *const *env, 2645251881Speter svn_boolean_t inherit, 2646251881Speter svn_boolean_t infile_pipe, 2647251881Speter apr_file_t *infile, 2648251881Speter svn_boolean_t outfile_pipe, 2649251881Speter apr_file_t *outfile, 2650251881Speter svn_boolean_t errfile_pipe, 2651251881Speter apr_file_t *errfile, 2652251881Speter apr_pool_t *pool) 2653251881Speter{ 2654251881Speter apr_status_t apr_err; 2655251881Speter apr_procattr_t *cmdproc_attr; 2656251881Speter int num_args; 2657251881Speter const char **args_native; 2658251881Speter const char *cmd_apr; 2659251881Speter 2660251881Speter SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe)); 2661251881Speter SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe)); 2662251881Speter SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe)); 2663251881Speter 2664251881Speter /* Create the process attributes. */ 2665251881Speter apr_err = apr_procattr_create(&cmdproc_attr, pool); 2666251881Speter if (apr_err) 2667251881Speter return svn_error_wrap_apr(apr_err, 2668251881Speter _("Can't create process '%s' attributes"), 2669251881Speter cmd); 2670251881Speter 2671251881Speter /* Make sure we invoke cmd directly, not through a shell. */ 2672251881Speter apr_err = apr_procattr_cmdtype_set(cmdproc_attr, 2673251881Speter inherit ? APR_PROGRAM_PATH : APR_PROGRAM); 2674251881Speter if (apr_err) 2675251881Speter return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"), 2676251881Speter cmd); 2677251881Speter 2678251881Speter /* Set the process's working directory. */ 2679251881Speter if (path) 2680251881Speter { 2681251881Speter const char *path_apr; 2682251881Speter 2683251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 2684251881Speter apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr); 2685251881Speter if (apr_err) 2686251881Speter return svn_error_wrap_apr(apr_err, 2687251881Speter _("Can't set process '%s' directory"), 2688251881Speter cmd); 2689251881Speter } 2690251881Speter 2691251881Speter /* Use requested inputs and outputs. 2692251881Speter 2693251881Speter ### Unfortunately each of these apr functions creates a pipe and then 2694251881Speter overwrites the pipe file descriptor with the descriptor we pass 2695251881Speter in. The pipes can then never be closed. This is an APR bug. */ 2696251881Speter if (infile) 2697251881Speter { 2698251881Speter apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL); 2699251881Speter if (apr_err) 2700251881Speter return svn_error_wrap_apr(apr_err, 2701251881Speter _("Can't set process '%s' child input"), 2702251881Speter cmd); 2703251881Speter } 2704251881Speter if (outfile) 2705251881Speter { 2706251881Speter apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL); 2707251881Speter if (apr_err) 2708251881Speter return svn_error_wrap_apr(apr_err, 2709251881Speter _("Can't set process '%s' child outfile"), 2710251881Speter cmd); 2711251881Speter } 2712251881Speter if (errfile) 2713251881Speter { 2714251881Speter apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL); 2715251881Speter if (apr_err) 2716251881Speter return svn_error_wrap_apr(apr_err, 2717251881Speter _("Can't set process '%s' child errfile"), 2718251881Speter cmd); 2719251881Speter } 2720251881Speter 2721251881Speter /* Forward request for pipes to APR. */ 2722251881Speter if (infile_pipe || outfile_pipe || errfile_pipe) 2723251881Speter { 2724251881Speter apr_err = apr_procattr_io_set(cmdproc_attr, 2725251881Speter infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE, 2726251881Speter outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE, 2727251881Speter errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE); 2728251881Speter 2729251881Speter if (apr_err) 2730251881Speter return svn_error_wrap_apr(apr_err, 2731251881Speter _("Can't set process '%s' stdio pipes"), 2732251881Speter cmd); 2733251881Speter } 2734251881Speter 2735251881Speter /* Have the child print any problems executing its program to errfile. */ 2736251881Speter apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool); 2737251881Speter if (apr_err) 2738251881Speter return svn_error_wrap_apr(apr_err, 2739251881Speter _("Can't set process '%s' child errfile for " 2740251881Speter "error handler"), 2741251881Speter cmd); 2742251881Speter apr_err = apr_procattr_child_errfn_set(cmdproc_attr, 2743251881Speter handle_child_process_error); 2744251881Speter if (apr_err) 2745251881Speter return svn_error_wrap_apr(apr_err, 2746251881Speter _("Can't set process '%s' error handler"), 2747251881Speter cmd); 2748251881Speter 2749251881Speter /* Convert cmd and args from UTF-8 */ 2750251881Speter SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool)); 2751251881Speter for (num_args = 0; args[num_args]; num_args++) 2752251881Speter ; 2753251881Speter args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *)); 2754251881Speter args_native[num_args] = NULL; 2755251881Speter while (num_args--) 2756251881Speter { 2757251881Speter /* ### Well, it turns out that on APR on Windows expects all 2758251881Speter program args to be in UTF-8. Callers of svn_io_run_cmd 2759251881Speter should be aware of that. */ 2760251881Speter SVN_ERR(cstring_from_utf8(&args_native[num_args], 2761251881Speter args[num_args], pool)); 2762251881Speter } 2763251881Speter 2764251881Speter 2765251881Speter /* Start the cmd command. */ 2766251881Speter apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native, 2767251881Speter inherit ? NULL : env, cmdproc_attr, pool); 2768251881Speter if (apr_err) 2769251881Speter return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd); 2770251881Speter 2771251881Speter return SVN_NO_ERROR; 2772251881Speter} 2773251881Speter 2774251881Speter#undef ERRFILE_KEY 2775251881Speter 2776251881Spetersvn_error_t * 2777251881Spetersvn_io_wait_for_cmd(apr_proc_t *cmd_proc, 2778251881Speter const char *cmd, 2779251881Speter int *exitcode, 2780251881Speter apr_exit_why_e *exitwhy, 2781251881Speter apr_pool_t *pool) 2782251881Speter{ 2783251881Speter apr_status_t apr_err; 2784251881Speter apr_exit_why_e exitwhy_val; 2785251881Speter int exitcode_val; 2786251881Speter 2787251881Speter /* The Win32 apr_proc_wait doesn't set this... */ 2788251881Speter exitwhy_val = APR_PROC_EXIT; 2789251881Speter 2790251881Speter /* Wait for the cmd command to finish. */ 2791251881Speter apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT); 2792251881Speter if (!APR_STATUS_IS_CHILD_DONE(apr_err)) 2793251881Speter return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"), 2794251881Speter cmd); 2795251881Speter 2796251881Speter if (exitwhy) 2797251881Speter *exitwhy = exitwhy_val; 2798251881Speter else if (APR_PROC_CHECK_SIGNALED(exitwhy_val) 2799251881Speter && APR_PROC_CHECK_CORE_DUMP(exitwhy_val)) 2800251881Speter return svn_error_createf 2801251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 2802251881Speter _("Process '%s' failed (signal %d, core dumped)"), 2803251881Speter cmd, exitcode_val); 2804251881Speter else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)) 2805251881Speter return svn_error_createf 2806251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 2807251881Speter _("Process '%s' failed (signal %d)"), 2808251881Speter cmd, exitcode_val); 2809251881Speter else if (! APR_PROC_CHECK_EXIT(exitwhy_val)) 2810251881Speter /* Don't really know what happened here. */ 2811251881Speter return svn_error_createf 2812251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 2813251881Speter _("Process '%s' failed (exitwhy %d, exitcode %d)"), 2814251881Speter cmd, exitwhy_val, exitcode_val); 2815251881Speter 2816251881Speter if (exitcode) 2817251881Speter *exitcode = exitcode_val; 2818251881Speter else if (exitcode_val != 0) 2819251881Speter return svn_error_createf 2820251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 2821251881Speter _("Process '%s' returned error exitcode %d"), cmd, exitcode_val); 2822251881Speter 2823251881Speter return SVN_NO_ERROR; 2824251881Speter} 2825251881Speter 2826251881Speter 2827251881Spetersvn_error_t * 2828251881Spetersvn_io_run_cmd(const char *path, 2829251881Speter const char *cmd, 2830251881Speter const char *const *args, 2831251881Speter int *exitcode, 2832251881Speter apr_exit_why_e *exitwhy, 2833251881Speter svn_boolean_t inherit, 2834251881Speter apr_file_t *infile, 2835251881Speter apr_file_t *outfile, 2836251881Speter apr_file_t *errfile, 2837251881Speter apr_pool_t *pool) 2838251881Speter{ 2839251881Speter apr_proc_t cmd_proc; 2840251881Speter 2841251881Speter SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit, 2842251881Speter FALSE, infile, FALSE, outfile, FALSE, errfile, 2843251881Speter pool)); 2844251881Speter 2845251881Speter return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool); 2846251881Speter} 2847251881Speter 2848251881Speter 2849251881Spetersvn_error_t * 2850251881Spetersvn_io_run_diff2(const char *dir, 2851251881Speter const char *const *user_args, 2852251881Speter int num_user_args, 2853251881Speter const char *label1, 2854251881Speter const char *label2, 2855251881Speter const char *from, 2856251881Speter const char *to, 2857251881Speter int *pexitcode, 2858251881Speter apr_file_t *outfile, 2859251881Speter apr_file_t *errfile, 2860251881Speter const char *diff_cmd, 2861251881Speter apr_pool_t *pool) 2862251881Speter{ 2863251881Speter const char **args; 2864251881Speter int i; 2865251881Speter int exitcode; 2866251881Speter int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */ 2867251881Speter apr_pool_t *subpool = svn_pool_create(pool); 2868251881Speter 2869251881Speter if (pexitcode == NULL) 2870251881Speter pexitcode = &exitcode; 2871251881Speter 2872251881Speter if (user_args != NULL) 2873251881Speter nargs += num_user_args; 2874251881Speter else 2875251881Speter nargs += 1; /* -u */ 2876251881Speter 2877251881Speter if (label1 != NULL) 2878251881Speter nargs += 2; /* the -L and the label itself */ 2879251881Speter if (label2 != NULL) 2880251881Speter nargs += 2; /* the -L and the label itself */ 2881251881Speter 2882251881Speter args = apr_palloc(subpool, nargs * sizeof(char *)); 2883251881Speter 2884251881Speter i = 0; 2885251881Speter args[i++] = diff_cmd; 2886251881Speter 2887251881Speter if (user_args != NULL) 2888251881Speter { 2889251881Speter int j; 2890251881Speter for (j = 0; j < num_user_args; ++j) 2891251881Speter args[i++] = user_args[j]; 2892251881Speter } 2893251881Speter else 2894251881Speter args[i++] = "-u"; /* assume -u if the user didn't give us any args */ 2895251881Speter 2896251881Speter if (label1 != NULL) 2897251881Speter { 2898251881Speter args[i++] = "-L"; 2899251881Speter args[i++] = label1; 2900251881Speter } 2901251881Speter if (label2 != NULL) 2902251881Speter { 2903251881Speter args[i++] = "-L"; 2904251881Speter args[i++] = label2; 2905251881Speter } 2906251881Speter 2907251881Speter args[i++] = svn_dirent_local_style(from, subpool); 2908251881Speter args[i++] = svn_dirent_local_style(to, subpool); 2909251881Speter args[i++] = NULL; 2910251881Speter 2911251881Speter SVN_ERR_ASSERT(i == nargs); 2912251881Speter 2913251881Speter SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE, 2914251881Speter NULL, outfile, errfile, subpool)); 2915251881Speter 2916251881Speter /* The man page for (GNU) diff describes the return value as: 2917251881Speter 2918251881Speter "An exit status of 0 means no differences were found, 1 means 2919251881Speter some differences were found, and 2 means trouble." 2920251881Speter 2921251881Speter A return value of 2 typically occurs when diff cannot read its input 2922251881Speter or write to its output, but in any case we probably ought to return an 2923251881Speter error for anything other than 0 or 1 as the output is likely to be 2924251881Speter corrupt. 2925251881Speter */ 2926251881Speter if (*pexitcode != 0 && *pexitcode != 1) 2927251881Speter return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 2928251881Speter _("'%s' returned %d"), 2929251881Speter svn_dirent_local_style(diff_cmd, pool), 2930251881Speter *pexitcode); 2931251881Speter 2932251881Speter svn_pool_destroy(subpool); 2933251881Speter 2934251881Speter return SVN_NO_ERROR; 2935251881Speter} 2936251881Speter 2937251881Speter 2938251881Spetersvn_error_t * 2939251881Spetersvn_io_run_diff3_3(int *exitcode, 2940251881Speter const char *dir, 2941251881Speter const char *mine, 2942251881Speter const char *older, 2943251881Speter const char *yours, 2944251881Speter const char *mine_label, 2945251881Speter const char *older_label, 2946251881Speter const char *yours_label, 2947251881Speter apr_file_t *merged, 2948251881Speter const char *diff3_cmd, 2949251881Speter const apr_array_header_t *user_args, 2950251881Speter apr_pool_t *pool) 2951251881Speter{ 2952251881Speter const char **args = apr_palloc(pool, 2953251881Speter sizeof(char*) * (13 2954251881Speter + (user_args 2955251881Speter ? user_args->nelts 2956251881Speter : 1))); 2957251881Speter#ifndef NDEBUG 2958251881Speter int nargs = 12; 2959251881Speter#endif 2960251881Speter int i = 0; 2961251881Speter 2962251881Speter /* Labels fall back to sensible defaults if not specified. */ 2963251881Speter if (mine_label == NULL) 2964251881Speter mine_label = ".working"; 2965251881Speter if (older_label == NULL) 2966251881Speter older_label = ".old"; 2967251881Speter if (yours_label == NULL) 2968251881Speter yours_label = ".new"; 2969251881Speter 2970251881Speter /* Set up diff3 command line. */ 2971251881Speter args[i++] = diff3_cmd; 2972251881Speter if (user_args) 2973251881Speter { 2974251881Speter int j; 2975251881Speter for (j = 0; j < user_args->nelts; ++j) 2976251881Speter args[i++] = APR_ARRAY_IDX(user_args, j, const char *); 2977251881Speter#ifndef NDEBUG 2978251881Speter nargs += user_args->nelts; 2979251881Speter#endif 2980251881Speter } 2981251881Speter else 2982251881Speter { 2983251881Speter args[i++] = "-E"; /* We tried "-A" here, but that caused 2984251881Speter overlapping identical changes to 2985251881Speter conflict. See issue #682. */ 2986251881Speter#ifndef NDEBUG 2987251881Speter ++nargs; 2988251881Speter#endif 2989251881Speter } 2990251881Speter args[i++] = "-m"; 2991251881Speter args[i++] = "-L"; 2992251881Speter args[i++] = mine_label; 2993251881Speter args[i++] = "-L"; 2994251881Speter args[i++] = older_label; /* note: this label is ignored if 2995251881Speter using 2-part markers, which is the 2996251881Speter case with "-E". */ 2997251881Speter args[i++] = "-L"; 2998251881Speter args[i++] = yours_label; 2999251881Speter#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG 3000251881Speter { 3001251881Speter svn_boolean_t has_arg; 3002251881Speter 3003251881Speter /* ### FIXME: we really shouldn't be reading the config here; 3004251881Speter instead, the necessary bits should be passed in by the caller. 3005251881Speter But should we add another parameter to this function, when the 3006251881Speter whole external diff3 thing might eventually go away? */ 3007251881Speter apr_hash_t *config; 3008251881Speter svn_config_t *cfg; 3009251881Speter 3010251881Speter SVN_ERR(svn_config_get_config(&config, pool)); 3011251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 3012251881Speter SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS, 3013251881Speter SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG, 3014251881Speter TRUE)); 3015251881Speter if (has_arg) 3016251881Speter { 3017251881Speter const char *diff_cmd, *diff_utf8; 3018251881Speter svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS, 3019251881Speter SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF); 3020251881Speter SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool)); 3021251881Speter args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL); 3022251881Speter#ifndef NDEBUG 3023251881Speter ++nargs; 3024251881Speter#endif 3025251881Speter } 3026251881Speter } 3027251881Speter#endif 3028251881Speter args[i++] = svn_dirent_local_style(mine, pool); 3029251881Speter args[i++] = svn_dirent_local_style(older, pool); 3030251881Speter args[i++] = svn_dirent_local_style(yours, pool); 3031251881Speter args[i++] = NULL; 3032251881Speter#ifndef NDEBUG 3033251881Speter SVN_ERR_ASSERT(i == nargs); 3034251881Speter#endif 3035251881Speter 3036251881Speter /* Run diff3, output the merged text into the scratch file. */ 3037251881Speter SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args, 3038251881Speter exitcode, NULL, 3039251881Speter TRUE, /* keep environment */ 3040251881Speter NULL, merged, NULL, 3041251881Speter pool)); 3042251881Speter 3043251881Speter /* According to the diff3 docs, a '0' means the merge was clean, and 3044251881Speter '1' means conflict markers were found. Anything else is real 3045251881Speter error. */ 3046251881Speter if ((*exitcode != 0) && (*exitcode != 1)) 3047251881Speter return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 3048251881Speter _("Error running '%s': exitcode was %d, " 3049251881Speter "args were:" 3050251881Speter "\nin directory '%s', basenames:\n%s\n%s\n%s"), 3051251881Speter svn_dirent_local_style(diff3_cmd, pool), 3052251881Speter *exitcode, 3053251881Speter svn_dirent_local_style(dir, pool), 3054251881Speter /* Don't call svn_path_local_style() on 3055251881Speter the basenames. We don't want them to 3056251881Speter be absolute, and we don't need the 3057251881Speter separator conversion. */ 3058251881Speter mine, older, yours); 3059251881Speter 3060251881Speter return SVN_NO_ERROR; 3061251881Speter} 3062251881Speter 3063251881Speter 3064251881Speter/* Canonicalize a string for hashing. Modifies KEY in place. */ 3065251881Speterstatic APR_INLINE char * 3066251881Speterfileext_tolower(char *key) 3067251881Speter{ 3068251881Speter register char *p; 3069251881Speter for (p = key; *p != 0; ++p) 3070251881Speter *p = (char)apr_tolower(*p); 3071251881Speter return key; 3072251881Speter} 3073251881Speter 3074251881Speter 3075251881Spetersvn_error_t * 3076251881Spetersvn_io_parse_mimetypes_file(apr_hash_t **type_map, 3077251881Speter const char *mimetypes_file, 3078251881Speter apr_pool_t *pool) 3079251881Speter{ 3080251881Speter svn_error_t *err = SVN_NO_ERROR; 3081251881Speter apr_hash_t *types = apr_hash_make(pool); 3082251881Speter svn_boolean_t eof = FALSE; 3083251881Speter svn_stringbuf_t *buf; 3084251881Speter apr_pool_t *subpool = svn_pool_create(pool); 3085251881Speter apr_file_t *types_file; 3086251881Speter svn_stream_t *mimetypes_stream; 3087251881Speter 3088251881Speter SVN_ERR(svn_io_file_open(&types_file, mimetypes_file, 3089251881Speter APR_READ, APR_OS_DEFAULT, pool)); 3090251881Speter mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool); 3091251881Speter 3092251881Speter while (1) 3093251881Speter { 3094251881Speter apr_array_header_t *tokens; 3095251881Speter const char *type; 3096251881Speter 3097251881Speter svn_pool_clear(subpool); 3098251881Speter 3099251881Speter /* Read a line. */ 3100251881Speter if ((err = svn_stream_readline(mimetypes_stream, &buf, 3101251881Speter APR_EOL_STR, &eof, subpool))) 3102251881Speter break; 3103251881Speter 3104251881Speter /* Only pay attention to non-empty, non-comment lines. */ 3105251881Speter if (buf->len) 3106251881Speter { 3107251881Speter int i; 3108251881Speter 3109251881Speter if (buf->data[0] == '#') 3110251881Speter continue; 3111251881Speter 3112251881Speter /* Tokenize (into our return pool). */ 3113251881Speter tokens = svn_cstring_split(buf->data, " \t", TRUE, pool); 3114251881Speter if (tokens->nelts < 2) 3115251881Speter continue; 3116251881Speter 3117251881Speter /* The first token in a multi-token line is the media type. 3118251881Speter Subsequent tokens are filename extensions associated with 3119251881Speter that media type. */ 3120251881Speter type = APR_ARRAY_IDX(tokens, 0, const char *); 3121251881Speter for (i = 1; i < tokens->nelts; i++) 3122251881Speter { 3123251881Speter /* We can safely address 'ext' as a non-const string because 3124251881Speter * we know svn_cstring_split() allocated it in 'pool' for us. */ 3125251881Speter char *ext = APR_ARRAY_IDX(tokens, i, char *); 3126251881Speter fileext_tolower(ext); 3127251881Speter svn_hash_sets(types, ext, type); 3128251881Speter } 3129251881Speter } 3130251881Speter if (eof) 3131251881Speter break; 3132251881Speter } 3133251881Speter svn_pool_destroy(subpool); 3134251881Speter 3135251881Speter /* If there was an error above, close the file (ignoring any error 3136251881Speter from *that*) and return the originally error. */ 3137251881Speter if (err) 3138251881Speter { 3139251881Speter svn_error_clear(svn_stream_close(mimetypes_stream)); 3140251881Speter return err; 3141251881Speter } 3142251881Speter 3143251881Speter /* Close the stream (which closes the underlying file, too). */ 3144251881Speter SVN_ERR(svn_stream_close(mimetypes_stream)); 3145251881Speter 3146251881Speter *type_map = types; 3147251881Speter return SVN_NO_ERROR; 3148251881Speter} 3149251881Speter 3150251881Speter 3151251881Spetersvn_error_t * 3152251881Spetersvn_io_detect_mimetype2(const char **mimetype, 3153251881Speter const char *file, 3154251881Speter apr_hash_t *mimetype_map, 3155251881Speter apr_pool_t *pool) 3156251881Speter{ 3157251881Speter static const char * const generic_binary = "application/octet-stream"; 3158251881Speter 3159251881Speter svn_node_kind_t kind; 3160251881Speter apr_file_t *fh; 3161251881Speter svn_error_t *err; 3162251881Speter unsigned char block[1024]; 3163251881Speter apr_size_t amt_read = sizeof(block); 3164251881Speter 3165251881Speter /* Default return value is NULL. */ 3166251881Speter *mimetype = NULL; 3167251881Speter 3168251881Speter /* If there is a mimetype_map provided, we'll first try to look up 3169251881Speter our file's extension in the map. Failing that, we'll run the 3170251881Speter heuristic. */ 3171251881Speter if (mimetype_map) 3172251881Speter { 3173251881Speter const char *type_from_map; 3174251881Speter char *path_ext; /* Can point to physical const memory but only when 3175251881Speter svn_path_splitext sets it to "". */ 3176251881Speter svn_path_splitext(NULL, (const char **)&path_ext, file, pool); 3177251881Speter fileext_tolower(path_ext); 3178251881Speter if ((type_from_map = svn_hash_gets(mimetype_map, path_ext))) 3179251881Speter { 3180251881Speter *mimetype = type_from_map; 3181251881Speter return SVN_NO_ERROR; 3182251881Speter } 3183251881Speter } 3184251881Speter 3185251881Speter /* See if this file even exists, and make sure it really is a file. */ 3186251881Speter SVN_ERR(svn_io_check_path(file, &kind, pool)); 3187251881Speter if (kind != svn_node_file) 3188251881Speter return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, 3189251881Speter _("Can't detect MIME type of non-file '%s'"), 3190251881Speter svn_dirent_local_style(file, pool)); 3191251881Speter 3192251881Speter SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool)); 3193251881Speter 3194251881Speter /* Read a block of data from FILE. */ 3195251881Speter err = svn_io_file_read(fh, block, &amt_read, pool); 3196251881Speter if (err && ! APR_STATUS_IS_EOF(err->apr_err)) 3197251881Speter return err; 3198251881Speter svn_error_clear(err); 3199251881Speter 3200251881Speter /* Now close the file. No use keeping it open any more. */ 3201251881Speter SVN_ERR(svn_io_file_close(fh, pool)); 3202251881Speter 3203251881Speter if (svn_io_is_binary_data(block, amt_read)) 3204251881Speter *mimetype = generic_binary; 3205251881Speter 3206251881Speter return SVN_NO_ERROR; 3207251881Speter} 3208251881Speter 3209251881Speter 3210251881Spetersvn_boolean_t 3211251881Spetersvn_io_is_binary_data(const void *data, apr_size_t len) 3212251881Speter{ 3213251881Speter const unsigned char *buf = data; 3214251881Speter 3215251881Speter if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) 3216251881Speter { 3217251881Speter /* This is an empty UTF-8 file which only contains the UTF-8 BOM. 3218251881Speter * Treat it as plain text. */ 3219251881Speter return FALSE; 3220251881Speter } 3221251881Speter 3222251881Speter /* Right now, this function is going to be really stupid. It's 3223251881Speter going to examine the block of data, and make sure that 15% 3224251881Speter of the bytes are such that their value is in the ranges 0x07-0x0D 3225251881Speter or 0x20-0x7F, and that none of those bytes is 0x00. If those 3226251881Speter criteria are not met, we're calling it binary. 3227251881Speter 3228251881Speter NOTE: Originally, I intended to target 85% of the bytes being in 3229251881Speter the specified ranges, but I flubbed the condition. At any rate, 3230251881Speter folks aren't complaining, so I'm not sure that it's worth 3231251881Speter adjusting this retroactively now. --cmpilato */ 3232251881Speter if (len > 0) 3233251881Speter { 3234251881Speter apr_size_t i; 3235251881Speter apr_size_t binary_count = 0; 3236251881Speter 3237251881Speter /* Run through the data we've read, counting the 'binary-ish' 3238251881Speter bytes. HINT: If we see a 0x00 byte, we'll set our count to its 3239251881Speter max and stop reading the file. */ 3240251881Speter for (i = 0; i < len; i++) 3241251881Speter { 3242251881Speter if (buf[i] == 0) 3243251881Speter { 3244251881Speter binary_count = len; 3245251881Speter break; 3246251881Speter } 3247251881Speter if ((buf[i] < 0x07) 3248251881Speter || ((buf[i] > 0x0D) && (buf[i] < 0x20)) 3249251881Speter || (buf[i] > 0x7F)) 3250251881Speter { 3251251881Speter binary_count++; 3252251881Speter } 3253251881Speter } 3254251881Speter 3255251881Speter return (((binary_count * 1000) / len) > 850); 3256251881Speter } 3257251881Speter 3258251881Speter return FALSE; 3259251881Speter} 3260251881Speter 3261251881Speter 3262251881Spetersvn_error_t * 3263251881Spetersvn_io_detect_mimetype(const char **mimetype, 3264251881Speter const char *file, 3265251881Speter apr_pool_t *pool) 3266251881Speter{ 3267251881Speter return svn_io_detect_mimetype2(mimetype, file, NULL, pool); 3268251881Speter} 3269251881Speter 3270251881Speter 3271251881Spetersvn_error_t * 3272251881Spetersvn_io_file_open(apr_file_t **new_file, const char *fname, 3273251881Speter apr_int32_t flag, apr_fileperms_t perm, 3274251881Speter apr_pool_t *pool) 3275251881Speter{ 3276251881Speter const char *fname_apr; 3277251881Speter apr_status_t status; 3278251881Speter 3279251881Speter SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); 3280251881Speter status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE, 3281251881Speter pool); 3282251881Speter 3283251881Speter if (status) 3284251881Speter return svn_error_wrap_apr(status, _("Can't open file '%s'"), 3285251881Speter svn_dirent_local_style(fname, pool)); 3286251881Speter else 3287251881Speter return SVN_NO_ERROR; 3288251881Speter} 3289251881Speter 3290251881Speter 3291251881Speterstatic APR_INLINE svn_error_t * 3292251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status, 3293251881Speter const char *msg, const char *msg_no_name, 3294251881Speter apr_pool_t *pool) 3295251881Speter{ 3296251881Speter const char *name; 3297251881Speter svn_error_t *err; 3298251881Speter 3299251881Speter if (! status) 3300251881Speter return SVN_NO_ERROR; 3301251881Speter 3302251881Speter err = svn_io_file_name_get(&name, file, pool); 3303251881Speter if (err) 3304251881Speter name = NULL; 3305251881Speter svn_error_clear(err); 3306251881Speter 3307251881Speter /* ### Issue #3014: Return a specific error for broken pipes, 3308251881Speter * ### with a single element in the error chain. */ 3309251881Speter if (APR_STATUS_IS_EPIPE(status)) 3310251881Speter return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); 3311251881Speter 3312251881Speter if (name) 3313251881Speter return svn_error_wrap_apr(status, _(msg), 3314251881Speter try_utf8_from_internal_style(name, pool)); 3315251881Speter else 3316251881Speter return svn_error_wrap_apr(status, "%s", _(msg_no_name)); 3317251881Speter} 3318251881Speter 3319251881Speter 3320251881Spetersvn_error_t * 3321251881Spetersvn_io_file_close(apr_file_t *file, apr_pool_t *pool) 3322251881Speter{ 3323251881Speter return do_io_file_wrapper_cleanup(file, apr_file_close(file), 3324251881Speter N_("Can't close file '%s'"), 3325251881Speter N_("Can't close stream"), 3326251881Speter pool); 3327251881Speter} 3328251881Speter 3329251881Speter 3330251881Spetersvn_error_t * 3331251881Spetersvn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool) 3332251881Speter{ 3333251881Speter return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file), 3334251881Speter N_("Can't read file '%s'"), 3335251881Speter N_("Can't read stream"), 3336251881Speter pool); 3337251881Speter} 3338251881Speter 3339251881Speter 3340251881Spetersvn_error_t * 3341251881Spetersvn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool) 3342251881Speter{ 3343251881Speter return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file), 3344251881Speter N_("Can't write file '%s'"), 3345251881Speter N_("Can't write stream"), 3346251881Speter pool); 3347251881Speter} 3348251881Speter 3349251881Speter 3350251881Spetersvn_error_t * 3351251881Spetersvn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted, 3352251881Speter apr_file_t *file, apr_pool_t *pool) 3353251881Speter{ 3354251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 3355251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 3356251881Speter 3357251881Speter return do_io_file_wrapper_cleanup( 3358251881Speter file, apr_file_info_get(finfo, wanted, file), 3359251881Speter N_("Can't get attribute information from file '%s'"), 3360251881Speter N_("Can't get attribute information from stream"), 3361251881Speter pool); 3362251881Speter} 3363251881Speter 3364251881Speter 3365251881Spetersvn_error_t * 3366251881Spetersvn_io_file_read(apr_file_t *file, void *buf, 3367251881Speter apr_size_t *nbytes, apr_pool_t *pool) 3368251881Speter{ 3369251881Speter return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes), 3370251881Speter N_("Can't read file '%s'"), 3371251881Speter N_("Can't read stream"), 3372251881Speter pool); 3373251881Speter} 3374251881Speter 3375251881Speter 3376251881Spetersvn_error_t * 3377251881Spetersvn_io_file_read_full2(apr_file_t *file, void *buf, 3378251881Speter apr_size_t nbytes, apr_size_t *bytes_read, 3379251881Speter svn_boolean_t *hit_eof, 3380251881Speter apr_pool_t *pool) 3381251881Speter{ 3382251881Speter apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read); 3383251881Speter if (hit_eof) 3384251881Speter { 3385251881Speter if (APR_STATUS_IS_EOF(status)) 3386251881Speter { 3387251881Speter *hit_eof = TRUE; 3388251881Speter return SVN_NO_ERROR; 3389251881Speter } 3390251881Speter else 3391251881Speter *hit_eof = FALSE; 3392251881Speter } 3393251881Speter 3394251881Speter return do_io_file_wrapper_cleanup(file, status, 3395251881Speter N_("Can't read file '%s'"), 3396251881Speter N_("Can't read stream"), 3397251881Speter pool); 3398251881Speter} 3399251881Speter 3400251881Speter 3401251881Spetersvn_error_t * 3402251881Spetersvn_io_file_seek(apr_file_t *file, apr_seek_where_t where, 3403251881Speter apr_off_t *offset, apr_pool_t *pool) 3404251881Speter{ 3405251881Speter return do_io_file_wrapper_cleanup( 3406251881Speter file, apr_file_seek(file, where, offset), 3407251881Speter N_("Can't set position pointer in file '%s'"), 3408251881Speter N_("Can't set position pointer in stream"), 3409251881Speter pool); 3410251881Speter} 3411251881Speter 3412251881Speter 3413251881Spetersvn_error_t * 3414251881Spetersvn_io_file_write(apr_file_t *file, const void *buf, 3415251881Speter apr_size_t *nbytes, apr_pool_t *pool) 3416251881Speter{ 3417251881Speter return svn_error_trace(do_io_file_wrapper_cleanup( 3418251881Speter file, apr_file_write(file, buf, nbytes), 3419251881Speter N_("Can't write to file '%s'"), 3420251881Speter N_("Can't write to stream"), 3421251881Speter pool)); 3422251881Speter} 3423251881Speter 3424251881Speter 3425251881Spetersvn_error_t * 3426251881Spetersvn_io_file_write_full(apr_file_t *file, const void *buf, 3427251881Speter apr_size_t nbytes, apr_size_t *bytes_written, 3428251881Speter apr_pool_t *pool) 3429251881Speter{ 3430251881Speter /* We cannot simply call apr_file_write_full on Win32 as it may fail 3431251881Speter for larger values of NBYTES. In that case, we have to emulate the 3432251881Speter "_full" part here. Thus, always call apr_file_write directly on 3433251881Speter Win32 as this minimizes overhead for small data buffers. */ 3434251881Speter#ifdef WIN32 3435251881Speter#define MAXBUFSIZE 30*1024 3436251881Speter apr_size_t bw = nbytes; 3437251881Speter apr_size_t to_write = nbytes; 3438251881Speter 3439251881Speter /* try a simple "write everything at once" first */ 3440251881Speter apr_status_t rv = apr_file_write(file, buf, &bw); 3441251881Speter buf = (char *)buf + bw; 3442251881Speter to_write -= bw; 3443251881Speter 3444251881Speter /* if the OS cannot handle that, use smaller chunks */ 3445251881Speter if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY) 3446251881Speter && nbytes > MAXBUFSIZE) 3447251881Speter { 3448251881Speter do { 3449251881Speter bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write; 3450251881Speter rv = apr_file_write(file, buf, &bw); 3451251881Speter buf = (char *)buf + bw; 3452251881Speter to_write -= bw; 3453251881Speter } while (rv == APR_SUCCESS && to_write > 0); 3454251881Speter } 3455251881Speter 3456251881Speter /* bytes_written may actually be NULL */ 3457251881Speter if (bytes_written) 3458251881Speter *bytes_written = nbytes - to_write; 3459251881Speter#undef MAXBUFSIZE 3460251881Speter#else 3461251881Speter apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written); 3462251881Speter#endif 3463251881Speter 3464251881Speter return svn_error_trace(do_io_file_wrapper_cleanup( 3465251881Speter file, rv, 3466251881Speter N_("Can't write to file '%s'"), 3467251881Speter N_("Can't write to stream"), 3468251881Speter pool)); 3469251881Speter} 3470251881Speter 3471251881Speter 3472251881Spetersvn_error_t * 3473251881Spetersvn_io_write_unique(const char **tmp_path, 3474251881Speter const char *dirpath, 3475251881Speter const void *buf, 3476251881Speter apr_size_t nbytes, 3477251881Speter svn_io_file_del_t delete_when, 3478251881Speter apr_pool_t *pool) 3479251881Speter{ 3480251881Speter apr_file_t *new_file; 3481251881Speter svn_error_t *err; 3482251881Speter 3483251881Speter SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath, 3484251881Speter delete_when, pool, pool)); 3485251881Speter 3486251881Speter err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool); 3487251881Speter 3488251881Speter if (!err) 3489251881Speter err = svn_io_file_flush_to_disk(new_file, pool); 3490251881Speter 3491251881Speter return svn_error_trace( 3492251881Speter svn_error_compose_create(err, 3493251881Speter svn_io_file_close(new_file, pool))); 3494251881Speter} 3495251881Speter 3496251881Speter 3497251881Spetersvn_error_t * 3498251881Spetersvn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool) 3499251881Speter{ 3500251881Speter /* This is a work-around. APR would flush the write buffer 3501251881Speter _after_ truncating the file causing now invalid buffered 3502251881Speter data to be written behind OFFSET. */ 3503251881Speter SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file), 3504251881Speter N_("Can't flush file '%s'"), 3505251881Speter N_("Can't flush stream"), 3506251881Speter pool)); 3507251881Speter 3508251881Speter return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset), 3509251881Speter N_("Can't truncate file '%s'"), 3510251881Speter N_("Can't truncate stream"), 3511251881Speter pool); 3512251881Speter} 3513251881Speter 3514251881Speter 3515251881Spetersvn_error_t * 3516251881Spetersvn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit, 3517251881Speter apr_pool_t *pool) 3518251881Speter{ 3519251881Speter /* variables */ 3520251881Speter apr_size_t total_read = 0; 3521251881Speter svn_boolean_t eof = FALSE; 3522251881Speter const char *name; 3523251881Speter svn_error_t *err; 3524251881Speter apr_size_t buf_size = *limit; 3525251881Speter 3526251881Speter while (buf_size > 0) 3527251881Speter { 3528251881Speter /* read a fair chunk of data at once. But don't get too ambitious 3529251881Speter * as that would result in too much waste. Also make sure we can 3530251881Speter * put a NUL after the last byte read. 3531251881Speter */ 3532251881Speter apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128; 3533251881Speter apr_size_t bytes_read = 0; 3534251881Speter char *eol; 3535251881Speter 3536253734Speter if (to_read == 0) 3537253734Speter break; 3538253734Speter 3539251881Speter /* read data block (or just a part of it) */ 3540251881Speter SVN_ERR(svn_io_file_read_full2(file, buf, to_read, 3541251881Speter &bytes_read, &eof, pool)); 3542251881Speter 3543251881Speter /* look or a newline char */ 3544251881Speter buf[bytes_read] = 0; 3545251881Speter eol = strchr(buf, '\n'); 3546251881Speter if (eol) 3547251881Speter { 3548251881Speter apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read; 3549251881Speter 3550251881Speter *eol = 0; 3551251881Speter *limit = total_read + (eol - buf); 3552251881Speter 3553251881Speter /* correct the file pointer: 3554251881Speter * appear as though we just had read the newline char 3555251881Speter */ 3556251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool)); 3557251881Speter 3558251881Speter return SVN_NO_ERROR; 3559251881Speter } 3560251881Speter else if (eof) 3561251881Speter { 3562251881Speter /* no EOL found but we hit the end of the file. 3563251881Speter * Generate a nice EOF error object and return it. 3564251881Speter */ 3565251881Speter char dummy; 3566251881Speter SVN_ERR(svn_io_file_getc(&dummy, file, pool)); 3567251881Speter } 3568251881Speter 3569251881Speter /* next data chunk */ 3570251881Speter buf_size -= bytes_read; 3571251881Speter buf += bytes_read; 3572251881Speter total_read += bytes_read; 3573251881Speter } 3574251881Speter 3575251881Speter /* buffer limit has been exceeded without finding the EOL */ 3576251881Speter err = svn_io_file_name_get(&name, file, pool); 3577251881Speter if (err) 3578251881Speter name = NULL; 3579251881Speter svn_error_clear(err); 3580251881Speter 3581251881Speter if (name) 3582251881Speter return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, 3583251881Speter _("Can't read length line in file '%s'"), 3584251881Speter svn_dirent_local_style(name, pool)); 3585251881Speter else 3586251881Speter return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, 3587251881Speter _("Can't read length line in stream")); 3588251881Speter} 3589251881Speter 3590251881Speter 3591251881Spetersvn_error_t * 3592251881Spetersvn_io_stat(apr_finfo_t *finfo, const char *fname, 3593251881Speter apr_int32_t wanted, apr_pool_t *pool) 3594251881Speter{ 3595251881Speter apr_status_t status; 3596251881Speter const char *fname_apr; 3597251881Speter 3598251881Speter /* APR doesn't like "" directories */ 3599251881Speter if (fname[0] == '\0') 3600251881Speter fname = "."; 3601251881Speter 3602251881Speter SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); 3603251881Speter 3604251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 3605251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 3606251881Speter 3607251881Speter status = apr_stat(finfo, fname_apr, wanted, pool); 3608251881Speter if (status) 3609251881Speter return svn_error_wrap_apr(status, _("Can't stat '%s'"), 3610251881Speter svn_dirent_local_style(fname, pool)); 3611251881Speter 3612251881Speter return SVN_NO_ERROR; 3613251881Speter} 3614251881Speter 3615251881Speter 3616251881Spetersvn_error_t * 3617251881Spetersvn_io_file_rename(const char *from_path, const char *to_path, 3618251881Speter apr_pool_t *pool) 3619251881Speter{ 3620251881Speter apr_status_t status = APR_SUCCESS; 3621251881Speter const char *from_path_apr, *to_path_apr; 3622251881Speter 3623251881Speter SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool)); 3624251881Speter SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool)); 3625251881Speter 3626251881Speter status = apr_file_rename(from_path_apr, to_path_apr, pool); 3627251881Speter 3628251881Speter#if defined(WIN32) || defined(__OS2__) 3629251881Speter /* If the target file is read only NTFS reports EACCESS and 3630251881Speter FAT/FAT32 reports EEXIST */ 3631251881Speter if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status)) 3632251881Speter { 3633251881Speter /* Set the destination file writable because Windows will not 3634251881Speter allow us to rename when to_path is read-only, but will 3635251881Speter allow renaming when from_path is read only. */ 3636251881Speter SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool)); 3637251881Speter 3638251881Speter status = apr_file_rename(from_path_apr, to_path_apr, pool); 3639251881Speter } 3640251881Speter WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool)); 3641251881Speter#endif /* WIN32 || __OS2__ */ 3642251881Speter 3643251881Speter if (status) 3644251881Speter return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"), 3645251881Speter svn_dirent_local_style(from_path, pool), 3646251881Speter svn_dirent_local_style(to_path, pool)); 3647251881Speter 3648251881Speter return SVN_NO_ERROR; 3649251881Speter} 3650251881Speter 3651251881Speter 3652251881Spetersvn_error_t * 3653251881Spetersvn_io_file_move(const char *from_path, const char *to_path, 3654251881Speter apr_pool_t *pool) 3655251881Speter{ 3656251881Speter svn_error_t *err = svn_io_file_rename(from_path, to_path, pool); 3657251881Speter 3658251881Speter if (err && APR_STATUS_IS_EXDEV(err->apr_err)) 3659251881Speter { 3660251881Speter const char *tmp_to_path; 3661251881Speter 3662251881Speter svn_error_clear(err); 3663251881Speter 3664251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path, 3665251881Speter svn_dirent_dirname(to_path, pool), 3666251881Speter svn_io_file_del_none, 3667251881Speter pool, pool)); 3668251881Speter 3669251881Speter err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool); 3670251881Speter if (err) 3671251881Speter goto failed_tmp; 3672251881Speter 3673251881Speter err = svn_io_file_rename(tmp_to_path, to_path, pool); 3674251881Speter if (err) 3675251881Speter goto failed_tmp; 3676251881Speter 3677251881Speter err = svn_io_remove_file2(from_path, FALSE, pool); 3678251881Speter if (! err) 3679251881Speter return SVN_NO_ERROR; 3680251881Speter 3681251881Speter svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool)); 3682251881Speter 3683251881Speter return err; 3684251881Speter 3685251881Speter failed_tmp: 3686251881Speter svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool)); 3687251881Speter } 3688251881Speter 3689251881Speter return err; 3690251881Speter} 3691251881Speter 3692251881Speter/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden. 3693251881Speter HIDDEN determines if the hidden attribute 3694251881Speter should be set on the newly created directory. */ 3695251881Speterstatic svn_error_t * 3696251881Speterdir_make(const char *path, apr_fileperms_t perm, 3697251881Speter svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool) 3698251881Speter{ 3699251881Speter apr_status_t status; 3700251881Speter const char *path_apr; 3701251881Speter 3702251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 3703251881Speter 3704251881Speter /* APR doesn't like "" directories */ 3705251881Speter if (path_apr[0] == '\0') 3706251881Speter path_apr = "."; 3707251881Speter 3708251881Speter#if (APR_OS_DEFAULT & APR_WSTICKY) 3709251881Speter /* The APR shipped with httpd 2.0.50 contains a bug where 3710251881Speter APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits. 3711251881Speter There is a special case for file creation, but not directory 3712251881Speter creation, so directories wind up getting created with the sticky 3713251881Speter bit set. (There is no such thing as a setuid directory, and the 3714251881Speter setgid bit is apparently ignored at mkdir() time.) If we detect 3715251881Speter this problem, work around it by unsetting those bits if we are 3716251881Speter passed APR_OS_DEFAULT. */ 3717251881Speter if (perm == APR_OS_DEFAULT) 3718251881Speter perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY); 3719251881Speter#endif 3720251881Speter 3721251881Speter status = apr_dir_make(path_apr, perm, pool); 3722251881Speter WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool)); 3723251881Speter 3724251881Speter if (status) 3725251881Speter return svn_error_wrap_apr(status, _("Can't create directory '%s'"), 3726251881Speter svn_dirent_local_style(path, pool)); 3727251881Speter 3728251881Speter#ifdef APR_FILE_ATTR_HIDDEN 3729251881Speter if (hidden) 3730251881Speter { 3731251881Speter#ifndef WIN32 3732251881Speter status = apr_file_attrs_set(path_apr, 3733251881Speter APR_FILE_ATTR_HIDDEN, 3734251881Speter APR_FILE_ATTR_HIDDEN, 3735251881Speter pool); 3736251881Speter#else 3737251881Speter /* on Windows, use our wrapper so we can also set the 3738251881Speter FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */ 3739251881Speter status = io_win_file_attrs_set(path_apr, 3740251881Speter FILE_ATTRIBUTE_HIDDEN | 3741251881Speter FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 3742251881Speter FILE_ATTRIBUTE_HIDDEN | 3743251881Speter FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 3744251881Speter pool); 3745251881Speter 3746251881Speter#endif 3747251881Speter if (status) 3748251881Speter return svn_error_wrap_apr(status, _("Can't hide directory '%s'"), 3749251881Speter svn_dirent_local_style(path, pool)); 3750251881Speter } 3751251881Speter#endif 3752251881Speter 3753251881Speter/* Windows does not implement sgid. Skip here because retrieving 3754251881Speter the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented 3755251881Speter to be 'incredibly expensive'. */ 3756251881Speter#ifndef WIN32 3757251881Speter if (sgid) 3758251881Speter { 3759251881Speter apr_finfo_t finfo; 3760251881Speter 3761251881Speter /* Per our contract, don't do error-checking. Some filesystems 3762251881Speter * don't support the sgid bit, and that's okay. */ 3763251881Speter status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool); 3764251881Speter 3765251881Speter if (!status) 3766251881Speter apr_file_perms_set(path_apr, finfo.protection | APR_GSETID); 3767251881Speter } 3768251881Speter#endif 3769251881Speter 3770251881Speter return SVN_NO_ERROR; 3771251881Speter} 3772251881Speter 3773251881Spetersvn_error_t * 3774251881Spetersvn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool) 3775251881Speter{ 3776251881Speter return dir_make(path, perm, FALSE, FALSE, pool); 3777251881Speter} 3778251881Speter 3779251881Spetersvn_error_t * 3780251881Spetersvn_io_dir_make_hidden(const char *path, apr_fileperms_t perm, 3781251881Speter apr_pool_t *pool) 3782251881Speter{ 3783251881Speter return dir_make(path, perm, TRUE, FALSE, pool); 3784251881Speter} 3785251881Speter 3786251881Spetersvn_error_t * 3787251881Spetersvn_io_dir_make_sgid(const char *path, apr_fileperms_t perm, 3788251881Speter apr_pool_t *pool) 3789251881Speter{ 3790251881Speter return dir_make(path, perm, FALSE, TRUE, pool); 3791251881Speter} 3792251881Speter 3793251881Speter 3794251881Spetersvn_error_t * 3795251881Spetersvn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool) 3796251881Speter{ 3797251881Speter apr_status_t status; 3798251881Speter const char *dirname_apr; 3799251881Speter 3800251881Speter /* APR doesn't like "" directories */ 3801251881Speter if (dirname[0] == '\0') 3802251881Speter dirname = "."; 3803251881Speter 3804251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 3805251881Speter 3806251881Speter status = apr_dir_open(new_dir, dirname_apr, pool); 3807251881Speter if (status) 3808251881Speter return svn_error_wrap_apr(status, _("Can't open directory '%s'"), 3809251881Speter svn_dirent_local_style(dirname, pool)); 3810251881Speter 3811251881Speter return SVN_NO_ERROR; 3812251881Speter} 3813251881Speter 3814251881Spetersvn_error_t * 3815251881Spetersvn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool) 3816251881Speter{ 3817251881Speter apr_status_t status; 3818251881Speter const char *dirname_apr; 3819251881Speter 3820251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 3821251881Speter 3822251881Speter status = apr_dir_remove(dirname_apr, pool); 3823251881Speter 3824251881Speter#ifdef WIN32 3825251881Speter { 3826251881Speter svn_boolean_t retry = TRUE; 3827251881Speter 3828251881Speter if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY) 3829251881Speter { 3830251881Speter apr_status_t empty_status = dir_is_empty(dirname_apr, pool); 3831251881Speter 3832251881Speter if (APR_STATUS_IS_ENOTEMPTY(empty_status)) 3833251881Speter retry = FALSE; 3834251881Speter } 3835251881Speter 3836251881Speter if (retry) 3837251881Speter { 3838251881Speter WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool)); 3839251881Speter } 3840251881Speter } 3841251881Speter#endif 3842251881Speter if (status) 3843251881Speter return svn_error_wrap_apr(status, _("Can't remove directory '%s'"), 3844251881Speter svn_dirent_local_style(dirname, pool)); 3845251881Speter 3846251881Speter return SVN_NO_ERROR; 3847251881Speter} 3848251881Speter 3849251881Speter 3850251881Spetersvn_error_t * 3851251881Spetersvn_io_dir_read(apr_finfo_t *finfo, 3852251881Speter apr_int32_t wanted, 3853251881Speter apr_dir_t *thedir, 3854251881Speter apr_pool_t *pool) 3855251881Speter{ 3856251881Speter apr_status_t status; 3857251881Speter 3858251881Speter status = apr_dir_read(finfo, wanted, thedir); 3859251881Speter 3860251881Speter if (status) 3861251881Speter return svn_error_wrap_apr(status, _("Can't read directory")); 3862251881Speter 3863251881Speter /* It would be nice to use entry_name_to_utf8() below, but can we 3864251881Speter get the dir's path out of an apr_dir_t? I don't see a reliable 3865251881Speter way to do it. */ 3866251881Speter 3867251881Speter if (finfo->fname) 3868251881Speter SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool)); 3869251881Speter 3870251881Speter if (finfo->name) 3871251881Speter SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool)); 3872251881Speter 3873251881Speter return SVN_NO_ERROR; 3874251881Speter} 3875251881Speter 3876251881Spetersvn_error_t * 3877251881Spetersvn_io_dir_close(apr_dir_t *thedir) 3878251881Speter{ 3879251881Speter apr_status_t apr_err = apr_dir_close(thedir); 3880251881Speter if (apr_err) 3881251881Speter return svn_error_wrap_apr(apr_err, _("Error closing directory")); 3882251881Speter 3883251881Speter return SVN_NO_ERROR; 3884251881Speter} 3885251881Speter 3886251881Spetersvn_error_t * 3887251881Spetersvn_io_dir_walk2(const char *dirname, 3888251881Speter apr_int32_t wanted, 3889251881Speter svn_io_walk_func_t walk_func, 3890251881Speter void *walk_baton, 3891251881Speter apr_pool_t *pool) 3892251881Speter{ 3893251881Speter apr_status_t apr_err; 3894251881Speter apr_dir_t *handle; 3895251881Speter apr_pool_t *subpool; 3896251881Speter const char *dirname_apr; 3897251881Speter apr_finfo_t finfo; 3898251881Speter 3899251881Speter wanted |= APR_FINFO_TYPE | APR_FINFO_NAME; 3900251881Speter 3901251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 3902251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 3903251881Speter 3904251881Speter /* The documentation for apr_dir_read used to state that "." and ".." 3905251881Speter will be returned as the first two files, but it doesn't 3906251881Speter work that way in practice, in particular ext3 on Linux-2.6 doesn't 3907251881Speter follow the rules. For details see 3908251881Speter http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666 3909251881Speter 3910251881Speter If APR ever does implement "dot-first" then it would be possible to 3911251881Speter remove the svn_io_stat and walk_func calls and use the walk_func 3912251881Speter inside the loop. 3913251881Speter 3914251881Speter Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is 3915251881Speter documented to provide it, so we have to do a bit extra. */ 3916251881Speter SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool)); 3917251881Speter SVN_ERR(cstring_from_utf8(&finfo.name, 3918251881Speter svn_dirent_basename(dirname, pool), 3919251881Speter pool)); 3920251881Speter finfo.valid |= APR_FINFO_NAME; 3921251881Speter SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool)); 3922251881Speter 3923251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 3924251881Speter 3925251881Speter /* APR doesn't like "" directories */ 3926251881Speter if (dirname_apr[0] == '\0') 3927251881Speter dirname_apr = "."; 3928251881Speter 3929251881Speter apr_err = apr_dir_open(&handle, dirname_apr, pool); 3930251881Speter if (apr_err) 3931251881Speter return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"), 3932251881Speter svn_dirent_local_style(dirname, pool)); 3933251881Speter 3934251881Speter /* iteration subpool */ 3935251881Speter subpool = svn_pool_create(pool); 3936251881Speter 3937251881Speter while (1) 3938251881Speter { 3939251881Speter const char *name_utf8; 3940251881Speter const char *full_path; 3941251881Speter 3942251881Speter svn_pool_clear(subpool); 3943251881Speter 3944251881Speter apr_err = apr_dir_read(&finfo, wanted, handle); 3945251881Speter if (APR_STATUS_IS_ENOENT(apr_err)) 3946251881Speter break; 3947251881Speter else if (apr_err) 3948251881Speter { 3949251881Speter return svn_error_wrap_apr(apr_err, 3950251881Speter _("Can't read directory entry in '%s'"), 3951251881Speter svn_dirent_local_style(dirname, pool)); 3952251881Speter } 3953251881Speter 3954251881Speter if (finfo.filetype == APR_DIR) 3955251881Speter { 3956251881Speter if (finfo.name[0] == '.' 3957251881Speter && (finfo.name[1] == '\0' 3958251881Speter || (finfo.name[1] == '.' && finfo.name[2] == '\0'))) 3959251881Speter /* skip "." and ".." */ 3960251881Speter continue; 3961251881Speter 3962251881Speter /* some other directory. recurse. it will be passed to the 3963251881Speter callback inside the recursion. */ 3964251881Speter SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname, 3965251881Speter subpool)); 3966251881Speter full_path = svn_dirent_join(dirname, name_utf8, subpool); 3967251881Speter SVN_ERR(svn_io_dir_walk2(full_path, 3968251881Speter wanted, 3969251881Speter walk_func, 3970251881Speter walk_baton, 3971251881Speter subpool)); 3972251881Speter } 3973251881Speter else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) 3974251881Speter { 3975251881Speter /* some other directory. pass it to the callback. */ 3976251881Speter SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname, 3977251881Speter subpool)); 3978251881Speter full_path = svn_dirent_join(dirname, name_utf8, subpool); 3979251881Speter SVN_ERR((*walk_func)(walk_baton, 3980251881Speter full_path, 3981251881Speter &finfo, 3982251881Speter subpool)); 3983251881Speter } 3984251881Speter /* else: 3985251881Speter Some other type of file; skip it for now. We've reserved the 3986251881Speter right to expand our coverage here in the future, though, 3987251881Speter without revving this API. 3988251881Speter */ 3989251881Speter } 3990251881Speter 3991251881Speter svn_pool_destroy(subpool); 3992251881Speter 3993251881Speter apr_err = apr_dir_close(handle); 3994251881Speter if (apr_err) 3995251881Speter return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"), 3996251881Speter svn_dirent_local_style(dirname, pool)); 3997251881Speter 3998251881Speter return SVN_NO_ERROR; 3999251881Speter} 4000251881Speter 4001251881Speter 4002251881Speter 4003251881Speter/** 4004251881Speter * Determine if a directory is empty or not. 4005251881Speter * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not. 4006251881Speter * @param path The directory. 4007251881Speter * @param pool Used for temporary allocation. 4008251881Speter * @remark If path is not a directory, or some other error occurs, 4009251881Speter * then return the appropriate apr status code. 4010251881Speter * 4011251881Speter * (This function is written in APR style, in anticipation of 4012251881Speter * perhaps someday being moved to APR as 'apr_dir_is_empty'.) 4013251881Speter */ 4014251881Speterstatic apr_status_t 4015251881Speterdir_is_empty(const char *dir, apr_pool_t *pool) 4016251881Speter{ 4017251881Speter apr_status_t apr_err; 4018251881Speter apr_dir_t *dir_handle; 4019251881Speter apr_finfo_t finfo; 4020251881Speter apr_status_t retval = APR_SUCCESS; 4021251881Speter 4022251881Speter /* APR doesn't like "" directories */ 4023251881Speter if (dir[0] == '\0') 4024251881Speter dir = "."; 4025251881Speter 4026251881Speter apr_err = apr_dir_open(&dir_handle, dir, pool); 4027251881Speter if (apr_err != APR_SUCCESS) 4028251881Speter return apr_err; 4029251881Speter 4030251881Speter for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle); 4031251881Speter apr_err == APR_SUCCESS; 4032251881Speter apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle)) 4033251881Speter { 4034251881Speter /* Ignore entries for this dir and its parent, robustly. 4035251881Speter (APR promises that they'll come first, so technically 4036251881Speter this guard could be moved outside the loop. But Ryan Bloom 4037251881Speter says he doesn't believe it, and I believe him. */ 4038251881Speter if (! (finfo.name[0] == '.' 4039251881Speter && (finfo.name[1] == '\0' 4040251881Speter || (finfo.name[1] == '.' && finfo.name[2] == '\0')))) 4041251881Speter { 4042251881Speter retval = APR_ENOTEMPTY; 4043251881Speter break; 4044251881Speter } 4045251881Speter } 4046251881Speter 4047251881Speter /* Make sure we broke out of the loop for the right reason. */ 4048251881Speter if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err)) 4049251881Speter return apr_err; 4050251881Speter 4051251881Speter apr_err = apr_dir_close(dir_handle); 4052251881Speter if (apr_err != APR_SUCCESS) 4053251881Speter return apr_err; 4054251881Speter 4055251881Speter return retval; 4056251881Speter} 4057251881Speter 4058251881Speter 4059251881Spetersvn_error_t * 4060251881Spetersvn_io_dir_empty(svn_boolean_t *is_empty_p, 4061251881Speter const char *path, 4062251881Speter apr_pool_t *pool) 4063251881Speter{ 4064251881Speter apr_status_t status; 4065251881Speter const char *path_apr; 4066251881Speter 4067251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 4068251881Speter 4069251881Speter status = dir_is_empty(path_apr, pool); 4070251881Speter 4071251881Speter if (!status) 4072251881Speter *is_empty_p = TRUE; 4073251881Speter else if (APR_STATUS_IS_ENOTEMPTY(status)) 4074251881Speter *is_empty_p = FALSE; 4075251881Speter else 4076251881Speter return svn_error_wrap_apr(status, _("Can't check directory '%s'"), 4077251881Speter svn_dirent_local_style(path, pool)); 4078251881Speter 4079251881Speter return SVN_NO_ERROR; 4080251881Speter} 4081251881Speter 4082251881Speter 4083251881Speter 4084251881Speter/*** Version/format files ***/ 4085251881Speter 4086251881Spetersvn_error_t * 4087251881Spetersvn_io_write_version_file(const char *path, 4088251881Speter int version, 4089251881Speter apr_pool_t *pool) 4090251881Speter{ 4091251881Speter const char *path_tmp; 4092251881Speter const char *format_contents = apr_psprintf(pool, "%d\n", version); 4093251881Speter 4094251881Speter SVN_ERR_ASSERT(version >= 0); 4095251881Speter 4096251881Speter SVN_ERR(svn_io_write_unique(&path_tmp, 4097251881Speter svn_dirent_dirname(path, pool), 4098251881Speter format_contents, strlen(format_contents), 4099251881Speter svn_io_file_del_none, pool)); 4100251881Speter 4101251881Speter#if defined(WIN32) || defined(__OS2__) 4102251881Speter /* make the destination writable, but only on Windows, because 4103251881Speter Windows does not let us replace read-only files. */ 4104251881Speter SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool)); 4105251881Speter#endif /* WIN32 || __OS2__ */ 4106251881Speter 4107251881Speter /* rename the temp file as the real destination */ 4108251881Speter SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); 4109251881Speter 4110251881Speter /* And finally remove the perms to make it read only */ 4111251881Speter return svn_io_set_file_read_only(path, FALSE, pool); 4112251881Speter} 4113251881Speter 4114251881Speter 4115251881Spetersvn_error_t * 4116251881Spetersvn_io_read_version_file(int *version, 4117251881Speter const char *path, 4118251881Speter apr_pool_t *pool) 4119251881Speter{ 4120251881Speter apr_file_t *format_file; 4121251881Speter char buf[80]; 4122251881Speter apr_size_t len; 4123251881Speter svn_error_t *err; 4124251881Speter 4125251881Speter /* Read a chunk of data from PATH */ 4126251881Speter SVN_ERR(svn_io_file_open(&format_file, path, APR_READ, 4127251881Speter APR_OS_DEFAULT, pool)); 4128251881Speter len = sizeof(buf); 4129251881Speter err = svn_io_file_read(format_file, buf, &len, pool); 4130251881Speter 4131251881Speter /* Close the file. */ 4132251881Speter SVN_ERR(svn_error_compose_create(err, 4133251881Speter svn_io_file_close(format_file, pool))); 4134251881Speter 4135251881Speter /* If there was no data in PATH, return an error. */ 4136251881Speter if (len == 0) 4137251881Speter return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, 4138251881Speter _("Reading '%s'"), 4139251881Speter svn_dirent_local_style(path, pool)); 4140251881Speter 4141251881Speter /* Check that the first line contains only digits. */ 4142251881Speter { 4143251881Speter apr_size_t i; 4144251881Speter 4145251881Speter for (i = 0; i < len; ++i) 4146251881Speter { 4147251881Speter char c = buf[i]; 4148251881Speter 4149251881Speter if (i > 0 && (c == '\r' || c == '\n')) 4150251881Speter { 4151251881Speter buf[i] = '\0'; 4152251881Speter break; 4153251881Speter } 4154251881Speter if (! svn_ctype_isdigit(c)) 4155251881Speter return svn_error_createf 4156251881Speter (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 4157251881Speter _("First line of '%s' contains non-digit"), 4158251881Speter svn_dirent_local_style(path, pool)); 4159251881Speter } 4160251881Speter } 4161251881Speter 4162251881Speter /* Convert to integer. */ 4163251881Speter SVN_ERR(svn_cstring_atoi(version, buf)); 4164251881Speter 4165251881Speter return SVN_NO_ERROR; 4166251881Speter} 4167251881Speter 4168251881Speter 4169251881Speter 4170251881Speter/* Do a byte-for-byte comparison of FILE1 and FILE2. */ 4171251881Speterstatic svn_error_t * 4172251881Spetercontents_identical_p(svn_boolean_t *identical_p, 4173251881Speter const char *file1, 4174251881Speter const char *file2, 4175251881Speter apr_pool_t *pool) 4176251881Speter{ 4177251881Speter svn_error_t *err; 4178251881Speter apr_size_t bytes_read1, bytes_read2; 4179251881Speter char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 4180251881Speter char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 4181251881Speter apr_file_t *file1_h; 4182251881Speter apr_file_t *file2_h; 4183251881Speter svn_boolean_t eof1 = FALSE; 4184251881Speter svn_boolean_t eof2 = FALSE; 4185251881Speter 4186251881Speter SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT, 4187251881Speter pool)); 4188251881Speter 4189251881Speter err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT, 4190251881Speter pool); 4191251881Speter 4192251881Speter if (err) 4193251881Speter return svn_error_trace( 4194251881Speter svn_error_compose_create(err, 4195251881Speter svn_io_file_close(file1_h, pool))); 4196251881Speter 4197251881Speter *identical_p = TRUE; /* assume TRUE, until disproved below */ 4198251881Speter while (!err && !eof1 && !eof2) 4199251881Speter { 4200251881Speter err = svn_io_file_read_full2(file1_h, buf1, 4201251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read1, 4202251881Speter &eof1, pool); 4203251881Speter if (err) 4204251881Speter break; 4205251881Speter 4206251881Speter err = svn_io_file_read_full2(file2_h, buf2, 4207251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read2, 4208251881Speter &eof2, pool); 4209251881Speter if (err) 4210251881Speter break; 4211251881Speter 4212251881Speter if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1)) 4213251881Speter { 4214251881Speter *identical_p = FALSE; 4215251881Speter break; 4216251881Speter } 4217251881Speter } 4218251881Speter 4219251881Speter /* Special case: one file being a prefix of the other and the shorter 4220251881Speter * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */ 4221251881Speter if (!err && (eof1 != eof2)) 4222251881Speter *identical_p = FALSE; 4223251881Speter 4224251881Speter return svn_error_trace( 4225251881Speter svn_error_compose_create( 4226251881Speter err, 4227251881Speter svn_error_compose_create(svn_io_file_close(file1_h, pool), 4228251881Speter svn_io_file_close(file2_h, pool)))); 4229251881Speter} 4230251881Speter 4231251881Speter 4232251881Speter 4233251881Speter/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */ 4234251881Speterstatic svn_error_t * 4235251881Spetercontents_three_identical_p(svn_boolean_t *identical_p12, 4236251881Speter svn_boolean_t *identical_p23, 4237251881Speter svn_boolean_t *identical_p13, 4238251881Speter const char *file1, 4239251881Speter const char *file2, 4240251881Speter const char *file3, 4241251881Speter apr_pool_t *scratch_pool) 4242251881Speter{ 4243251881Speter svn_error_t *err; 4244251881Speter apr_size_t bytes_read1, bytes_read2, bytes_read3; 4245251881Speter char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4246251881Speter char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4247251881Speter char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4248251881Speter apr_file_t *file1_h; 4249251881Speter apr_file_t *file2_h; 4250251881Speter apr_file_t *file3_h; 4251251881Speter svn_boolean_t eof1 = FALSE; 4252251881Speter svn_boolean_t eof2 = FALSE; 4253251881Speter svn_boolean_t eof3 = FALSE; 4254251881Speter svn_boolean_t read_1, read_2, read_3; 4255251881Speter 4256251881Speter SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT, 4257251881Speter scratch_pool)); 4258251881Speter 4259251881Speter err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT, 4260251881Speter scratch_pool); 4261251881Speter 4262251881Speter if (err) 4263251881Speter return svn_error_trace( 4264251881Speter svn_error_compose_create(err, 4265251881Speter svn_io_file_close(file1_h, scratch_pool))); 4266251881Speter 4267251881Speter err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT, 4268251881Speter scratch_pool); 4269251881Speter 4270251881Speter if (err) 4271251881Speter return svn_error_trace( 4272251881Speter svn_error_compose_create( 4273251881Speter err, 4274251881Speter svn_error_compose_create(svn_io_file_close(file1_h, 4275251881Speter scratch_pool), 4276251881Speter svn_io_file_close(file2_h, 4277251881Speter scratch_pool)))); 4278251881Speter 4279251881Speter /* assume TRUE, until disproved below */ 4280251881Speter *identical_p12 = *identical_p23 = *identical_p13 = TRUE; 4281251881Speter /* We need to read as long as no error occurs, and as long as one of the 4282251881Speter * flags could still change due to a read operation */ 4283251881Speter while (!err 4284251881Speter && ((*identical_p12 && !eof1 && !eof2) 4285251881Speter || (*identical_p23 && !eof2 && !eof3) 4286251881Speter || (*identical_p13 && !eof1 && !eof3))) 4287251881Speter { 4288251881Speter read_1 = read_2 = read_3 = FALSE; 4289251881Speter 4290251881Speter /* As long as a file is not at the end yet, and it is still 4291251881Speter * potentially identical to another file, we read the next chunk.*/ 4292251881Speter if (!eof1 && (identical_p12 || identical_p13)) 4293251881Speter { 4294251881Speter err = svn_io_file_read_full2(file1_h, buf1, 4295251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read1, 4296251881Speter &eof1, scratch_pool); 4297251881Speter if (err) 4298251881Speter break; 4299251881Speter read_1 = TRUE; 4300251881Speter } 4301251881Speter 4302251881Speter if (!eof2 && (identical_p12 || identical_p23)) 4303251881Speter { 4304251881Speter err = svn_io_file_read_full2(file2_h, buf2, 4305251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read2, 4306251881Speter &eof2, scratch_pool); 4307251881Speter if (err) 4308251881Speter break; 4309251881Speter read_2 = TRUE; 4310251881Speter } 4311251881Speter 4312251881Speter if (!eof3 && (identical_p13 || identical_p23)) 4313251881Speter { 4314251881Speter err = svn_io_file_read_full2(file3_h, buf3, 4315251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read3, 4316251881Speter &eof3, scratch_pool); 4317251881Speter if (err) 4318251881Speter break; 4319251881Speter read_3 = TRUE; 4320251881Speter } 4321251881Speter 4322251881Speter /* If the files are still marked identical, and at least one of them 4323251881Speter * is not at the end of file, we check whether they differ, and set 4324251881Speter * their flag to false then. */ 4325251881Speter if (*identical_p12 4326251881Speter && (read_1 || read_2) 4327251881Speter && ((eof1 != eof2) 4328251881Speter || (bytes_read1 != bytes_read2) 4329251881Speter || memcmp(buf1, buf2, bytes_read1))) 4330251881Speter { 4331251881Speter *identical_p12 = FALSE; 4332251881Speter } 4333251881Speter 4334251881Speter if (*identical_p23 4335251881Speter && (read_2 || read_3) 4336251881Speter && ((eof2 != eof3) 4337251881Speter || (bytes_read2 != bytes_read3) 4338251881Speter || memcmp(buf2, buf3, bytes_read2))) 4339251881Speter { 4340251881Speter *identical_p23 = FALSE; 4341251881Speter } 4342251881Speter 4343251881Speter if (*identical_p13 4344251881Speter && (read_1 || read_3) 4345251881Speter && ((eof1 != eof3) 4346251881Speter || (bytes_read1 != bytes_read3) 4347251881Speter || memcmp(buf1, buf3, bytes_read3))) 4348251881Speter { 4349251881Speter *identical_p13 = FALSE; 4350251881Speter } 4351251881Speter } 4352251881Speter 4353251881Speter return svn_error_trace( 4354251881Speter svn_error_compose_create( 4355251881Speter err, 4356251881Speter svn_error_compose_create( 4357251881Speter svn_io_file_close(file1_h, scratch_pool), 4358251881Speter svn_error_compose_create( 4359251881Speter svn_io_file_close(file2_h, scratch_pool), 4360251881Speter svn_io_file_close(file3_h, scratch_pool))))); 4361251881Speter} 4362251881Speter 4363251881Speter 4364251881Speter 4365251881Spetersvn_error_t * 4366251881Spetersvn_io_files_contents_same_p(svn_boolean_t *same, 4367251881Speter const char *file1, 4368251881Speter const char *file2, 4369251881Speter apr_pool_t *pool) 4370251881Speter{ 4371251881Speter svn_boolean_t q; 4372251881Speter 4373251881Speter SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool)); 4374251881Speter 4375251881Speter if (q) 4376251881Speter { 4377251881Speter *same = FALSE; 4378251881Speter return SVN_NO_ERROR; 4379251881Speter } 4380251881Speter 4381251881Speter SVN_ERR(contents_identical_p(&q, file1, file2, pool)); 4382251881Speter 4383251881Speter if (q) 4384251881Speter *same = TRUE; 4385251881Speter else 4386251881Speter *same = FALSE; 4387251881Speter 4388251881Speter return SVN_NO_ERROR; 4389251881Speter} 4390251881Speter 4391251881Spetersvn_error_t * 4392251881Spetersvn_io_files_contents_three_same_p(svn_boolean_t *same12, 4393251881Speter svn_boolean_t *same23, 4394251881Speter svn_boolean_t *same13, 4395251881Speter const char *file1, 4396251881Speter const char *file2, 4397251881Speter const char *file3, 4398251881Speter apr_pool_t *scratch_pool) 4399251881Speter{ 4400251881Speter svn_boolean_t diff_size12, diff_size23, diff_size13; 4401251881Speter 4402251881Speter SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12, 4403251881Speter &diff_size23, 4404251881Speter &diff_size13, 4405251881Speter file1, 4406251881Speter file2, 4407251881Speter file3, 4408251881Speter scratch_pool)); 4409251881Speter 4410251881Speter if (diff_size12 && diff_size23 && diff_size13) 4411251881Speter { 4412251881Speter *same12 = *same23 = *same13 = FALSE; 4413251881Speter } 4414251881Speter else if (diff_size12 && diff_size23) 4415251881Speter { 4416251881Speter *same12 = *same23 = FALSE; 4417251881Speter SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool)); 4418251881Speter } 4419251881Speter else if (diff_size23 && diff_size13) 4420251881Speter { 4421251881Speter *same23 = *same13 = FALSE; 4422251881Speter SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool)); 4423251881Speter } 4424251881Speter else if (diff_size12 && diff_size13) 4425251881Speter { 4426251881Speter *same12 = *same13 = FALSE; 4427251881Speter SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool)); 4428251881Speter } 4429251881Speter else 4430251881Speter { 4431251881Speter SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13); 4432251881Speter SVN_ERR(contents_three_identical_p(same12, same23, same13, 4433251881Speter file1, file2, file3, 4434251881Speter scratch_pool)); 4435251881Speter } 4436251881Speter 4437251881Speter return SVN_NO_ERROR; 4438251881Speter} 4439251881Speter 4440251881Speter#ifdef WIN32 4441251881Speter/* Counter value of file_mktemp request (used in a threadsafe way), to make 4442251881Speter sure that a single process normally never generates the same tempname 4443251881Speter twice */ 4444251881Speterstatic volatile apr_uint32_t tempname_counter = 0; 4445251881Speter#endif 4446251881Speter 4447251881Speter/* Creates a new temporary file in DIRECTORY with apr flags FLAGS. 4448251881Speter Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name. 4449251881Speter Perform temporary allocations in SCRATCH_POOL and the result in 4450251881Speter RESULT_POOL. */ 4451251881Speterstatic svn_error_t * 4452251881Spetertemp_file_create(apr_file_t **new_file, 4453251881Speter const char **new_file_name, 4454251881Speter const char *directory, 4455251881Speter apr_int32_t flags, 4456251881Speter apr_pool_t *result_pool, 4457251881Speter apr_pool_t *scratch_pool) 4458251881Speter{ 4459251881Speter#ifndef WIN32 4460251881Speter const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool); 4461251881Speter const char *templ_apr; 4462251881Speter apr_status_t status; 4463251881Speter 4464251881Speter SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool)); 4465251881Speter 4466251881Speter /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the 4467251881Speter data available in POOL and we need a non-const pointer here, 4468251881Speter as apr changes the template to return the new filename. */ 4469251881Speter status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool); 4470251881Speter 4471251881Speter if (status) 4472251881Speter return svn_error_wrap_apr(status, _("Can't create temporary file from " 4473251881Speter "template '%s'"), templ); 4474251881Speter 4475251881Speter /* Translate the returned path back to utf-8 before returning it */ 4476251881Speter return svn_error_trace(svn_path_cstring_to_utf8(new_file_name, 4477251881Speter templ_apr, 4478251881Speter result_pool)); 4479251881Speter#else 4480251881Speter /* The Windows implementation of apr_file_mktemp doesn't handle access 4481251881Speter denied errors correctly. Therefore we implement our own temp file 4482251881Speter creation function here. */ 4483251881Speter 4484251881Speter /* ### Most of this is borrowed from the svn_io_open_uniquely_named(), 4485251881Speter ### the function we used before. But we try to guess a more unique 4486251881Speter ### name before trying if it exists. */ 4487251881Speter 4488251881Speter /* Offset by some time value and a unique request nr to make the number 4489251881Speter +- unique for both this process and on the computer */ 4490251881Speter int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter) 4491251881Speter + GetCurrentProcessId(); 4492251881Speter int i; 4493251881Speter 4494251881Speter /* ### Maybe use an iterpool? */ 4495251881Speter for (i = 0; i <= 99999; i++) 4496251881Speter { 4497251881Speter apr_uint32_t unique_nr; 4498251881Speter const char *unique_name; 4499251881Speter const char *unique_name_apr; 4500251881Speter apr_file_t *try_file; 4501251881Speter apr_status_t apr_err; 4502251881Speter 4503251881Speter /* Generate a number that should be unique for this application and 4504251881Speter usually for the entire computer to reduce the number of cycles 4505251881Speter through this loop. (A bit of calculation is much cheaper then 4506251881Speter disk io) */ 4507251881Speter unique_nr = baseNr + 3 * i; 4508251881Speter 4509251881Speter unique_name = svn_dirent_join(directory, 4510251881Speter apr_psprintf(scratch_pool, "svn-%X", 4511251881Speter unique_nr), 4512251881Speter scratch_pool); 4513251881Speter 4514251881Speter SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool)); 4515251881Speter 4516251881Speter apr_err = file_open(&try_file, unique_name_apr, flags, 4517251881Speter APR_OS_DEFAULT, FALSE, scratch_pool); 4518251881Speter 4519251881Speter if (APR_STATUS_IS_EEXIST(apr_err)) 4520251881Speter continue; 4521251881Speter else if (apr_err) 4522251881Speter { 4523251881Speter /* On Win32, CreateFile fails with an "Access Denied" error 4524251881Speter code, rather than "File Already Exists", if the colliding 4525251881Speter name belongs to a directory. */ 4526251881Speter 4527251881Speter if (APR_STATUS_IS_EACCES(apr_err)) 4528251881Speter { 4529251881Speter apr_finfo_t finfo; 4530251881Speter apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, 4531251881Speter APR_FINFO_TYPE, scratch_pool); 4532251881Speter 4533251881Speter if (!apr_err_2 && finfo.filetype == APR_DIR) 4534251881Speter continue; 4535251881Speter 4536251881Speter apr_err_2 = APR_TO_OS_ERROR(apr_err); 4537251881Speter 4538251881Speter if (apr_err_2 == ERROR_ACCESS_DENIED || 4539251881Speter apr_err_2 == ERROR_SHARING_VIOLATION) 4540251881Speter { 4541251881Speter /* The file is in use by another process or is hidden; 4542251881Speter create a new name, but don't do this 99999 times in 4543251881Speter case the folder is not writable */ 4544251881Speter i += 797; 4545251881Speter continue; 4546251881Speter } 4547251881Speter 4548251881Speter /* Else fall through and return the original error. */ 4549251881Speter } 4550251881Speter 4551251881Speter return svn_error_wrap_apr(apr_err, _("Can't open '%s'"), 4552251881Speter svn_dirent_local_style(unique_name, 4553251881Speter scratch_pool)); 4554251881Speter } 4555251881Speter else 4556251881Speter { 4557251881Speter /* Move file to the right pool */ 4558251881Speter apr_err = apr_file_setaside(new_file, try_file, result_pool); 4559251881Speter 4560251881Speter if (apr_err) 4561251881Speter return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"), 4562251881Speter svn_dirent_local_style(unique_name, 4563251881Speter scratch_pool)); 4564251881Speter 4565251881Speter *new_file_name = apr_pstrdup(result_pool, unique_name); 4566251881Speter 4567251881Speter return SVN_NO_ERROR; 4568251881Speter } 4569251881Speter } 4570251881Speter 4571251881Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 4572251881Speter NULL, 4573251881Speter _("Unable to make name in '%s'"), 4574251881Speter svn_dirent_local_style(directory, scratch_pool)); 4575251881Speter#endif 4576251881Speter} 4577251881Speter 4578251881Speter/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */ 4579251881Spetersvn_error_t * 4580251881Spetersvn_io_file_name_get(const char **filename, 4581251881Speter apr_file_t *file, 4582251881Speter apr_pool_t *pool) 4583251881Speter{ 4584251881Speter const char *fname_apr; 4585251881Speter apr_status_t status; 4586251881Speter 4587251881Speter status = apr_file_name_get(&fname_apr, file); 4588251881Speter if (status) 4589251881Speter return svn_error_wrap_apr(status, _("Can't get file name")); 4590251881Speter 4591251881Speter if (fname_apr) 4592251881Speter SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool)); 4593251881Speter else 4594251881Speter *filename = NULL; 4595251881Speter 4596251881Speter return SVN_NO_ERROR; 4597251881Speter} 4598251881Speter 4599251881Speter 4600251881Spetersvn_error_t * 4601251881Spetersvn_io_open_unique_file3(apr_file_t **file, 4602251881Speter const char **unique_path, 4603251881Speter const char *dirpath, 4604251881Speter svn_io_file_del_t delete_when, 4605251881Speter apr_pool_t *result_pool, 4606251881Speter apr_pool_t *scratch_pool) 4607251881Speter{ 4608251881Speter apr_file_t *tempfile; 4609251881Speter const char *tempname; 4610251881Speter struct temp_file_cleanup_s *baton = NULL; 4611251881Speter apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL | 4612251881Speter APR_BUFFERED | APR_BINARY); 4613251881Speter#if !defined(WIN32) && !defined(__OS2__) 4614251881Speter apr_fileperms_t perms; 4615251881Speter svn_boolean_t using_system_temp_dir = FALSE; 4616251881Speter#endif 4617251881Speter 4618251881Speter SVN_ERR_ASSERT(file || unique_path); 4619251881Speter if (file) 4620251881Speter *file = NULL; 4621251881Speter if (unique_path) 4622251881Speter *unique_path = NULL; 4623251881Speter 4624251881Speter if (dirpath == NULL) 4625251881Speter { 4626251881Speter#if !defined(WIN32) && !defined(__OS2__) 4627251881Speter using_system_temp_dir = TRUE; 4628251881Speter#endif 4629251881Speter SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool)); 4630251881Speter } 4631251881Speter 4632251881Speter switch (delete_when) 4633251881Speter { 4634251881Speter case svn_io_file_del_on_pool_cleanup: 4635251881Speter baton = apr_palloc(result_pool, sizeof(*baton)); 4636251881Speter baton->pool = result_pool; 4637251881Speter baton->fname_apr = NULL; 4638251881Speter 4639251881Speter /* Because cleanups are run LIFO, we need to make sure to register 4640251881Speter our cleanup before the apr_file_close cleanup: 4641251881Speter 4642251881Speter On Windows, you can't remove an open file. 4643251881Speter */ 4644251881Speter apr_pool_cleanup_register(result_pool, baton, 4645251881Speter temp_file_plain_cleanup_handler, 4646251881Speter temp_file_child_cleanup_handler); 4647251881Speter 4648251881Speter break; 4649251881Speter case svn_io_file_del_on_close: 4650251881Speter flags |= APR_DELONCLOSE; 4651251881Speter break; 4652251881Speter default: 4653251881Speter break; 4654251881Speter } 4655251881Speter 4656251881Speter SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags, 4657251881Speter result_pool, scratch_pool)); 4658251881Speter 4659251881Speter#if !defined(WIN32) && !defined(__OS2__) 4660251881Speter /* apr_file_mktemp() creates files with mode 0600. 4661251881Speter * This is appropriate if we're using a system temp dir since we don't 4662251881Speter * want to leak sensitive data into temp files other users can read. 4663251881Speter * If we're not using a system temp dir we're probably using the 4664251881Speter * .svn/tmp area and it's likely that the tempfile will end up being 4665251881Speter * copied or renamed into the working copy. 4666251881Speter * This would cause working files having mode 0600 while users might 4667251881Speter * expect to see 0644 or 0664. So we tweak perms of the tempfile in this 4668251881Speter * case, but only if the umask allows it. */ 4669251881Speter if (!using_system_temp_dir) 4670251881Speter { 4671251881Speter SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool)); 4672251881Speter SVN_ERR(file_perms_set2(tempfile, perms, scratch_pool)); 4673251881Speter } 4674251881Speter#endif 4675251881Speter 4676251881Speter if (file) 4677251881Speter *file = tempfile; 4678251881Speter else 4679251881Speter SVN_ERR(svn_io_file_close(tempfile, scratch_pool)); 4680251881Speter 4681251881Speter if (unique_path) 4682251881Speter *unique_path = tempname; /* Was allocated in result_pool */ 4683251881Speter 4684251881Speter if (baton) 4685251881Speter SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool)); 4686251881Speter 4687251881Speter return SVN_NO_ERROR; 4688251881Speter} 4689251881Speter 4690251881Spetersvn_error_t * 4691251881Spetersvn_io_file_readline(apr_file_t *file, 4692251881Speter svn_stringbuf_t **stringbuf, 4693251881Speter const char **eol, 4694251881Speter svn_boolean_t *eof, 4695251881Speter apr_size_t max_len, 4696251881Speter apr_pool_t *result_pool, 4697251881Speter apr_pool_t *scratch_pool) 4698251881Speter{ 4699251881Speter svn_stringbuf_t *str; 4700251881Speter const char *eol_str; 4701251881Speter apr_size_t numbytes; 4702251881Speter char c; 4703251881Speter apr_size_t len; 4704251881Speter svn_boolean_t found_eof; 4705251881Speter 4706251881Speter str = svn_stringbuf_create_ensure(80, result_pool); 4707251881Speter 4708251881Speter /* Read bytes into STR up to and including, but not storing, 4709251881Speter * the next EOL sequence. */ 4710251881Speter eol_str = NULL; 4711251881Speter numbytes = 1; 4712251881Speter len = 0; 4713251881Speter found_eof = FALSE; 4714251881Speter while (!found_eof) 4715251881Speter { 4716251881Speter if (len < max_len) 4717251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 4718251881Speter &found_eof, scratch_pool)); 4719251881Speter len++; 4720251881Speter if (numbytes != 1 || len > max_len) 4721251881Speter { 4722251881Speter found_eof = TRUE; 4723251881Speter break; 4724251881Speter } 4725251881Speter 4726251881Speter if (c == '\n') 4727251881Speter { 4728251881Speter eol_str = "\n"; 4729251881Speter } 4730251881Speter else if (c == '\r') 4731251881Speter { 4732251881Speter eol_str = "\r"; 4733251881Speter 4734251881Speter if (!found_eof && len < max_len) 4735251881Speter { 4736251881Speter apr_off_t pos; 4737251881Speter 4738251881Speter /* Check for "\r\n" by peeking at the next byte. */ 4739251881Speter pos = 0; 4740251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); 4741251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 4742251881Speter &found_eof, scratch_pool)); 4743251881Speter if (numbytes == 1 && c == '\n') 4744251881Speter { 4745251881Speter eol_str = "\r\n"; 4746251881Speter len++; 4747251881Speter } 4748251881Speter else 4749251881Speter { 4750251881Speter /* Pretend we never peeked. */ 4751251881Speter SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); 4752251881Speter found_eof = FALSE; 4753251881Speter numbytes = 1; 4754251881Speter } 4755251881Speter } 4756251881Speter } 4757251881Speter else 4758251881Speter svn_stringbuf_appendbyte(str, c); 4759251881Speter 4760251881Speter if (eol_str) 4761251881Speter break; 4762251881Speter } 4763251881Speter 4764251881Speter if (eol) 4765251881Speter *eol = eol_str; 4766251881Speter if (eof) 4767251881Speter *eof = found_eof; 4768251881Speter *stringbuf = str; 4769251881Speter 4770251881Speter return SVN_NO_ERROR; 4771251881Speter} 4772