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 50299742Sdim#if APR_HAVE_FCNTL_H 51299742Sdim#include <fcntl.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" 69299742Sdim#include "private/svn_utf_private.h" 70299742Sdim#include "private/svn_dep_compat.h" 71251881Speter 72251881Speter#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS" 73251881Speter 74251881Speter/* 75251881Speter Windows is 'aided' by a number of types of applications that 76251881Speter follow other applications around and open up files they have 77251881Speter changed for various reasons (the most intrusive are virus 78251881Speter scanners). So, if one of these other apps has glommed onto 79251881Speter our file we may get an 'access denied' error. 80251881Speter 81251881Speter This retry loop does not completely solve the problem (who 82251881Speter knows how long the other app is going to hold onto it for), but 83251881Speter goes a long way towards minimizing it. It is not an infinite 84251881Speter loop because there might really be an error. 85251881Speter 86251881Speter Another reason for retrying delete operations on Windows 87251881Speter is that they are asynchronous -- the file or directory is not 88251881Speter actually deleted until the last handle to it is closed. The 89251881Speter retry loop cannot completely solve this problem either, but can 90251881Speter help mitigate it. 91251881Speter*/ 92251881Speter#define RETRY_MAX_ATTEMPTS 100 93251881Speter#define RETRY_INITIAL_SLEEP 1000 94251881Speter#define RETRY_MAX_SLEEP 128000 95251881Speter 96251881Speter#define RETRY_LOOP(err, expr, retry_test, sleep_test) \ 97251881Speter do \ 98251881Speter { \ 99251881Speter apr_status_t os_err = APR_TO_OS_ERROR(err); \ 100251881Speter int sleep_count = RETRY_INITIAL_SLEEP; \ 101251881Speter int retries; \ 102251881Speter for (retries = 0; \ 103251881Speter retries < RETRY_MAX_ATTEMPTS && (retry_test); \ 104251881Speter os_err = APR_TO_OS_ERROR(err)) \ 105251881Speter { \ 106251881Speter if (sleep_test) \ 107251881Speter { \ 108251881Speter ++retries; \ 109251881Speter apr_sleep(sleep_count); \ 110251881Speter if (sleep_count < RETRY_MAX_SLEEP) \ 111251881Speter sleep_count *= 2; \ 112251881Speter } \ 113251881Speter (err) = (expr); \ 114251881Speter } \ 115251881Speter } \ 116251881Speter while (0) 117251881Speter 118251881Speter#if defined(EDEADLK) && APR_HAS_THREADS 119251881Speter#define FILE_LOCK_RETRY_LOOP(err, expr) \ 120251881Speter RETRY_LOOP(err, \ 121251881Speter expr, \ 122251881Speter (APR_STATUS_IS_EINTR(err) || os_err == EDEADLK), \ 123251881Speter (!APR_STATUS_IS_EINTR(err))) 124251881Speter#else 125251881Speter#define FILE_LOCK_RETRY_LOOP(err, expr) \ 126251881Speter RETRY_LOOP(err, \ 127251881Speter expr, \ 128251881Speter (APR_STATUS_IS_EINTR(err)), \ 129251881Speter 0) 130251881Speter#endif 131251881Speter 132251881Speter#ifndef WIN32_RETRY_LOOP 133251881Speter#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP) 134251881Speter#define WIN32_RETRY_LOOP(err, expr) \ 135251881Speter RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED \ 136251881Speter || os_err == ERROR_SHARING_VIOLATION \ 137251881Speter || os_err == ERROR_DIR_NOT_EMPTY), \ 138251881Speter 1) 139251881Speter#else 140251881Speter#define WIN32_RETRY_LOOP(err, expr) ((void)0) 141251881Speter#endif 142251881Speter#endif 143251881Speter 144299742Sdim#ifdef WIN32 145299742Sdim 146299742Sdim#if _WIN32_WINNT < 0x600 /* Does the SDK assume Windows Vista+? */ 147299742Sdimtypedef struct _FILE_RENAME_INFO { 148299742Sdim BOOL ReplaceIfExists; 149299742Sdim HANDLE RootDirectory; 150299742Sdim DWORD FileNameLength; 151299742Sdim WCHAR FileName[1]; 152299742Sdim} FILE_RENAME_INFO, *PFILE_RENAME_INFO; 153299742Sdim 154299742Sdimtypedef struct _FILE_DISPOSITION_INFO { 155299742Sdim BOOL DeleteFile; 156299742Sdim} FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO; 157299742Sdim 158299742Sdim#define FileRenameInfo 3 159299742Sdim#define FileDispositionInfo 4 160299742Sdim#endif /* WIN32 < Vista */ 161299742Sdim 162299742Sdim/* One-time initialization of the late bound Windows API functions. */ 163299742Sdimstatic volatile svn_atomic_t win_dynamic_imports_state = 0; 164299742Sdim 165299742Sdim/* Pointer to GetFinalPathNameByHandleW function from kernel32.dll. */ 166299742Sdimtypedef DWORD (WINAPI *GETFINALPATHNAMEBYHANDLE)( 167299742Sdim HANDLE hFile, 168299742Sdim WCHAR *lpszFilePath, 169299742Sdim DWORD cchFilePath, 170299742Sdim DWORD dwFlags); 171299742Sdim 172299742Sdimtypedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile, 173299742Sdim int FileInformationClass, 174299742Sdim LPVOID lpFileInformation, 175299742Sdim DWORD dwBufferSize); 176299742Sdim 177299742Sdimstatic GETFINALPATHNAMEBYHANDLE get_final_path_name_by_handle_proc = NULL; 178299742Sdimstatic SetFileInformationByHandle_t set_file_information_by_handle_proc = NULL; 179299742Sdim 180299742Sdim/* Forward declaration. */ 181299742Sdimstatic svn_error_t * io_win_read_link(svn_string_t **dest, 182299742Sdim const char *path, 183299742Sdim apr_pool_t *pool); 184299742Sdim 185299742Sdim#endif 186299742Sdim 187251881Speter/* Forward declaration */ 188251881Speterstatic apr_status_t 189251881Speterdir_is_empty(const char *dir, apr_pool_t *pool); 190251881Speterstatic APR_INLINE svn_error_t * 191251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status, 192251881Speter const char *msg, const char *msg_no_name, 193251881Speter apr_pool_t *pool); 194251881Speter 195251881Speter/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on 196251881Speter * operating systems where APR always uses utf-8 as native path format */ 197251881Speterstatic svn_error_t * 198251881Spetercstring_to_utf8(const char **path_utf8, 199251881Speter const char *path_apr, 200251881Speter apr_pool_t *pool) 201251881Speter{ 202251881Speter#if defined(WIN32) || defined(DARWIN) 203251881Speter *path_utf8 = path_apr; 204251881Speter return SVN_NO_ERROR; 205251881Speter#else 206251881Speter return svn_path_cstring_to_utf8(path_utf8, path_apr, pool); 207251881Speter#endif 208251881Speter} 209251881Speter 210251881Speter/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on 211251881Speter * operating systems where APR always uses utf-8 as native path format */ 212251881Speterstatic svn_error_t * 213251881Spetercstring_from_utf8(const char **path_apr, 214251881Speter const char *path_utf8, 215251881Speter apr_pool_t *pool) 216251881Speter{ 217251881Speter#if defined(WIN32) || defined(DARWIN) 218251881Speter *path_apr = path_utf8; 219251881Speter return SVN_NO_ERROR; 220251881Speter#else 221251881Speter return svn_path_cstring_from_utf8(path_apr, path_utf8, pool); 222251881Speter#endif 223251881Speter} 224251881Speter 225251881Speter/* Helper function that allows to convert an APR-level PATH to something 226251881Speter * that we can pass the svn_error_wrap_apr. Since we use it in context 227251881Speter * of error reporting, having *some* path info may be more useful than 228251881Speter * having none. Therefore, we use a best effort approach here. 229251881Speter * 230251881Speter * This is different from svn_io_file_name_get in that it uses a different 231251881Speter * signature style and will never fail. 232251881Speter */ 233251881Speterstatic const char * 234251881Spetertry_utf8_from_internal_style(const char *path, apr_pool_t *pool) 235251881Speter{ 236251881Speter svn_error_t *error; 237251881Speter const char *path_utf8; 238251881Speter 239251881Speter /* Special case. */ 240251881Speter if (path == NULL) 241251881Speter return "(NULL)"; 242251881Speter 243251881Speter /* (try to) convert PATH to UTF-8. If that fails, continue with the plain 244251881Speter * PATH because it is the best we have. It may actually be UTF-8 already. 245251881Speter */ 246251881Speter error = cstring_to_utf8(&path_utf8, path, pool); 247251881Speter if (error) 248251881Speter { 249251881Speter /* fallback to best representation we have */ 250251881Speter 251251881Speter svn_error_clear(error); 252251881Speter path_utf8 = path; 253251881Speter } 254251881Speter 255251881Speter /* Toggle (back-)slashes etc. as necessary. 256251881Speter */ 257251881Speter return svn_dirent_local_style(path_utf8, pool); 258251881Speter} 259251881Speter 260251881Speter 261251881Speter/* Set *NAME_P to the UTF-8 representation of directory entry NAME. 262251881Speter * NAME is in the internal encoding used by APR; PARENT is in 263251881Speter * UTF-8 and in internal (not local) style. 264251881Speter * 265251881Speter * Use PARENT only for generating an error string if the conversion 266251881Speter * fails because NAME could not be represented in UTF-8. In that 267251881Speter * case, return a two-level error in which the outer error's message 268251881Speter * mentions PARENT, but the inner error's message does not mention 269251881Speter * NAME (except possibly in hex) since NAME may not be printable. 270251881Speter * Such a compound error at least allows the user to go looking in the 271251881Speter * right directory for the problem. 272251881Speter * 273251881Speter * If there is any other error, just return that error directly. 274251881Speter * 275251881Speter * If there is any error, the effect on *NAME_P is undefined. 276251881Speter * 277251881Speter * *NAME_P and NAME may refer to the same storage. 278251881Speter */ 279251881Speterstatic svn_error_t * 280251881Speterentry_name_to_utf8(const char **name_p, 281251881Speter const char *name, 282251881Speter const char *parent, 283251881Speter apr_pool_t *pool) 284251881Speter{ 285251881Speter#if defined(WIN32) || defined(DARWIN) 286251881Speter *name_p = apr_pstrdup(pool, name); 287251881Speter return SVN_NO_ERROR; 288251881Speter#else 289251881Speter svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool); 290251881Speter if (err && err->apr_err == APR_EINVAL) 291251881Speter { 292251881Speter return svn_error_createf(err->apr_err, err, 293251881Speter _("Error converting entry " 294251881Speter "in directory '%s' to UTF-8"), 295251881Speter svn_dirent_local_style(parent, pool)); 296251881Speter } 297251881Speter return err; 298251881Speter#endif 299251881Speter} 300251881Speter 301251881Speter 302251881Speter 303251881Speterstatic void 304251881Spetermap_apr_finfo_to_node_kind(svn_node_kind_t *kind, 305251881Speter svn_boolean_t *is_special, 306251881Speter apr_finfo_t *finfo) 307251881Speter{ 308251881Speter *is_special = FALSE; 309251881Speter 310251881Speter if (finfo->filetype == APR_REG) 311251881Speter *kind = svn_node_file; 312251881Speter else if (finfo->filetype == APR_DIR) 313251881Speter *kind = svn_node_dir; 314251881Speter else if (finfo->filetype == APR_LNK) 315251881Speter { 316251881Speter *is_special = TRUE; 317251881Speter *kind = svn_node_file; 318251881Speter } 319251881Speter else 320251881Speter *kind = svn_node_unknown; 321251881Speter} 322251881Speter 323251881Speter/* Helper for svn_io_check_path() and svn_io_check_resolved_path(); 324251881Speter essentially the same semantics as those two, with the obvious 325251881Speter interpretation for RESOLVE_SYMLINKS. */ 326251881Speterstatic svn_error_t * 327251881Speterio_check_path(const char *path, 328251881Speter svn_boolean_t resolve_symlinks, 329251881Speter svn_boolean_t *is_special_p, 330251881Speter svn_node_kind_t *kind, 331251881Speter apr_pool_t *pool) 332251881Speter{ 333251881Speter apr_int32_t flags; 334251881Speter apr_finfo_t finfo; 335251881Speter apr_status_t apr_err; 336251881Speter const char *path_apr; 337251881Speter svn_boolean_t is_special = FALSE; 338251881Speter 339251881Speter if (path[0] == '\0') 340251881Speter path = "."; 341251881Speter 342251881Speter /* Not using svn_io_stat() here because we want to check the 343251881Speter apr_err return explicitly. */ 344251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 345251881Speter 346251881Speter flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK); 347251881Speter apr_err = apr_stat(&finfo, path_apr, flags, pool); 348251881Speter 349251881Speter if (APR_STATUS_IS_ENOENT(apr_err)) 350251881Speter *kind = svn_node_none; 351251881Speter else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err)) 352251881Speter *kind = svn_node_none; 353251881Speter else if (apr_err) 354251881Speter return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"), 355251881Speter svn_dirent_local_style(path, pool)); 356251881Speter else 357251881Speter map_apr_finfo_to_node_kind(kind, &is_special, &finfo); 358251881Speter 359251881Speter *is_special_p = is_special; 360251881Speter 361251881Speter return SVN_NO_ERROR; 362251881Speter} 363251881Speter 364251881Speter 365251881Speter/* Wrapper for apr_file_open(), taking an APR-encoded filename. */ 366251881Speterstatic apr_status_t 367251881Speterfile_open(apr_file_t **f, 368251881Speter const char *fname_apr, 369251881Speter apr_int32_t flag, 370251881Speter apr_fileperms_t perm, 371251881Speter svn_boolean_t retry_on_failure, 372251881Speter apr_pool_t *pool) 373251881Speter{ 374251881Speter apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool); 375251881Speter 376251881Speter if (retry_on_failure) 377251881Speter { 378299742Sdim#ifdef WIN32 379299742Sdim if (status == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED)) 380299742Sdim { 381299742Sdim if ((flag & (APR_CREATE | APR_EXCL)) == (APR_CREATE | APR_EXCL)) 382299742Sdim return status; /* Can't create if there is something */ 383299742Sdim 384299742Sdim if (flag & (APR_WRITE | APR_CREATE)) 385299742Sdim { 386299742Sdim apr_finfo_t finfo; 387299742Sdim 388299742Sdim if (!apr_stat(&finfo, fname_apr, SVN__APR_FINFO_READONLY, pool)) 389299742Sdim { 390299742Sdim if (finfo.protection & APR_FREADONLY) 391299742Sdim return status; /* Retrying won't fix this */ 392299742Sdim } 393299742Sdim } 394299742Sdim } 395299742Sdim#endif 396299742Sdim 397251881Speter WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool)); 398251881Speter } 399251881Speter return status; 400251881Speter} 401251881Speter 402251881Speter 403251881Spetersvn_error_t * 404251881Spetersvn_io_check_resolved_path(const char *path, 405251881Speter svn_node_kind_t *kind, 406251881Speter apr_pool_t *pool) 407251881Speter{ 408251881Speter svn_boolean_t ignored; 409251881Speter return io_check_path(path, TRUE, &ignored, kind, pool); 410251881Speter} 411251881Speter 412251881Spetersvn_error_t * 413251881Spetersvn_io_check_path(const char *path, 414251881Speter svn_node_kind_t *kind, 415251881Speter apr_pool_t *pool) 416251881Speter{ 417251881Speter svn_boolean_t ignored; 418251881Speter return io_check_path(path, FALSE, &ignored, kind, pool); 419251881Speter} 420251881Speter 421251881Spetersvn_error_t * 422251881Spetersvn_io_check_special_path(const char *path, 423251881Speter svn_node_kind_t *kind, 424251881Speter svn_boolean_t *is_special, 425251881Speter apr_pool_t *pool) 426251881Speter{ 427251881Speter return io_check_path(path, FALSE, is_special, kind, pool); 428251881Speter} 429251881Speter 430251881Speterstruct temp_file_cleanup_s 431251881Speter{ 432251881Speter apr_pool_t *pool; 433251881Speter /* The (APR-encoded) full path of the file to be removed, or NULL if 434251881Speter * nothing to do. */ 435251881Speter const char *fname_apr; 436251881Speter}; 437251881Speter 438251881Speter 439251881Speterstatic apr_status_t 440251881Spetertemp_file_plain_cleanup_handler(void *baton) 441251881Speter{ 442251881Speter struct temp_file_cleanup_s *b = baton; 443251881Speter apr_status_t apr_err = APR_SUCCESS; 444251881Speter 445251881Speter if (b->fname_apr) 446251881Speter { 447251881Speter apr_err = apr_file_remove(b->fname_apr, b->pool); 448251881Speter WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool)); 449251881Speter } 450251881Speter 451251881Speter return apr_err; 452251881Speter} 453251881Speter 454251881Speter 455251881Speterstatic apr_status_t 456251881Spetertemp_file_child_cleanup_handler(void *baton) 457251881Speter{ 458251881Speter struct temp_file_cleanup_s *b = baton; 459251881Speter 460251881Speter apr_pool_cleanup_kill(b->pool, b, 461251881Speter temp_file_plain_cleanup_handler); 462251881Speter 463251881Speter return APR_SUCCESS; 464251881Speter} 465251881Speter 466251881Speter 467251881Spetersvn_error_t * 468251881Spetersvn_io_open_uniquely_named(apr_file_t **file, 469251881Speter const char **unique_path, 470251881Speter const char *dirpath, 471251881Speter const char *filename, 472251881Speter const char *suffix, 473251881Speter svn_io_file_del_t delete_when, 474251881Speter apr_pool_t *result_pool, 475251881Speter apr_pool_t *scratch_pool) 476251881Speter{ 477251881Speter const char *path; 478251881Speter unsigned int i; 479251881Speter struct temp_file_cleanup_s *baton = NULL; 480251881Speter 481251881Speter /* At the beginning, we don't know whether unique_path will need 482251881Speter UTF8 conversion */ 483251881Speter svn_boolean_t needs_utf8_conversion = TRUE; 484251881Speter 485251881Speter SVN_ERR_ASSERT(file || unique_path); 486251881Speter 487251881Speter if (dirpath == NULL) 488251881Speter SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool)); 489251881Speter if (filename == NULL) 490251881Speter filename = "tempfile"; 491251881Speter if (suffix == NULL) 492251881Speter suffix = ".tmp"; 493251881Speter 494251881Speter path = svn_dirent_join(dirpath, filename, scratch_pool); 495251881Speter 496251881Speter if (delete_when == svn_io_file_del_on_pool_cleanup) 497251881Speter { 498251881Speter baton = apr_palloc(result_pool, sizeof(*baton)); 499251881Speter 500251881Speter baton->pool = result_pool; 501251881Speter baton->fname_apr = NULL; 502251881Speter 503251881Speter /* Because cleanups are run LIFO, we need to make sure to register 504251881Speter our cleanup before the apr_file_close cleanup: 505251881Speter 506251881Speter On Windows, you can't remove an open file. 507251881Speter */ 508251881Speter apr_pool_cleanup_register(result_pool, baton, 509251881Speter temp_file_plain_cleanup_handler, 510251881Speter temp_file_child_cleanup_handler); 511251881Speter } 512251881Speter 513251881Speter for (i = 1; i <= 99999; i++) 514251881Speter { 515251881Speter const char *unique_name; 516251881Speter const char *unique_name_apr; 517251881Speter apr_file_t *try_file; 518251881Speter apr_status_t apr_err; 519251881Speter apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL 520251881Speter | APR_BUFFERED | APR_BINARY); 521251881Speter 522251881Speter if (delete_when == svn_io_file_del_on_close) 523251881Speter flag |= APR_DELONCLOSE; 524251881Speter 525251881Speter /* Special case the first attempt -- if we can avoid having a 526251881Speter generated numeric portion at all, that's best. So first we 527251881Speter try with just the suffix; then future tries add a number 528251881Speter before the suffix. (A do-while loop could avoid the repeated 529251881Speter conditional, but it's not worth the clarity loss.) 530251881Speter 531251881Speter If the first attempt fails, the first number will be "2". 532251881Speter This is good, since "1" would misleadingly imply that 533251881Speter the second attempt was actually the first... and if someone's 534251881Speter got conflicts on their conflicts, we probably don't want to 535251881Speter add to their confusion :-). */ 536251881Speter if (i == 1) 537251881Speter unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix); 538251881Speter else 539251881Speter unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix); 540251881Speter 541251881Speter /* Hmmm. Ideally, we would append to a native-encoding buf 542251881Speter before starting iteration, then convert back to UTF-8 for 543251881Speter return. But I suppose that would make the appending code 544251881Speter sensitive to i18n in a way it shouldn't be... Oh well. */ 545251881Speter if (needs_utf8_conversion) 546251881Speter { 547251881Speter SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, 548251881Speter scratch_pool)); 549251881Speter if (i == 1) 550251881Speter { 551251881Speter /* The variable parts of unique_name will not require UTF8 552251881Speter conversion. Therefore, if UTF8 conversion had no effect 553251881Speter on it in the first iteration, it won't require conversion 554251881Speter in any future iteration. */ 555251881Speter needs_utf8_conversion = strcmp(unique_name_apr, unique_name); 556251881Speter } 557251881Speter } 558251881Speter else 559251881Speter unique_name_apr = unique_name; 560251881Speter 561251881Speter apr_err = file_open(&try_file, unique_name_apr, flag, 562251881Speter APR_OS_DEFAULT, FALSE, result_pool); 563251881Speter 564251881Speter if (APR_STATUS_IS_EEXIST(apr_err)) 565251881Speter continue; 566251881Speter else if (apr_err) 567251881Speter { 568251881Speter /* On Win32, CreateFile fails with an "Access Denied" error 569251881Speter code, rather than "File Already Exists", if the colliding 570251881Speter name belongs to a directory. */ 571251881Speter if (APR_STATUS_IS_EACCES(apr_err)) 572251881Speter { 573251881Speter apr_finfo_t finfo; 574251881Speter apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, 575251881Speter APR_FINFO_TYPE, scratch_pool); 576251881Speter 577251881Speter if (!apr_err_2 && finfo.filetype == APR_DIR) 578251881Speter continue; 579251881Speter 580251881Speter#ifdef WIN32 581251881Speter apr_err_2 = APR_TO_OS_ERROR(apr_err); 582251881Speter 583251881Speter if (apr_err_2 == ERROR_ACCESS_DENIED || 584251881Speter apr_err_2 == ERROR_SHARING_VIOLATION) 585251881Speter { 586251881Speter /* The file is in use by another process or is hidden; 587251881Speter create a new name, but don't do this 99999 times in 588251881Speter case the folder is not writable */ 589251881Speter i += 797; 590251881Speter continue; 591251881Speter } 592251881Speter#endif 593251881Speter 594251881Speter /* Else fall through and return the original error. */ 595251881Speter } 596251881Speter 597251881Speter if (file) 598251881Speter *file = NULL; 599251881Speter if (unique_path) 600251881Speter *unique_path = NULL; 601251881Speter return svn_error_wrap_apr(apr_err, _("Can't open '%s'"), 602251881Speter svn_dirent_local_style(unique_name, 603251881Speter scratch_pool)); 604251881Speter } 605251881Speter else 606251881Speter { 607251881Speter if (delete_when == svn_io_file_del_on_pool_cleanup) 608251881Speter baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr); 609251881Speter 610251881Speter if (file) 611251881Speter *file = try_file; 612251881Speter else 613251881Speter apr_file_close(try_file); 614251881Speter if (unique_path) 615251881Speter *unique_path = apr_pstrdup(result_pool, unique_name); 616251881Speter 617251881Speter return SVN_NO_ERROR; 618251881Speter } 619251881Speter } 620251881Speter 621251881Speter if (file) 622251881Speter *file = NULL; 623251881Speter if (unique_path) 624251881Speter *unique_path = NULL; 625251881Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 626251881Speter NULL, 627251881Speter _("Unable to make name for '%s'"), 628251881Speter svn_dirent_local_style(path, scratch_pool)); 629251881Speter} 630251881Speter 631251881Spetersvn_error_t * 632251881Spetersvn_io_create_unique_link(const char **unique_name_p, 633251881Speter const char *path, 634251881Speter const char *dest, 635251881Speter const char *suffix, 636251881Speter apr_pool_t *pool) 637251881Speter{ 638251881Speter#ifdef HAVE_SYMLINK 639251881Speter unsigned int i; 640251881Speter const char *unique_name; 641251881Speter const char *unique_name_apr; 642251881Speter const char *dest_apr; 643251881Speter int rv; 644251881Speter 645251881Speter SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool)); 646251881Speter for (i = 1; i <= 99999; i++) 647251881Speter { 648251881Speter apr_status_t apr_err; 649251881Speter 650251881Speter /* Special case the first attempt -- if we can avoid having a 651251881Speter generated numeric portion at all, that's best. So first we 652251881Speter try with just the suffix; then future tries add a number 653251881Speter before the suffix. (A do-while loop could avoid the repeated 654251881Speter conditional, but it's not worth the clarity loss.) 655251881Speter 656251881Speter If the first attempt fails, the first number will be "2". 657251881Speter This is good, since "1" would misleadingly imply that 658251881Speter the second attempt was actually the first... and if someone's 659251881Speter got conflicts on their conflicts, we probably don't want to 660251881Speter add to their confusion :-). */ 661251881Speter if (i == 1) 662251881Speter unique_name = apr_psprintf(pool, "%s%s", path, suffix); 663251881Speter else 664251881Speter unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix); 665251881Speter 666251881Speter /* Hmmm. Ideally, we would append to a native-encoding buf 667251881Speter before starting iteration, then convert back to UTF-8 for 668251881Speter return. But I suppose that would make the appending code 669251881Speter sensitive to i18n in a way it shouldn't be... Oh well. */ 670251881Speter SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool)); 671251881Speter do { 672251881Speter rv = symlink(dest_apr, unique_name_apr); 673251881Speter } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); 674251881Speter 675251881Speter apr_err = apr_get_os_error(); 676251881Speter 677251881Speter if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err)) 678251881Speter continue; 679251881Speter else if (rv == -1 && apr_err) 680251881Speter { 681251881Speter /* On Win32, CreateFile fails with an "Access Denied" error 682251881Speter code, rather than "File Already Exists", if the colliding 683251881Speter name belongs to a directory. */ 684251881Speter if (APR_STATUS_IS_EACCES(apr_err)) 685251881Speter { 686251881Speter apr_finfo_t finfo; 687251881Speter apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, 688251881Speter APR_FINFO_TYPE, pool); 689251881Speter 690251881Speter if (!apr_err_2 691251881Speter && (finfo.filetype == APR_DIR)) 692251881Speter continue; 693251881Speter 694251881Speter /* Else ignore apr_err_2; better to fall through and 695251881Speter return the original error. */ 696251881Speter } 697251881Speter 698251881Speter *unique_name_p = NULL; 699251881Speter return svn_error_wrap_apr(apr_err, 700251881Speter _("Can't create symbolic link '%s'"), 701251881Speter svn_dirent_local_style(unique_name, pool)); 702251881Speter } 703251881Speter else 704251881Speter { 705251881Speter *unique_name_p = unique_name; 706251881Speter return SVN_NO_ERROR; 707251881Speter } 708251881Speter } 709251881Speter 710251881Speter *unique_name_p = NULL; 711251881Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 712251881Speter NULL, 713251881Speter _("Unable to make name for '%s'"), 714251881Speter svn_dirent_local_style(path, pool)); 715251881Speter#else 716251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 717251881Speter _("Symbolic links are not supported on this " 718251881Speter "platform")); 719251881Speter#endif 720251881Speter} 721251881Speter 722251881Spetersvn_error_t * 723251881Spetersvn_io_read_link(svn_string_t **dest, 724251881Speter const char *path, 725251881Speter apr_pool_t *pool) 726251881Speter{ 727299742Sdim#if defined(HAVE_READLINK) 728251881Speter svn_string_t dest_apr; 729251881Speter const char *path_apr; 730251881Speter char buf[1025]; 731251881Speter ssize_t rv; 732251881Speter 733251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 734251881Speter do { 735251881Speter rv = readlink(path_apr, buf, sizeof(buf) - 1); 736251881Speter } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); 737251881Speter 738251881Speter if (rv == -1) 739251881Speter return svn_error_wrap_apr(apr_get_os_error(), 740251881Speter _("Can't read contents of link")); 741251881Speter 742251881Speter buf[rv] = '\0'; 743251881Speter dest_apr.data = buf; 744251881Speter dest_apr.len = rv; 745251881Speter 746251881Speter /* ### Cast needed, one of these interfaces is wrong */ 747251881Speter return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool); 748299742Sdim#elif defined(WIN32) 749299742Sdim return io_win_read_link(dest, path, pool); 750251881Speter#else 751251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 752251881Speter _("Symbolic links are not supported on this " 753251881Speter "platform")); 754251881Speter#endif 755251881Speter} 756251881Speter 757251881Speter 758251881Spetersvn_error_t * 759251881Spetersvn_io_copy_link(const char *src, 760251881Speter const char *dst, 761251881Speter apr_pool_t *pool) 762251881Speter 763251881Speter{ 764251881Speter#ifdef HAVE_READLINK 765251881Speter svn_string_t *link_dest; 766251881Speter const char *dst_tmp; 767251881Speter 768251881Speter /* Notice what the link is pointing at... */ 769251881Speter SVN_ERR(svn_io_read_link(&link_dest, src, pool)); 770251881Speter 771251881Speter /* Make a tmp-link pointing at the same thing. */ 772251881Speter SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data, 773251881Speter ".tmp", pool)); 774251881Speter 775251881Speter /* Move the tmp-link to link. */ 776251881Speter return svn_io_file_rename(dst_tmp, dst, pool); 777251881Speter 778251881Speter#else 779251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 780251881Speter _("Symbolic links are not supported on this " 781251881Speter "platform")); 782251881Speter#endif 783251881Speter} 784251881Speter 785251881Speter/* Temporary directory name cache for svn_io_temp_dir() */ 786251881Speterstatic volatile svn_atomic_t temp_dir_init_state = 0; 787251881Speterstatic const char *temp_dir; 788251881Speter 789251881Speter/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */ 790251881Speterstatic svn_error_t * 791251881Speterinit_temp_dir(void *baton, apr_pool_t *scratch_pool) 792251881Speter{ 793251881Speter /* Global pool for the temp path */ 794251881Speter apr_pool_t *global_pool = svn_pool_create(NULL); 795251881Speter const char *dir; 796251881Speter 797251881Speter apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool); 798251881Speter 799251881Speter if (apr_err) 800251881Speter return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory")); 801251881Speter 802251881Speter SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool)); 803251881Speter 804251881Speter dir = svn_dirent_internal_style(dir, scratch_pool); 805251881Speter 806251881Speter SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool)); 807251881Speter 808251881Speter return SVN_NO_ERROR; 809251881Speter} 810251881Speter 811251881Speter 812251881Spetersvn_error_t * 813251881Spetersvn_io_temp_dir(const char **dir, 814251881Speter apr_pool_t *pool) 815251881Speter{ 816251881Speter SVN_ERR(svn_atomic__init_once(&temp_dir_init_state, 817251881Speter init_temp_dir, NULL, pool)); 818251881Speter 819251881Speter *dir = apr_pstrdup(pool, temp_dir); 820251881Speter 821251881Speter return SVN_NO_ERROR; 822251881Speter} 823251881Speter 824251881Speter 825251881Speter 826251881Speter 827251881Speter/*** Creating, copying and appending files. ***/ 828251881Speter 829251881Speter/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary 830251881Speter * allocations. 831251881Speter * 832251881Speter * NOTE: We don't use apr_copy_file() for this, since it takes filenames 833251881Speter * as parameters. Since we want to copy to a temporary file 834251881Speter * and rename for atomicity (see below), this would require an extra 835251881Speter * close/open pair, which can be expensive, especially on 836251881Speter * remote file systems. 837251881Speter */ 838251881Speterstatic apr_status_t 839251881Spetercopy_contents(apr_file_t *from_file, 840251881Speter apr_file_t *to_file, 841251881Speter apr_pool_t *pool) 842251881Speter{ 843251881Speter /* Copy bytes till the cows come home. */ 844251881Speter while (1) 845251881Speter { 846251881Speter char buf[SVN__STREAM_CHUNK_SIZE]; 847251881Speter apr_size_t bytes_this_time = sizeof(buf); 848251881Speter apr_status_t read_err; 849251881Speter apr_status_t write_err; 850251881Speter 851251881Speter /* Read 'em. */ 852251881Speter read_err = apr_file_read(from_file, buf, &bytes_this_time); 853251881Speter if (read_err && !APR_STATUS_IS_EOF(read_err)) 854251881Speter { 855251881Speter return read_err; 856251881Speter } 857251881Speter 858251881Speter /* Write 'em. */ 859251881Speter write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL); 860251881Speter if (write_err) 861251881Speter { 862251881Speter return write_err; 863251881Speter } 864251881Speter 865251881Speter if (read_err && APR_STATUS_IS_EOF(read_err)) 866251881Speter { 867251881Speter /* Return the results of this close: an error, or success. */ 868251881Speter return APR_SUCCESS; 869251881Speter } 870251881Speter } 871251881Speter /* NOTREACHED */ 872251881Speter} 873251881Speter 874251881Speter 875251881Spetersvn_error_t * 876251881Spetersvn_io_copy_file(const char *src, 877251881Speter const char *dst, 878251881Speter svn_boolean_t copy_perms, 879251881Speter apr_pool_t *pool) 880251881Speter{ 881251881Speter apr_file_t *from_file, *to_file; 882251881Speter apr_status_t apr_err; 883251881Speter const char *dst_tmp; 884251881Speter svn_error_t *err; 885251881Speter 886251881Speter /* ### NOTE: sometimes src == dst. In this case, because we copy to a 887251881Speter ### temporary file, and then rename over the top of the destination, 888251881Speter ### the net result is resetting the permissions on src/dst. 889251881Speter ### 890251881Speter ### Note: specifically, this can happen during a switch when the desired 891251881Speter ### permissions for a file change from one branch to another. See 892251881Speter ### switch_tests 17. 893251881Speter ### 894251881Speter ### ... yes, we should avoid copying to the same file, and we should 895251881Speter ### make the "reset perms" explicit. The switch *happens* to work 896251881Speter ### because of this copy-to-temp-then-rename implementation. If it 897251881Speter ### weren't for that, the switch would break. 898251881Speter */ 899251881Speter#ifdef CHECK_FOR_SAME_FILE 900251881Speter if (strcmp(src, dst) == 0) 901251881Speter return SVN_NO_ERROR; 902251881Speter#endif 903251881Speter 904251881Speter SVN_ERR(svn_io_file_open(&from_file, src, APR_READ, 905251881Speter APR_OS_DEFAULT, pool)); 906251881Speter 907251881Speter /* For atomicity, we copy to a tmp file and then rename the tmp 908251881Speter file over the real destination. */ 909251881Speter 910251881Speter SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp, 911251881Speter svn_dirent_dirname(dst, pool), 912251881Speter svn_io_file_del_none, pool, pool)); 913251881Speter 914251881Speter apr_err = copy_contents(from_file, to_file, pool); 915251881Speter 916251881Speter if (apr_err) 917251881Speter { 918251881Speter err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"), 919251881Speter svn_dirent_local_style(src, pool), 920251881Speter svn_dirent_local_style(dst_tmp, pool)); 921251881Speter } 922251881Speter else 923251881Speter err = NULL; 924251881Speter 925251881Speter err = svn_error_compose_create(err, 926251881Speter svn_io_file_close(from_file, pool)); 927251881Speter 928251881Speter err = svn_error_compose_create(err, 929251881Speter svn_io_file_close(to_file, pool)); 930251881Speter 931251881Speter if (err) 932251881Speter { 933251881Speter return svn_error_compose_create( 934251881Speter err, 935251881Speter svn_io_remove_file2(dst_tmp, TRUE, pool)); 936251881Speter } 937251881Speter 938251881Speter /* If copying perms, set the perms on dst_tmp now, so they will be 939251881Speter atomically inherited in the upcoming rename. But note that we 940251881Speter had to wait until now to set perms, because if they say 941251881Speter read-only, then we'd have failed filling dst_tmp's contents. */ 942251881Speter if (copy_perms) 943251881Speter SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool)); 944251881Speter 945251881Speter return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool)); 946251881Speter} 947251881Speter 948251881Speter#if !defined(WIN32) && !defined(__OS2__) 949251881Speter/* Wrapper for apr_file_perms_set(), taking a UTF8-encoded filename. */ 950251881Speterstatic svn_error_t * 951251881Speterfile_perms_set(const char *fname, apr_fileperms_t perms, 952251881Speter apr_pool_t *pool) 953251881Speter{ 954251881Speter const char *fname_apr; 955251881Speter apr_status_t status; 956251881Speter 957251881Speter SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); 958251881Speter 959251881Speter status = apr_file_perms_set(fname_apr, perms); 960251881Speter if (status) 961251881Speter return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"), 962251881Speter fname); 963251881Speter else 964251881Speter return SVN_NO_ERROR; 965251881Speter} 966251881Speter 967251881Speter/* Set permissions PERMS on the FILE. This is a cheaper variant of the 968251881Speter * file_perms_set wrapper() function because no locale-dependent string 969251881Speter * conversion is required. POOL will be used for allocations. 970251881Speter */ 971251881Speterstatic svn_error_t * 972251881Speterfile_perms_set2(apr_file_t* file, apr_fileperms_t perms, apr_pool_t *pool) 973251881Speter{ 974251881Speter const char *fname_apr; 975251881Speter apr_status_t status; 976251881Speter 977251881Speter status = apr_file_name_get(&fname_apr, file); 978251881Speter if (status) 979251881Speter return svn_error_wrap_apr(status, _("Can't get file name")); 980251881Speter 981251881Speter status = apr_file_perms_set(fname_apr, perms); 982251881Speter if (status) 983251881Speter return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"), 984251881Speter try_utf8_from_internal_style(fname_apr, pool)); 985251881Speter else 986251881Speter return SVN_NO_ERROR; 987251881Speter} 988251881Speter 989251881Speter#endif /* !WIN32 && !__OS2__ */ 990251881Speter 991251881Spetersvn_error_t * 992251881Spetersvn_io_copy_perms(const char *src, 993251881Speter const char *dst, 994251881Speter apr_pool_t *pool) 995251881Speter{ 996251881Speter /* ### On Windows or OS/2, apr_file_perms_set always returns APR_ENOTIMPL, 997251881Speter and the path passed to apr_file_perms_set must be encoded 998251881Speter in the platform-specific path encoding; not necessary UTF-8. 999251881Speter We need a platform-specific implementation to get the 1000251881Speter permissions right. */ 1001251881Speter 1002251881Speter#if !defined(WIN32) && !defined(__OS2__) 1003251881Speter { 1004251881Speter apr_finfo_t finfo; 1005251881Speter svn_node_kind_t kind; 1006251881Speter svn_boolean_t is_special; 1007251881Speter svn_error_t *err; 1008251881Speter 1009251881Speter /* If DST is a symlink, don't bother copying permissions. */ 1010251881Speter SVN_ERR(svn_io_check_special_path(dst, &kind, &is_special, pool)); 1011251881Speter if (is_special) 1012251881Speter return SVN_NO_ERROR; 1013251881Speter 1014251881Speter SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_PROT, pool)); 1015251881Speter err = file_perms_set(dst, finfo.protection, pool); 1016251881Speter if (err) 1017251881Speter { 1018251881Speter /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL 1019251881Speter here under normal circumstances, because the perms themselves 1020251881Speter came from a call to apr_file_info_get(), and we already know 1021251881Speter this is the non-Win32 case. But if it does happen, it's not 1022251881Speter an error. */ 1023251881Speter if (APR_STATUS_IS_INCOMPLETE(err->apr_err) || 1024251881Speter APR_STATUS_IS_ENOTIMPL(err->apr_err)) 1025251881Speter svn_error_clear(err); 1026251881Speter else 1027251881Speter { 1028299742Sdim return svn_error_quick_wrapf( 1029299742Sdim err, _("Can't set permissions on '%s'"), 1030299742Sdim svn_dirent_local_style(dst, pool)); 1031251881Speter } 1032251881Speter } 1033251881Speter } 1034251881Speter#endif /* !WIN32 && !__OS2__ */ 1035251881Speter 1036251881Speter return SVN_NO_ERROR; 1037251881Speter} 1038251881Speter 1039251881Speter 1040251881Spetersvn_error_t * 1041251881Spetersvn_io_append_file(const char *src, const char *dst, apr_pool_t *pool) 1042251881Speter{ 1043251881Speter apr_status_t apr_err; 1044251881Speter const char *src_apr, *dst_apr; 1045251881Speter 1046251881Speter SVN_ERR(cstring_from_utf8(&src_apr, src, pool)); 1047251881Speter SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool)); 1048251881Speter 1049251881Speter apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool); 1050251881Speter 1051251881Speter if (apr_err) 1052251881Speter return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"), 1053251881Speter svn_dirent_local_style(src, pool), 1054251881Speter svn_dirent_local_style(dst, pool)); 1055251881Speter 1056251881Speter return SVN_NO_ERROR; 1057251881Speter} 1058251881Speter 1059251881Speter 1060251881Spetersvn_error_t *svn_io_copy_dir_recursively(const char *src, 1061251881Speter const char *dst_parent, 1062251881Speter const char *dst_basename, 1063251881Speter svn_boolean_t copy_perms, 1064251881Speter svn_cancel_func_t cancel_func, 1065251881Speter void *cancel_baton, 1066251881Speter apr_pool_t *pool) 1067251881Speter{ 1068251881Speter svn_node_kind_t kind; 1069251881Speter apr_status_t status; 1070251881Speter const char *dst_path; 1071251881Speter apr_dir_t *this_dir; 1072251881Speter apr_finfo_t this_entry; 1073251881Speter apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; 1074251881Speter 1075251881Speter /* Make a subpool for recursion */ 1076251881Speter apr_pool_t *subpool = svn_pool_create(pool); 1077251881Speter 1078251881Speter /* The 'dst_path' is simply dst_parent/dst_basename */ 1079251881Speter dst_path = svn_dirent_join(dst_parent, dst_basename, pool); 1080251881Speter 1081251881Speter /* Sanity checks: SRC and DST_PARENT are directories, and 1082251881Speter DST_BASENAME doesn't already exist in DST_PARENT. */ 1083251881Speter SVN_ERR(svn_io_check_path(src, &kind, subpool)); 1084251881Speter if (kind != svn_node_dir) 1085251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 1086251881Speter _("Source '%s' is not a directory"), 1087251881Speter svn_dirent_local_style(src, pool)); 1088251881Speter 1089251881Speter SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool)); 1090251881Speter if (kind != svn_node_dir) 1091251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 1092251881Speter _("Destination '%s' is not a directory"), 1093251881Speter svn_dirent_local_style(dst_parent, pool)); 1094251881Speter 1095251881Speter SVN_ERR(svn_io_check_path(dst_path, &kind, subpool)); 1096251881Speter if (kind != svn_node_none) 1097251881Speter return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, 1098251881Speter _("Destination '%s' already exists"), 1099251881Speter svn_dirent_local_style(dst_path, pool)); 1100251881Speter 1101251881Speter /* Create the new directory. */ 1102251881Speter /* ### TODO: copy permissions (needs apr_file_attrs_get()) */ 1103251881Speter SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool)); 1104251881Speter 1105251881Speter /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */ 1106251881Speter SVN_ERR(svn_io_dir_open(&this_dir, src, subpool)); 1107251881Speter 1108251881Speter for (status = apr_dir_read(&this_entry, flags, this_dir); 1109251881Speter status == APR_SUCCESS; 1110251881Speter status = apr_dir_read(&this_entry, flags, this_dir)) 1111251881Speter { 1112251881Speter if ((this_entry.name[0] == '.') 1113251881Speter && ((this_entry.name[1] == '\0') 1114251881Speter || ((this_entry.name[1] == '.') 1115251881Speter && (this_entry.name[2] == '\0')))) 1116251881Speter { 1117251881Speter continue; 1118251881Speter } 1119251881Speter else 1120251881Speter { 1121251881Speter const char *src_target, *entryname_utf8; 1122251881Speter 1123251881Speter if (cancel_func) 1124251881Speter SVN_ERR(cancel_func(cancel_baton)); 1125251881Speter 1126251881Speter SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name, 1127251881Speter src, subpool)); 1128251881Speter src_target = svn_dirent_join(src, entryname_utf8, subpool); 1129251881Speter 1130251881Speter if (this_entry.filetype == APR_REG) /* regular file */ 1131251881Speter { 1132251881Speter const char *dst_target = svn_dirent_join(dst_path, 1133251881Speter entryname_utf8, 1134251881Speter subpool); 1135251881Speter SVN_ERR(svn_io_copy_file(src_target, dst_target, 1136251881Speter copy_perms, subpool)); 1137251881Speter } 1138251881Speter else if (this_entry.filetype == APR_LNK) /* symlink */ 1139251881Speter { 1140251881Speter const char *dst_target = svn_dirent_join(dst_path, 1141251881Speter entryname_utf8, 1142251881Speter subpool); 1143251881Speter SVN_ERR(svn_io_copy_link(src_target, dst_target, 1144251881Speter subpool)); 1145251881Speter } 1146251881Speter else if (this_entry.filetype == APR_DIR) /* recurse */ 1147251881Speter { 1148251881Speter /* Prevent infinite recursion by filtering off our 1149251881Speter newly created destination path. */ 1150251881Speter if (strcmp(src, dst_parent) == 0 1151251881Speter && strcmp(entryname_utf8, dst_basename) == 0) 1152251881Speter continue; 1153251881Speter 1154251881Speter SVN_ERR(svn_io_copy_dir_recursively 1155251881Speter (src_target, 1156251881Speter dst_path, 1157251881Speter entryname_utf8, 1158251881Speter copy_perms, 1159251881Speter cancel_func, 1160251881Speter cancel_baton, 1161251881Speter subpool)); 1162251881Speter } 1163251881Speter /* ### support other APR node types someday?? */ 1164251881Speter 1165251881Speter } 1166251881Speter } 1167251881Speter 1168251881Speter if (! (APR_STATUS_IS_ENOENT(status))) 1169251881Speter return svn_error_wrap_apr(status, _("Can't read directory '%s'"), 1170251881Speter svn_dirent_local_style(src, pool)); 1171251881Speter 1172251881Speter status = apr_dir_close(this_dir); 1173251881Speter if (status) 1174251881Speter return svn_error_wrap_apr(status, _("Error closing directory '%s'"), 1175251881Speter svn_dirent_local_style(src, pool)); 1176251881Speter 1177251881Speter /* Free any memory used by recursion */ 1178251881Speter svn_pool_destroy(subpool); 1179251881Speter 1180251881Speter return SVN_NO_ERROR; 1181251881Speter} 1182251881Speter 1183251881Speter 1184251881Spetersvn_error_t * 1185251881Spetersvn_io_make_dir_recursively(const char *path, apr_pool_t *pool) 1186251881Speter{ 1187251881Speter const char *path_apr; 1188251881Speter apr_status_t apr_err; 1189251881Speter 1190251881Speter if (svn_path_is_empty(path)) 1191251881Speter /* Empty path (current dir) is assumed to always exist, 1192251881Speter so we do nothing, per docs. */ 1193251881Speter return SVN_NO_ERROR; 1194251881Speter 1195251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1196251881Speter 1197251881Speter apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool); 1198299742Sdim#ifdef WIN32 1199299742Sdim /* Don't retry on ERROR_ACCESS_DENIED, as that typically signals a 1200299742Sdim permanent error */ 1201299742Sdim if (apr_err == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION)) 1202299742Sdim WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr, 1203299742Sdim APR_OS_DEFAULT, pool)); 1204299742Sdim#endif 1205251881Speter 1206251881Speter if (apr_err) 1207251881Speter return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"), 1208251881Speter svn_dirent_local_style(path, pool)); 1209251881Speter 1210251881Speter return SVN_NO_ERROR; 1211251881Speter} 1212251881Speter 1213299742Sdimsvn_error_t * 1214299742Sdimsvn_io_file_create_bytes(const char *file, 1215299742Sdim const void *contents, 1216299742Sdim apr_size_t length, 1217299742Sdim apr_pool_t *scratch_pool) 1218251881Speter{ 1219251881Speter apr_file_t *f; 1220251881Speter apr_size_t written; 1221251881Speter svn_error_t *err = SVN_NO_ERROR; 1222251881Speter 1223251881Speter SVN_ERR(svn_io_file_open(&f, file, 1224251881Speter (APR_WRITE | APR_CREATE | APR_EXCL), 1225251881Speter APR_OS_DEFAULT, 1226299742Sdim scratch_pool)); 1227299742Sdim if (length) 1228299742Sdim err = svn_io_file_write_full(f, contents, length, &written, 1229299742Sdim scratch_pool); 1230251881Speter 1231299742Sdim err = svn_error_compose_create( 1232299742Sdim err, 1233299742Sdim svn_io_file_close(f, scratch_pool)); 1234251881Speter 1235299742Sdim if (err) 1236299742Sdim { 1237299742Sdim /* Our caller doesn't know if we left a file or not if we return 1238299742Sdim an error. Better to cleanup after ourselves if we created the 1239299742Sdim file. */ 1240299742Sdim return svn_error_trace( 1241299742Sdim svn_error_compose_create( 1242299742Sdim err, 1243299742Sdim svn_io_remove_file2(file, TRUE, scratch_pool))); 1244299742Sdim } 1245299742Sdim 1246299742Sdim return SVN_NO_ERROR; 1247251881Speter} 1248251881Speter 1249299742Sdimsvn_error_t * 1250299742Sdimsvn_io_file_create(const char *file, 1251299742Sdim const char *contents, 1252299742Sdim apr_pool_t *pool) 1253251881Speter{ 1254299742Sdim return svn_error_trace(svn_io_file_create_bytes(file, contents, 1255299742Sdim contents ? strlen(contents) 1256299742Sdim : 0, 1257299742Sdim pool)); 1258299742Sdim} 1259299742Sdim 1260299742Sdimsvn_error_t * 1261299742Sdimsvn_io_file_create_empty(const char *file, 1262299742Sdim apr_pool_t *scratch_pool) 1263299742Sdim{ 1264299742Sdim return svn_error_trace(svn_io_file_create_bytes(file, NULL, 0, 1265299742Sdim scratch_pool)); 1266299742Sdim} 1267299742Sdim 1268299742Sdimsvn_error_t * 1269299742Sdimsvn_io_dir_file_copy(const char *src_path, 1270299742Sdim const char *dest_path, 1271299742Sdim const char *file, 1272299742Sdim apr_pool_t *pool) 1273299742Sdim{ 1274251881Speter const char *file_dest_path = svn_dirent_join(dest_path, file, pool); 1275251881Speter const char *file_src_path = svn_dirent_join(src_path, file, pool); 1276251881Speter 1277299742Sdim return svn_error_trace( 1278299742Sdim svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool)); 1279251881Speter} 1280251881Speter 1281251881Speter 1282251881Speter/*** Modtime checking. ***/ 1283251881Speter 1284251881Spetersvn_error_t * 1285251881Spetersvn_io_file_affected_time(apr_time_t *apr_time, 1286251881Speter const char *path, 1287251881Speter apr_pool_t *pool) 1288251881Speter{ 1289251881Speter apr_finfo_t finfo; 1290251881Speter 1291251881Speter SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool)); 1292251881Speter 1293251881Speter *apr_time = finfo.mtime; 1294251881Speter 1295251881Speter return SVN_NO_ERROR; 1296251881Speter} 1297251881Speter 1298251881Speter 1299251881Spetersvn_error_t * 1300251881Spetersvn_io_set_file_affected_time(apr_time_t apr_time, 1301251881Speter const char *path, 1302251881Speter apr_pool_t *pool) 1303251881Speter{ 1304251881Speter apr_status_t status; 1305251881Speter const char *native_path; 1306251881Speter 1307251881Speter SVN_ERR(cstring_from_utf8(&native_path, path, pool)); 1308251881Speter status = apr_file_mtime_set(native_path, apr_time, pool); 1309251881Speter 1310251881Speter if (status) 1311251881Speter return svn_error_wrap_apr(status, _("Can't set access time of '%s'"), 1312251881Speter svn_dirent_local_style(path, pool)); 1313251881Speter 1314251881Speter return SVN_NO_ERROR; 1315251881Speter} 1316251881Speter 1317251881Speter 1318251881Spetervoid 1319251881Spetersvn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool) 1320251881Speter{ 1321251881Speter apr_time_t now, then; 1322251881Speter svn_error_t *err; 1323251881Speter char *sleep_env_var; 1324251881Speter 1325251881Speter sleep_env_var = getenv(SVN_SLEEP_ENV_VAR); 1326251881Speter 1327251881Speter if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0) 1328251881Speter return; /* Allow skipping for testing */ 1329251881Speter 1330251881Speter now = apr_time_now(); 1331251881Speter 1332251881Speter /* Calculate 0.02 seconds after the next second wallclock tick. */ 1333251881Speter then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50); 1334251881Speter 1335251881Speter /* Worst case is waiting one second, so we can use that time to determine 1336251881Speter if we can sleep shorter than that */ 1337251881Speter if (path) 1338251881Speter { 1339251881Speter apr_finfo_t finfo; 1340251881Speter 1341251881Speter err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool); 1342251881Speter 1343251881Speter if (err) 1344251881Speter { 1345251881Speter svn_error_clear(err); /* Fall back on original behavior */ 1346251881Speter } 1347251881Speter else if (finfo.mtime % APR_USEC_PER_SEC) 1348251881Speter { 1349251881Speter /* Very simplistic but safe approach: 1350251881Speter If the filesystem has < sec mtime we can be reasonably sure 1351269847Speter that the filesystem has some sub-second resolution. On Windows 1352269847Speter it is likely to be sub-millisecond; on Linux systems it depends 1353269847Speter on the filesystem, ext4 is typically 1ms, 4ms or 10ms resolution. 1354251881Speter 1355251881Speter ## Perhaps find a better algorithm here. This will fail once 1356269847Speter in every 1000 cases on a millisecond precision filesystem 1357269847Speter if the mtime happens to be an exact second. 1358251881Speter 1359251881Speter But better to fail once in every thousand cases than every 1360251881Speter time, like we did before. 1361251881Speter 1362251881Speter Note for further research on algorithm: 1363269847Speter FAT32 has < 1 sec precision on ctime, but 2 sec on mtime. 1364251881Speter 1365269847Speter Linux/ext4 with CONFIG_HZ=250 has high resolution 1366269847Speter apr_time_now and although the filesystem timestamps 1367269847Speter have similar high precision they are only updated with 1368269847Speter a coarser 4ms resolution. */ 1369251881Speter 1370269847Speter /* 10 milliseconds after now. */ 1371269847Speter#ifndef SVN_HI_RES_SLEEP_MS 1372269847Speter#define SVN_HI_RES_SLEEP_MS 10 1373269847Speter#endif 1374269847Speter then = now + apr_time_from_msec(SVN_HI_RES_SLEEP_MS); 1375251881Speter } 1376251881Speter 1377269847Speter /* Remove time taken to do stat() from sleep. */ 1378269847Speter now = apr_time_now(); 1379251881Speter } 1380251881Speter 1381269847Speter if (now >= then) 1382269847Speter return; /* Passing negative values may suspend indefinitely (Windows) */ 1383269847Speter 1384269847Speter /* (t < 1000 will be round to 0 in apr) */ 1385269847Speter if (then - now < 1000) 1386269847Speter apr_sleep(1000); 1387269847Speter else 1388269847Speter apr_sleep(then - now); 1389251881Speter} 1390251881Speter 1391251881Speter 1392251881Spetersvn_error_t * 1393251881Spetersvn_io_filesizes_different_p(svn_boolean_t *different_p, 1394251881Speter const char *file1, 1395251881Speter const char *file2, 1396251881Speter apr_pool_t *pool) 1397251881Speter{ 1398251881Speter apr_finfo_t finfo1; 1399251881Speter apr_finfo_t finfo2; 1400251881Speter apr_status_t status; 1401251881Speter const char *file1_apr, *file2_apr; 1402251881Speter 1403251881Speter /* Not using svn_io_stat() because don't want to generate 1404251881Speter svn_error_t objects for non-error conditions. */ 1405251881Speter 1406251881Speter SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool)); 1407251881Speter SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool)); 1408251881Speter 1409251881Speter /* Stat both files */ 1410251881Speter status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool); 1411251881Speter if (status) 1412251881Speter { 1413251881Speter /* If we got an error stat'ing a file, it could be because the 1414251881Speter file was removed... or who knows. Whatever the case, we 1415251881Speter don't know if the filesizes are definitely different, so 1416251881Speter assume that they're not. */ 1417251881Speter *different_p = FALSE; 1418251881Speter return SVN_NO_ERROR; 1419251881Speter } 1420251881Speter 1421251881Speter status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool); 1422251881Speter if (status) 1423251881Speter { 1424251881Speter /* See previous comment. */ 1425251881Speter *different_p = FALSE; 1426251881Speter return SVN_NO_ERROR; 1427251881Speter } 1428251881Speter 1429251881Speter /* Examine file sizes */ 1430251881Speter if (finfo1.size == finfo2.size) 1431251881Speter *different_p = FALSE; 1432251881Speter else 1433251881Speter *different_p = TRUE; 1434251881Speter 1435251881Speter return SVN_NO_ERROR; 1436251881Speter} 1437251881Speter 1438251881Speter 1439251881Spetersvn_error_t * 1440251881Spetersvn_io_filesizes_three_different_p(svn_boolean_t *different_p12, 1441251881Speter svn_boolean_t *different_p23, 1442251881Speter svn_boolean_t *different_p13, 1443251881Speter const char *file1, 1444251881Speter const char *file2, 1445251881Speter const char *file3, 1446251881Speter apr_pool_t *scratch_pool) 1447251881Speter{ 1448251881Speter apr_finfo_t finfo1, finfo2, finfo3; 1449251881Speter apr_status_t status1, status2, status3; 1450251881Speter const char *file1_apr, *file2_apr, *file3_apr; 1451251881Speter 1452251881Speter /* Not using svn_io_stat() because don't want to generate 1453251881Speter svn_error_t objects for non-error conditions. */ 1454251881Speter 1455251881Speter SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool)); 1456251881Speter SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool)); 1457251881Speter SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool)); 1458251881Speter 1459251881Speter /* Stat all three files */ 1460251881Speter status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool); 1461251881Speter status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool); 1462251881Speter status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool); 1463251881Speter 1464251881Speter /* If we got an error stat'ing a file, it could be because the 1465251881Speter file was removed... or who knows. Whatever the case, we 1466251881Speter don't know if the filesizes are definitely different, so 1467251881Speter assume that they're not. */ 1468251881Speter *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size; 1469251881Speter *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size; 1470251881Speter *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size; 1471251881Speter 1472251881Speter return SVN_NO_ERROR; 1473251881Speter} 1474251881Speter 1475251881Speter 1476251881Spetersvn_error_t * 1477251881Spetersvn_io_file_checksum2(svn_checksum_t **checksum, 1478251881Speter const char *file, 1479251881Speter svn_checksum_kind_t kind, 1480251881Speter apr_pool_t *pool) 1481251881Speter{ 1482251881Speter svn_stream_t *file_stream; 1483251881Speter svn_stream_t *checksum_stream; 1484251881Speter apr_file_t* f; 1485251881Speter 1486251881Speter SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool)); 1487251881Speter file_stream = svn_stream_from_aprfile2(f, FALSE, pool); 1488251881Speter checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind, 1489251881Speter TRUE, pool); 1490251881Speter 1491251881Speter /* Because the checksummed stream will force the reading (and 1492251881Speter checksumming) of all the file's bytes, we can just close the stream 1493251881Speter and let its magic work. */ 1494251881Speter return svn_stream_close(checksum_stream); 1495251881Speter} 1496251881Speter 1497251881Speter 1498251881Spetersvn_error_t * 1499251881Spetersvn_io_file_checksum(unsigned char digest[], 1500251881Speter const char *file, 1501251881Speter apr_pool_t *pool) 1502251881Speter{ 1503251881Speter svn_checksum_t *checksum; 1504251881Speter 1505251881Speter SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool)); 1506251881Speter memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE); 1507251881Speter 1508251881Speter return SVN_NO_ERROR; 1509251881Speter} 1510251881Speter 1511251881Speter 1512251881Speter 1513251881Speter/*** Permissions and modes. ***/ 1514251881Speter 1515251881Speter#if !defined(WIN32) && !defined(__OS2__) 1516251881Speter/* Given the file specified by PATH, attempt to create an 1517251881Speter identical version of it owned by the current user. This is done by 1518251881Speter moving it to a temporary location, copying the file back to its old 1519251881Speter path, then deleting the temporarily moved version. All temporary 1520251881Speter allocations are done in POOL. */ 1521251881Speterstatic svn_error_t * 1522251881Speterreown_file(const char *path, 1523251881Speter apr_pool_t *pool) 1524251881Speter{ 1525251881Speter const char *unique_name; 1526251881Speter 1527251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name, 1528251881Speter svn_dirent_dirname(path, pool), 1529251881Speter svn_io_file_del_none, pool, pool)); 1530251881Speter SVN_ERR(svn_io_file_rename(path, unique_name, pool)); 1531251881Speter SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool)); 1532251881Speter return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool)); 1533251881Speter} 1534251881Speter 1535251881Speter/* Determine what the PERMS for a new file should be by looking at the 1536251881Speter permissions of a temporary file that we create. 1537251881Speter Unfortunately, umask() as defined in POSIX provides no thread-safe way 1538251881Speter to get at the current value of the umask, so what we're doing here is 1539251881Speter the only way we have to determine which combination of write bits 1540251881Speter (User/Group/World) should be set by default. 1541251881Speter Make temporary allocations in SCRATCH_POOL. */ 1542251881Speterstatic svn_error_t * 1543251881Speterget_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool) 1544251881Speter{ 1545251881Speter /* the default permissions as read from the temp folder */ 1546251881Speter static apr_fileperms_t default_perms = 0; 1547251881Speter 1548251881Speter /* Technically, this "racy": Multiple threads may use enter here and 1549251881Speter try to figure out the default permission concurrently. That's fine 1550251881Speter since they will end up with the same results. Even more technical, 1551251881Speter apr_fileperms_t is an atomic type on 32+ bit machines. 1552251881Speter */ 1553251881Speter if (default_perms == 0) 1554251881Speter { 1555251881Speter apr_finfo_t finfo; 1556251881Speter apr_file_t *fd; 1557251881Speter const char *fname_base, *fname; 1558251881Speter apr_uint32_t randomish; 1559251881Speter svn_error_t *err; 1560251881Speter 1561251881Speter /* Get the perms for a newly created file to find out what bits 1562251881Speter should be set. 1563251881Speter 1564299742Sdim Explicitly delete the file because we want this file to be as 1565251881Speter short-lived as possible since its presence means other 1566251881Speter processes may have to try multiple names. 1567251881Speter 1568251881Speter Using svn_io_open_uniquely_named() here because other tempfile 1569251881Speter creation functions tweak the permission bits of files they create. 1570251881Speter */ 1571251881Speter randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool 1572251881Speter + (apr_uint32_t)apr_time_now()); 1573251881Speter fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish); 1574251881Speter 1575251881Speter SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base, 1576251881Speter NULL, svn_io_file_del_none, 1577251881Speter scratch_pool, scratch_pool)); 1578251881Speter err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool); 1579251881Speter err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool)); 1580251881Speter err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE, 1581251881Speter scratch_pool)); 1582251881Speter SVN_ERR(err); 1583251881Speter *perms = finfo.protection; 1584251881Speter default_perms = finfo.protection; 1585251881Speter } 1586251881Speter else 1587251881Speter *perms = default_perms; 1588251881Speter 1589251881Speter return SVN_NO_ERROR; 1590251881Speter} 1591251881Speter 1592251881Speter/* OR together permission bits of the file FD and the default permissions 1593251881Speter of a file as determined by get_default_file_perms(). Do temporary 1594251881Speter allocations in SCRATCH_POOL. */ 1595251881Speterstatic svn_error_t * 1596251881Spetermerge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms, 1597251881Speter apr_pool_t *scratch_pool) 1598251881Speter{ 1599251881Speter apr_finfo_t finfo; 1600251881Speter apr_fileperms_t default_perms; 1601251881Speter 1602251881Speter SVN_ERR(get_default_file_perms(&default_perms, scratch_pool)); 1603251881Speter SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool)); 1604251881Speter 1605251881Speter /* Glom the perms together. */ 1606251881Speter *perms = default_perms | finfo.protection; 1607251881Speter return SVN_NO_ERROR; 1608251881Speter} 1609251881Speter 1610251881Speter/* This is a helper function for the svn_io_set_file_read* functions 1611251881Speter that attempts to honor the users umask when dealing with 1612251881Speter permission changes. It is a no-op when invoked on a symlink. */ 1613251881Speterstatic svn_error_t * 1614251881Speterio_set_file_perms(const char *path, 1615251881Speter svn_boolean_t change_readwrite, 1616251881Speter svn_boolean_t enable_write, 1617251881Speter svn_boolean_t change_executable, 1618251881Speter svn_boolean_t executable, 1619251881Speter svn_boolean_t ignore_enoent, 1620251881Speter apr_pool_t *pool) 1621251881Speter{ 1622251881Speter apr_status_t status; 1623251881Speter const char *path_apr; 1624251881Speter apr_finfo_t finfo; 1625251881Speter apr_fileperms_t perms_to_set; 1626251881Speter 1627251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 1628251881Speter 1629251881Speter /* Try to change only a minimal amount of the perms first 1630251881Speter by getting the current perms and adding bits 1631251881Speter only on where read perms are granted. If this fails 1632251881Speter fall through to just setting file attributes. */ 1633251881Speter status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool); 1634251881Speter if (status) 1635251881Speter { 1636299742Sdim if (ignore_enoent && (APR_STATUS_IS_ENOENT(status) 1637299742Sdim || SVN__APR_STATUS_IS_ENOTDIR(status))) 1638251881Speter return SVN_NO_ERROR; 1639251881Speter else if (status != APR_ENOTIMPL) 1640251881Speter return svn_error_wrap_apr(status, 1641251881Speter _("Can't change perms of file '%s'"), 1642251881Speter svn_dirent_local_style(path, pool)); 1643251881Speter return SVN_NO_ERROR; 1644251881Speter } 1645251881Speter 1646251881Speter if (finfo.filetype == APR_LNK) 1647251881Speter return SVN_NO_ERROR; 1648251881Speter 1649251881Speter perms_to_set = finfo.protection; 1650251881Speter if (change_readwrite) 1651251881Speter { 1652251881Speter if (enable_write) /* Make read-write. */ 1653251881Speter { 1654262253Speter /* Tweak the owner bits only. The group/other bits aren't safe to 1655262253Speter * touch because we may end up setting them in undesired ways. */ 1656262253Speter perms_to_set |= (APR_UREAD|APR_UWRITE); 1657251881Speter } 1658251881Speter else 1659251881Speter { 1660251881Speter if (finfo.protection & APR_UREAD) 1661251881Speter perms_to_set &= ~APR_UWRITE; 1662251881Speter if (finfo.protection & APR_GREAD) 1663251881Speter perms_to_set &= ~APR_GWRITE; 1664251881Speter if (finfo.protection & APR_WREAD) 1665251881Speter perms_to_set &= ~APR_WWRITE; 1666251881Speter } 1667251881Speter } 1668251881Speter 1669251881Speter if (change_executable) 1670251881Speter { 1671251881Speter if (executable) 1672251881Speter { 1673251881Speter if (finfo.protection & APR_UREAD) 1674251881Speter perms_to_set |= APR_UEXECUTE; 1675251881Speter if (finfo.protection & APR_GREAD) 1676251881Speter perms_to_set |= APR_GEXECUTE; 1677251881Speter if (finfo.protection & APR_WREAD) 1678251881Speter perms_to_set |= APR_WEXECUTE; 1679251881Speter } 1680251881Speter else 1681251881Speter { 1682251881Speter if (finfo.protection & APR_UREAD) 1683251881Speter perms_to_set &= ~APR_UEXECUTE; 1684251881Speter if (finfo.protection & APR_GREAD) 1685251881Speter perms_to_set &= ~APR_GEXECUTE; 1686251881Speter if (finfo.protection & APR_WREAD) 1687251881Speter perms_to_set &= ~APR_WEXECUTE; 1688251881Speter } 1689251881Speter } 1690251881Speter 1691251881Speter /* If we aren't changing anything then just return, this saves 1692251881Speter some system calls and helps with shared working copies */ 1693251881Speter if (perms_to_set == finfo.protection) 1694251881Speter return SVN_NO_ERROR; 1695251881Speter 1696251881Speter status = apr_file_perms_set(path_apr, perms_to_set); 1697251881Speter if (!status) 1698251881Speter return SVN_NO_ERROR; 1699251881Speter 1700251881Speter if (APR_STATUS_IS_EPERM(status)) 1701251881Speter { 1702251881Speter /* We don't have permissions to change the 1703251881Speter permissions! Try a move, copy, and delete 1704251881Speter workaround to see if we can get the file owned by 1705251881Speter us. If these succeed, try the permissions set 1706251881Speter again. 1707251881Speter 1708251881Speter Note that we only attempt this in the 1709251881Speter stat-available path. This assumes that the 1710251881Speter move-copy workaround will only be helpful on 1711251881Speter platforms that implement apr_stat. */ 1712251881Speter SVN_ERR(reown_file(path, pool)); 1713251881Speter status = apr_file_perms_set(path_apr, perms_to_set); 1714251881Speter } 1715251881Speter 1716251881Speter if (!status) 1717251881Speter return SVN_NO_ERROR; 1718251881Speter 1719251881Speter if (ignore_enoent && APR_STATUS_IS_ENOENT(status)) 1720251881Speter return SVN_NO_ERROR; 1721251881Speter else if (status == APR_ENOTIMPL) 1722251881Speter { 1723251881Speter /* At least try to set the attributes. */ 1724251881Speter apr_fileattrs_t attrs = 0; 1725251881Speter apr_fileattrs_t attrs_values = 0; 1726251881Speter 1727251881Speter if (change_readwrite) 1728251881Speter { 1729251881Speter attrs = APR_FILE_ATTR_READONLY; 1730251881Speter if (!enable_write) 1731251881Speter attrs_values = APR_FILE_ATTR_READONLY; 1732251881Speter } 1733251881Speter if (change_executable) 1734251881Speter { 1735251881Speter attrs = APR_FILE_ATTR_EXECUTABLE; 1736251881Speter if (executable) 1737251881Speter attrs_values = APR_FILE_ATTR_EXECUTABLE; 1738251881Speter } 1739251881Speter status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool); 1740251881Speter } 1741251881Speter 1742251881Speter return svn_error_wrap_apr(status, 1743251881Speter _("Can't change perms of file '%s'"), 1744251881Speter svn_dirent_local_style(path, pool)); 1745251881Speter} 1746251881Speter#endif /* !WIN32 && !__OS2__ */ 1747251881Speter 1748251881Speter#ifdef WIN32 1749299742Sdim/* This is semantically the same as the APR utf8_to_unicode_path 1750299742Sdim function, but reimplemented here because APR does not export it. */ 1751299742Sdimsvn_error_t* 1752299742Sdimsvn_io__utf8_to_unicode_longpath(const WCHAR **result, 1753299742Sdim const char *source, 1754299742Sdim apr_pool_t *result_pool) 1755251881Speter{ 1756251881Speter /* This is correct, we don't twist the filename if it will 1757251881Speter * definitely be shorter than 248 characters. It merits some 1758251881Speter * performance testing to see if this has any effect, but there 1759251881Speter * seem to be applications that get confused by the resulting 1760251881Speter * Unicode \\?\ style file names, especially if they use argv[0] 1761251881Speter * or call the Win32 API functions such as GetModuleName, etc. 1762251881Speter * Not every application is prepared to handle such names. 1763251881Speter * 1764251881Speter * Note also this is shorter than MAX_PATH, as directory paths 1765251881Speter * are actually limited to 248 characters. 1766251881Speter * 1767251881Speter * Note that a utf-8 name can never result in more wide chars 1768251881Speter * than the original number of utf-8 narrow chars. 1769251881Speter */ 1770299742Sdim const WCHAR *prefix = NULL; 1771299742Sdim const int srclen = strlen(source); 1772299742Sdim WCHAR *buffer; 1773299742Sdim 1774299742Sdim if (srclen > 248) 1775299742Sdim { 1776299742Sdim if (svn_ctype_isalpha(source[0]) && source[1] == ':' 1777299742Sdim && (source[2] == '/' || source[2] == '\\')) 1778299742Sdim { 1779299742Sdim /* This is an ordinary absolute path. */ 1780299742Sdim prefix = L"\\\\?\\"; 1781251881Speter } 1782299742Sdim else if ((source[0] == '/' || source[0] == '\\') 1783299742Sdim && (source[1] == '/' || source[1] == '\\') 1784299742Sdim && source[2] != '?') 1785299742Sdim { 1786299742Sdim /* This is a UNC path */ 1787299742Sdim source += 2; /* Skip the leading slashes */ 1788299742Sdim prefix = L"\\\\?\\UNC\\"; 1789251881Speter } 1790251881Speter } 1791251881Speter 1792299742Sdim SVN_ERR(svn_utf__win32_utf8_to_utf16(&(const WCHAR*)buffer, source, 1793299742Sdim prefix, result_pool)); 1794299742Sdim 1795299742Sdim /* Convert slashes to backslashes because the \\?\ path format 1796299742Sdim does not allow backslashes as path separators. */ 1797299742Sdim *result = buffer; 1798299742Sdim for (; *buffer; ++buffer) 1799299742Sdim { 1800299742Sdim if (*buffer == '/') 1801299742Sdim *buffer = '\\'; 1802251881Speter } 1803299742Sdim return SVN_NO_ERROR; 1804299742Sdim} 1805299742Sdim 1806299742Sdim/* This is semantically the same as the APR unicode_to_utf8_path 1807299742Sdim function, but reimplemented here because APR does not export it. */ 1808299742Sdimstatic svn_error_t * 1809299742Sdimio_unicode_to_utf8_path(const char **result, 1810299742Sdim const WCHAR *source, 1811299742Sdim apr_pool_t *result_pool) 1812299742Sdim{ 1813299742Sdim const char *utf8_buffer; 1814299742Sdim char *buffer; 1815299742Sdim 1816299742Sdim SVN_ERR(svn_utf__win32_utf16_to_utf8(&utf8_buffer, source, 1817299742Sdim NULL, result_pool)); 1818299742Sdim if (!*utf8_buffer) 1819299742Sdim { 1820299742Sdim *result = utf8_buffer; 1821299742Sdim return SVN_NO_ERROR; 1822299742Sdim } 1823299742Sdim 1824299742Sdim /* We know that the non-empty buffer returned from the UTF-16 to 1825299742Sdim UTF-8 conversion function is in fact writable. */ 1826299742Sdim buffer = (char*)utf8_buffer; 1827299742Sdim 1828299742Sdim /* Skip the leading 4 characters if the path begins \\?\, or substitute 1829299742Sdim * // for the \\?\UNC\ path prefix, allocating the maximum string 1830299742Sdim * length based on the remaining string, plus the trailing null. 1831299742Sdim * then transform \\'s back into /'s since the \\?\ form never 1832299742Sdim * allows '/' path separators, and APR always uses '/'s. 1833299742Sdim */ 1834299742Sdim if (0 == strncmp(buffer, "\\\\?\\", 4)) 1835299742Sdim { 1836299742Sdim buffer += 4; 1837299742Sdim if (0 == strncmp(buffer, "UNC\\", 4)) 1838299742Sdim { 1839299742Sdim buffer += 2; 1840299742Sdim *buffer = '/'; 1841299742Sdim } 1842251881Speter } 1843299742Sdim 1844299742Sdim *result = buffer; 1845299742Sdim for (; *buffer; ++buffer) 1846299742Sdim { 1847299742Sdim if (*buffer == '\\') 1848299742Sdim *buffer = '/'; 1849299742Sdim } 1850299742Sdim return SVN_NO_ERROR; 1851251881Speter} 1852251881Speter 1853299742Sdimstatic svn_error_t * 1854299742Sdimio_win_file_attrs_set(const char *fname, 1855299742Sdim DWORD attributes, 1856299742Sdim DWORD attr_mask, 1857299742Sdim apr_pool_t *pool) 1858251881Speter{ 1859251881Speter /* this is an implementation of apr_file_attrs_set() but one 1860251881Speter that uses the proper Windows attributes instead of the apr 1861251881Speter attributes. This way, we can apply any Windows file and 1862251881Speter folder attributes even if apr doesn't implement them */ 1863251881Speter DWORD flags; 1864299742Sdim const WCHAR *wfname; 1865251881Speter 1866299742Sdim SVN_ERR(svn_io__utf8_to_unicode_longpath(&wfname, fname, pool)); 1867299742Sdim 1868299742Sdim flags = GetFileAttributesW(wfname); 1869299742Sdim if (flags == 0xFFFFFFFF) 1870299742Sdim return svn_error_wrap_apr(apr_get_os_error(), 1871299742Sdim _("Can't get attributes of file '%s'"), 1872299742Sdim svn_dirent_local_style(fname, pool)); 1873299742Sdim 1874299742Sdim flags &= ~attr_mask; 1875299742Sdim flags |= (attributes & attr_mask); 1876299742Sdim 1877299742Sdim if (!SetFileAttributesW(wfname, flags)) 1878299742Sdim return svn_error_wrap_apr(apr_get_os_error(), 1879299742Sdim _("Can't set attributes of file '%s'"), 1880299742Sdim svn_dirent_local_style(fname, pool)); 1881299742Sdim 1882299742Sdim return SVN_NO_ERROR;; 1883299742Sdim} 1884299742Sdim 1885299742Sdimstatic svn_error_t *win_init_dynamic_imports(void *baton, apr_pool_t *pool) 1886299742Sdim{ 1887299742Sdim HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); 1888299742Sdim 1889299742Sdim if (kernel32) 1890251881Speter { 1891299742Sdim get_final_path_name_by_handle_proc = (GETFINALPATHNAMEBYHANDLE) 1892299742Sdim GetProcAddress(kernel32, "GetFinalPathNameByHandleW"); 1893299742Sdim 1894299742Sdim set_file_information_by_handle_proc = (SetFileInformationByHandle_t) 1895299742Sdim GetProcAddress(kernel32, "SetFileInformationByHandle"); 1896251881Speter } 1897299742Sdim 1898299742Sdim return SVN_NO_ERROR; 1899299742Sdim} 1900299742Sdim 1901299742Sdimstatic svn_error_t * io_win_read_link(svn_string_t **dest, 1902299742Sdim const char *path, 1903299742Sdim apr_pool_t *pool) 1904299742Sdim{ 1905299742Sdim SVN_ERR(svn_atomic__init_once(&win_dynamic_imports_state, 1906299742Sdim win_init_dynamic_imports, NULL, pool)); 1907299742Sdim 1908299742Sdim if (get_final_path_name_by_handle_proc) 1909299742Sdim { 1910299742Sdim DWORD rv; 1911299742Sdim apr_status_t status; 1912299742Sdim apr_file_t *file; 1913299742Sdim apr_os_file_t filehand; 1914299742Sdim WCHAR wdest[APR_PATH_MAX]; 1915299742Sdim const char *data; 1916299742Sdim 1917299742Sdim /* reserve one char for terminating zero. */ 1918299742Sdim DWORD wdest_len = sizeof(wdest)/sizeof(wdest[0]) - 1; 1919299742Sdim 1920299742Sdim status = apr_file_open(&file, path, APR_OPENINFO, APR_OS_DEFAULT, pool); 1921299742Sdim 1922299742Sdim if (status) 1923299742Sdim return svn_error_wrap_apr(status, 1924299742Sdim _("Can't read contents of link")); 1925299742Sdim 1926299742Sdim apr_os_file_get(&filehand, file); 1927299742Sdim 1928299742Sdim rv = get_final_path_name_by_handle_proc( 1929299742Sdim filehand, wdest, wdest_len, 1930299742Sdim FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); 1931299742Sdim 1932299742Sdim /* Save error code. */ 1933299742Sdim status = apr_get_os_error(); 1934299742Sdim 1935299742Sdim /* Close file/directory handle in any case. */ 1936299742Sdim apr_file_close(file); 1937299742Sdim 1938299742Sdim /* GetFinaPathNameByHandleW returns number of characters copied to 1939299742Sdim * output buffer. Returns zero on error. Returns required buffer size 1940299742Sdim * if supplied buffer is not enough. */ 1941299742Sdim if (rv > wdest_len || rv == 0) 1942299742Sdim { 1943299742Sdim return svn_error_wrap_apr(status, 1944299742Sdim _("Can't read contents of link")); 1945299742Sdim } 1946299742Sdim 1947299742Sdim /* GetFinaPathNameByHandleW doesn't add terminating NUL. */ 1948299742Sdim wdest[rv] = 0; 1949299742Sdim SVN_ERR(io_unicode_to_utf8_path(&data, wdest, pool)); 1950299742Sdim 1951299742Sdim /* The result is already in the correct pool, so avoid copying 1952299742Sdim it to create the string. */ 1953299742Sdim *dest = svn_string_create_empty(pool); 1954299742Sdim if (*data) 1955299742Sdim { 1956299742Sdim (*dest)->data = data; 1957299742Sdim (*dest)->len = strlen(data); 1958299742Sdim } 1959299742Sdim 1960299742Sdim return SVN_NO_ERROR; 1961299742Sdim } 1962299742Sdim else 1963299742Sdim { 1964299742Sdim return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 1965299742Sdim _("Symbolic links are not supported on this " 1966299742Sdim "platform")); 1967299742Sdim } 1968299742Sdim} 1969299742Sdim 1970299742Sdim/* Wrapper around Windows API function SetFileInformationByHandle() that 1971299742Sdim * returns APR status instead of boolean flag. */ 1972299742Sdimstatic apr_status_t 1973299742Sdimwin32_set_file_information_by_handle(HANDLE hFile, 1974299742Sdim int FileInformationClass, 1975299742Sdim LPVOID lpFileInformation, 1976299742Sdim DWORD dwBufferSize) 1977299742Sdim{ 1978299742Sdim svn_error_clear(svn_atomic__init_once(&win_dynamic_imports_state, 1979299742Sdim win_init_dynamic_imports, 1980299742Sdim NULL, NULL)); 1981299742Sdim 1982299742Sdim if (!set_file_information_by_handle_proc) 1983251881Speter { 1984299742Sdim return SVN_ERR_UNSUPPORTED_FEATURE; 1985251881Speter } 1986251881Speter 1987299742Sdim if (!set_file_information_by_handle_proc(hFile, FileInformationClass, 1988299742Sdim lpFileInformation, 1989299742Sdim dwBufferSize)) 1990299742Sdim { 1991299742Sdim return apr_get_os_error(); 1992299742Sdim } 1993251881Speter 1994299742Sdim return APR_SUCCESS; 1995299742Sdim} 1996251881Speter 1997299742Sdimsvn_error_t * 1998299742Sdimsvn_io__win_delete_file_on_close(apr_file_t *file, 1999299742Sdim const char *path, 2000299742Sdim apr_pool_t *pool) 2001299742Sdim{ 2002299742Sdim FILE_DISPOSITION_INFO disposition_info; 2003299742Sdim HANDLE hFile; 2004299742Sdim apr_status_t status; 2005299742Sdim 2006299742Sdim apr_os_file_get(&hFile, file); 2007299742Sdim 2008299742Sdim disposition_info.DeleteFile = TRUE; 2009299742Sdim 2010299742Sdim status = win32_set_file_information_by_handle(hFile, FileDispositionInfo, 2011299742Sdim &disposition_info, 2012299742Sdim sizeof(disposition_info)); 2013299742Sdim 2014299742Sdim if (status) 2015251881Speter { 2016299742Sdim return svn_error_wrap_apr(status, _("Can't remove file '%s'"), 2017299742Sdim svn_dirent_local_style(path, pool)); 2018251881Speter } 2019299742Sdim 2020299742Sdim return SVN_NO_ERROR; 2021299742Sdim} 2022299742Sdim 2023299742Sdimsvn_error_t * 2024299742Sdimsvn_io__win_rename_open_file(apr_file_t *file, 2025299742Sdim const char *from_path, 2026299742Sdim const char *to_path, 2027299742Sdim apr_pool_t *pool) 2028299742Sdim{ 2029299742Sdim WCHAR *w_final_abspath; 2030299742Sdim size_t path_len; 2031299742Sdim size_t rename_size; 2032299742Sdim FILE_RENAME_INFO *rename_info; 2033299742Sdim HANDLE hFile; 2034299742Sdim apr_status_t status; 2035299742Sdim 2036299742Sdim apr_os_file_get(&hFile, file); 2037299742Sdim 2038299742Sdim SVN_ERR(svn_io__utf8_to_unicode_longpath( 2039299742Sdim &w_final_abspath, svn_dirent_local_style(to_path,pool), 2040299742Sdim pool)); 2041299742Sdim 2042299742Sdim path_len = wcslen(w_final_abspath); 2043299742Sdim rename_size = sizeof(*rename_info) + sizeof(WCHAR) * path_len; 2044299742Sdim 2045299742Sdim /* The rename info struct doesn't need hacks for long paths, 2046299742Sdim so no ugly escaping calls here */ 2047299742Sdim rename_info = apr_pcalloc(pool, rename_size); 2048299742Sdim rename_info->ReplaceIfExists = TRUE; 2049299742Sdim rename_info->FileNameLength = path_len; 2050299742Sdim memcpy(rename_info->FileName, w_final_abspath, path_len * sizeof(WCHAR)); 2051299742Sdim 2052299742Sdim status = win32_set_file_information_by_handle(hFile, FileRenameInfo, 2053299742Sdim rename_info, 2054299742Sdim rename_size); 2055299742Sdim 2056299742Sdim if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status)) 2057251881Speter { 2058299742Sdim /* Set the destination file writable because Windows will not allow 2059299742Sdim us to rename when final_abspath is read-only. */ 2060299742Sdim SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool)); 2061299742Sdim 2062299742Sdim status = win32_set_file_information_by_handle(hFile, 2063299742Sdim FileRenameInfo, 2064299742Sdim rename_info, 2065299742Sdim rename_size); 2066299742Sdim } 2067299742Sdim 2068299742Sdim /* Windows returns Vista+ client accessing network share stored on Windows 2069299742Sdim Server 2003 returns ERROR_ACCESS_DENIED. The same happens when Vista+ 2070299742Sdim client access Windows Server 2008 with disabled SMBv2 protocol. 2071299742Sdim 2072299742Sdim So return SVN_ERR_UNSUPPORTED_FEATURE in this case like we do when 2073299742Sdim SetFileInformationByHandle() is not available and let caller to 2074299742Sdim handle it. 2075299742Sdim 2076299742Sdim See "Access denied error on checkout-commit after updating to 1.9.X" 2077299742Sdim discussion on dev@s.a.o: 2078299742Sdim http://svn.haxx.se/dev/archive-2015-09/0054.shtml */ 2079299742Sdim if (status == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED)) 2080299742Sdim { 2081299742Sdim status = SVN_ERR_UNSUPPORTED_FEATURE; 2082251881Speter } 2083251881Speter 2084299742Sdim if (status) 2085299742Sdim { 2086299742Sdim return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"), 2087299742Sdim svn_dirent_local_style(from_path, pool), 2088299742Sdim svn_dirent_local_style(to_path, pool)); 2089299742Sdim } 2090251881Speter 2091299742Sdim return SVN_NO_ERROR; 2092251881Speter} 2093251881Speter 2094299742Sdim#endif /* WIN32 */ 2095251881Speter 2096251881Spetersvn_error_t * 2097251881Spetersvn_io_set_file_read_write_carefully(const char *path, 2098251881Speter svn_boolean_t enable_write, 2099251881Speter svn_boolean_t ignore_enoent, 2100251881Speter apr_pool_t *pool) 2101251881Speter{ 2102251881Speter if (enable_write) 2103251881Speter return svn_io_set_file_read_write(path, ignore_enoent, pool); 2104251881Speter return svn_io_set_file_read_only(path, ignore_enoent, pool); 2105251881Speter} 2106251881Speter 2107251881Spetersvn_error_t * 2108251881Spetersvn_io_set_file_read_only(const char *path, 2109251881Speter svn_boolean_t ignore_enoent, 2110251881Speter apr_pool_t *pool) 2111251881Speter{ 2112251881Speter /* On Windows and OS/2, just set the file attributes -- on unix call 2113251881Speter our internal function which attempts to honor the umask. */ 2114251881Speter#if !defined(WIN32) && !defined(__OS2__) 2115251881Speter return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE, 2116251881Speter ignore_enoent, pool); 2117251881Speter#else 2118251881Speter apr_status_t status; 2119251881Speter const char *path_apr; 2120251881Speter 2121251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 2122251881Speter 2123251881Speter status = apr_file_attrs_set(path_apr, 2124251881Speter APR_FILE_ATTR_READONLY, 2125251881Speter APR_FILE_ATTR_READONLY, 2126251881Speter pool); 2127251881Speter 2128251881Speter if (status && status != APR_ENOTIMPL) 2129299742Sdim if (!(ignore_enoent && (APR_STATUS_IS_ENOENT(status) 2130299742Sdim || SVN__APR_STATUS_IS_ENOTDIR(status)))) 2131251881Speter return svn_error_wrap_apr(status, 2132251881Speter _("Can't set file '%s' read-only"), 2133251881Speter svn_dirent_local_style(path, pool)); 2134251881Speter 2135251881Speter return SVN_NO_ERROR; 2136251881Speter#endif 2137251881Speter} 2138251881Speter 2139251881Speter 2140251881Spetersvn_error_t * 2141251881Spetersvn_io_set_file_read_write(const char *path, 2142251881Speter svn_boolean_t ignore_enoent, 2143251881Speter apr_pool_t *pool) 2144251881Speter{ 2145251881Speter /* On Windows and OS/2, just set the file attributes -- on unix call 2146251881Speter our internal function which attempts to honor the umask. */ 2147251881Speter#if !defined(WIN32) && !defined(__OS2__) 2148251881Speter return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE, 2149251881Speter ignore_enoent, pool); 2150251881Speter#else 2151251881Speter apr_status_t status; 2152251881Speter const char *path_apr; 2153251881Speter 2154251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 2155251881Speter 2156251881Speter status = apr_file_attrs_set(path_apr, 2157251881Speter 0, 2158251881Speter APR_FILE_ATTR_READONLY, 2159251881Speter pool); 2160251881Speter 2161251881Speter if (status && status != APR_ENOTIMPL) 2162251881Speter if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status)) 2163251881Speter return svn_error_wrap_apr(status, 2164251881Speter _("Can't set file '%s' read-write"), 2165251881Speter svn_dirent_local_style(path, pool)); 2166251881Speter 2167251881Speter return SVN_NO_ERROR; 2168251881Speter#endif 2169251881Speter} 2170251881Speter 2171251881Spetersvn_error_t * 2172251881Spetersvn_io_set_file_executable(const char *path, 2173251881Speter svn_boolean_t executable, 2174251881Speter svn_boolean_t ignore_enoent, 2175251881Speter apr_pool_t *pool) 2176251881Speter{ 2177251881Speter /* On Windows and OS/2, just exit -- on unix call our internal function 2178251881Speter which attempts to honor the umask. */ 2179251881Speter#if (!defined(WIN32) && !defined(__OS2__)) 2180251881Speter return io_set_file_perms(path, FALSE, FALSE, TRUE, executable, 2181251881Speter ignore_enoent, pool); 2182251881Speter#else 2183251881Speter return SVN_NO_ERROR; 2184251881Speter#endif 2185251881Speter} 2186251881Speter 2187251881Speter 2188251881Spetersvn_error_t * 2189251881Spetersvn_io__is_finfo_read_only(svn_boolean_t *read_only, 2190251881Speter apr_finfo_t *file_info, 2191251881Speter apr_pool_t *pool) 2192251881Speter{ 2193251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 2194251881Speter apr_status_t apr_err; 2195251881Speter apr_uid_t uid; 2196251881Speter apr_gid_t gid; 2197251881Speter 2198251881Speter *read_only = FALSE; 2199251881Speter 2200251881Speter apr_err = apr_uid_current(&uid, &gid, pool); 2201251881Speter 2202251881Speter if (apr_err) 2203251881Speter return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); 2204251881Speter 2205251881Speter /* Check write bit for current user. */ 2206251881Speter if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) 2207251881Speter *read_only = !(file_info->protection & APR_UWRITE); 2208251881Speter 2209251881Speter else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) 2210251881Speter *read_only = !(file_info->protection & APR_GWRITE); 2211251881Speter 2212251881Speter else 2213251881Speter *read_only = !(file_info->protection & APR_WWRITE); 2214251881Speter 2215251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 2216251881Speter *read_only = (file_info->protection & APR_FREADONLY); 2217251881Speter#endif 2218251881Speter 2219251881Speter return SVN_NO_ERROR; 2220251881Speter} 2221251881Speter 2222251881Spetersvn_error_t * 2223251881Spetersvn_io__is_finfo_executable(svn_boolean_t *executable, 2224251881Speter apr_finfo_t *file_info, 2225251881Speter apr_pool_t *pool) 2226251881Speter{ 2227251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 2228251881Speter apr_status_t apr_err; 2229251881Speter apr_uid_t uid; 2230251881Speter apr_gid_t gid; 2231251881Speter 2232251881Speter *executable = FALSE; 2233251881Speter 2234251881Speter apr_err = apr_uid_current(&uid, &gid, pool); 2235251881Speter 2236251881Speter if (apr_err) 2237251881Speter return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); 2238251881Speter 2239251881Speter /* Check executable bit for current user. */ 2240251881Speter if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) 2241251881Speter *executable = (file_info->protection & APR_UEXECUTE); 2242251881Speter 2243251881Speter else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) 2244251881Speter *executable = (file_info->protection & APR_GEXECUTE); 2245251881Speter 2246251881Speter else 2247251881Speter *executable = (file_info->protection & APR_WEXECUTE); 2248251881Speter 2249251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 2250251881Speter *executable = FALSE; 2251251881Speter#endif 2252251881Speter 2253251881Speter return SVN_NO_ERROR; 2254251881Speter} 2255251881Speter 2256251881Spetersvn_error_t * 2257251881Spetersvn_io_is_file_executable(svn_boolean_t *executable, 2258251881Speter const char *path, 2259251881Speter apr_pool_t *pool) 2260251881Speter{ 2261251881Speter#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) 2262251881Speter apr_finfo_t file_info; 2263251881Speter 2264251881Speter SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER, 2265251881Speter pool)); 2266251881Speter SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool)); 2267251881Speter 2268251881Speter#else /* WIN32 || __OS2__ || !APR_HAS_USER */ 2269251881Speter *executable = FALSE; 2270251881Speter#endif 2271251881Speter 2272251881Speter return SVN_NO_ERROR; 2273251881Speter} 2274251881Speter 2275251881Speter 2276251881Speter/*** File locking. ***/ 2277251881Speter#if !defined(WIN32) && !defined(__OS2__) 2278251881Speter/* Clear all outstanding locks on ARG, an open apr_file_t *. */ 2279251881Speterstatic apr_status_t 2280251881Speterfile_clear_locks(void *arg) 2281251881Speter{ 2282251881Speter apr_status_t apr_err; 2283251881Speter apr_file_t *f = arg; 2284251881Speter 2285251881Speter /* Remove locks. */ 2286251881Speter apr_err = apr_file_unlock(f); 2287251881Speter if (apr_err) 2288251881Speter return apr_err; 2289251881Speter 2290251881Speter return 0; 2291251881Speter} 2292251881Speter#endif 2293251881Speter 2294251881Spetersvn_error_t * 2295251881Spetersvn_io_lock_open_file(apr_file_t *lockfile_handle, 2296251881Speter svn_boolean_t exclusive, 2297251881Speter svn_boolean_t nonblocking, 2298251881Speter apr_pool_t *pool) 2299251881Speter{ 2300251881Speter int locktype = APR_FLOCK_SHARED; 2301251881Speter apr_status_t apr_err; 2302251881Speter const char *fname; 2303251881Speter 2304251881Speter if (exclusive) 2305251881Speter locktype = APR_FLOCK_EXCLUSIVE; 2306251881Speter if (nonblocking) 2307251881Speter locktype |= APR_FLOCK_NONBLOCK; 2308251881Speter 2309251881Speter /* We need this only in case of an error but this is cheap to get - 2310251881Speter * so we do it here for clarity. */ 2311251881Speter apr_err = apr_file_name_get(&fname, lockfile_handle); 2312251881Speter if (apr_err) 2313251881Speter return svn_error_wrap_apr(apr_err, _("Can't get file name")); 2314251881Speter 2315251881Speter /* Get lock on the filehandle. */ 2316251881Speter apr_err = apr_file_lock(lockfile_handle, locktype); 2317251881Speter 2318251881Speter /* In deployments with two or more multithreaded servers running on 2319251881Speter the same system serving two or more fsfs repositories it is 2320251881Speter possible for a deadlock to occur when getting a write lock on 2321251881Speter db/txn-current-lock: 2322251881Speter 2323251881Speter Process 1 Process 2 2324251881Speter --------- --------- 2325251881Speter thread 1: get lock in repos A 2326251881Speter thread 1: get lock in repos B 2327251881Speter thread 2: block getting lock in repos A 2328251881Speter thread 2: try to get lock in B *** deadlock *** 2329251881Speter 2330251881Speter Retry for a while for the deadlock to clear. */ 2331251881Speter FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype)); 2332251881Speter 2333251881Speter if (apr_err) 2334251881Speter { 2335251881Speter switch (locktype & APR_FLOCK_TYPEMASK) 2336251881Speter { 2337251881Speter case APR_FLOCK_SHARED: 2338251881Speter return svn_error_wrap_apr(apr_err, 2339251881Speter _("Can't get shared lock on file '%s'"), 2340251881Speter try_utf8_from_internal_style(fname, pool)); 2341251881Speter case APR_FLOCK_EXCLUSIVE: 2342251881Speter return svn_error_wrap_apr(apr_err, 2343251881Speter _("Can't get exclusive lock on file '%s'"), 2344251881Speter try_utf8_from_internal_style(fname, pool)); 2345251881Speter default: 2346251881Speter SVN_ERR_MALFUNCTION(); 2347251881Speter } 2348251881Speter } 2349251881Speter 2350251881Speter/* On Windows and OS/2 file locks are automatically released when 2351251881Speter the file handle closes */ 2352251881Speter#if !defined(WIN32) && !defined(__OS2__) 2353251881Speter apr_pool_cleanup_register(pool, lockfile_handle, 2354251881Speter file_clear_locks, 2355251881Speter apr_pool_cleanup_null); 2356251881Speter#endif 2357251881Speter 2358251881Speter return SVN_NO_ERROR; 2359251881Speter} 2360251881Speter 2361251881Spetersvn_error_t * 2362251881Spetersvn_io_unlock_open_file(apr_file_t *lockfile_handle, 2363251881Speter apr_pool_t *pool) 2364251881Speter{ 2365251881Speter const char *fname; 2366251881Speter apr_status_t apr_err; 2367251881Speter 2368251881Speter /* We need this only in case of an error but this is cheap to get - 2369251881Speter * so we do it here for clarity. */ 2370251881Speter apr_err = apr_file_name_get(&fname, lockfile_handle); 2371251881Speter if (apr_err) 2372251881Speter return svn_error_wrap_apr(apr_err, _("Can't get file name")); 2373251881Speter 2374251881Speter /* The actual unlock attempt. */ 2375251881Speter apr_err = apr_file_unlock(lockfile_handle); 2376251881Speter if (apr_err) 2377251881Speter return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"), 2378251881Speter try_utf8_from_internal_style(fname, pool)); 2379251881Speter 2380251881Speter/* On Windows and OS/2 file locks are automatically released when 2381251881Speter the file handle closes */ 2382251881Speter#if !defined(WIN32) && !defined(__OS2__) 2383251881Speter apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks); 2384251881Speter#endif 2385251881Speter 2386251881Speter return SVN_NO_ERROR; 2387251881Speter} 2388251881Speter 2389251881Spetersvn_error_t * 2390251881Spetersvn_io_file_lock2(const char *lock_file, 2391251881Speter svn_boolean_t exclusive, 2392251881Speter svn_boolean_t nonblocking, 2393251881Speter apr_pool_t *pool) 2394251881Speter{ 2395251881Speter int locktype = APR_FLOCK_SHARED; 2396251881Speter apr_file_t *lockfile_handle; 2397251881Speter apr_int32_t flags; 2398251881Speter 2399251881Speter if (exclusive) 2400251881Speter locktype = APR_FLOCK_EXCLUSIVE; 2401251881Speter 2402251881Speter flags = APR_READ; 2403251881Speter if (locktype == APR_FLOCK_EXCLUSIVE) 2404251881Speter flags |= APR_WRITE; 2405251881Speter 2406251881Speter /* locktype is never read after this block, so we don't need to bother 2407251881Speter setting it. If that were to ever change, uncomment the following 2408251881Speter block. 2409251881Speter if (nonblocking) 2410251881Speter locktype |= APR_FLOCK_NONBLOCK; 2411251881Speter */ 2412251881Speter 2413251881Speter SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags, 2414251881Speter APR_OS_DEFAULT, 2415251881Speter pool)); 2416251881Speter 2417251881Speter /* Get lock on the filehandle. */ 2418251881Speter return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool); 2419251881Speter} 2420251881Speter 2421299742Sdimsvn_error_t * 2422299742Sdimsvn_io__file_lock_autocreate(const char *lock_file, 2423299742Sdim apr_pool_t *pool) 2424299742Sdim{ 2425299742Sdim svn_error_t *err 2426299742Sdim = svn_io_file_lock2(lock_file, TRUE, FALSE, pool); 2427299742Sdim if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 2428299742Sdim { 2429299742Sdim /* No lock file? No big deal; these are just empty files anyway. 2430299742Sdim Create it and try again. */ 2431299742Sdim svn_error_clear(err); 2432251881Speter 2433299742Sdim /* This file creation is racy. 2434299742Sdim We don't care as long as file gets created at all. */ 2435299742Sdim err = svn_io_file_create_empty(lock_file, pool); 2436299742Sdim if (err && APR_STATUS_IS_EEXIST(err->apr_err)) 2437299742Sdim { 2438299742Sdim svn_error_clear(err); 2439299742Sdim err = NULL; 2440299742Sdim } 2441299742Sdim 2442299742Sdim /* Finally, lock the file - if it exists */ 2443299742Sdim if (!err) 2444299742Sdim err = svn_io_file_lock2(lock_file, TRUE, FALSE, pool); 2445299742Sdim } 2446299742Sdim 2447299742Sdim return svn_error_trace(err); 2448299742Sdim} 2449299742Sdim 2450299742Sdim 2451251881Speter 2452251881Speter/* Data consistency/coherency operations. */ 2453251881Speter 2454251881Spetersvn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, 2455251881Speter apr_pool_t *pool) 2456251881Speter{ 2457251881Speter apr_os_file_t filehand; 2458251881Speter 2459299742Sdim /* ### In apr 1.4+ we could delegate most of this function to 2460299742Sdim apr_file_sync(). The only major difference is that this doesn't 2461299742Sdim contain the retry loop for EINTR on linux. */ 2462299742Sdim 2463251881Speter /* First make sure that any user-space buffered data is flushed. */ 2464299742Sdim SVN_ERR(svn_io_file_flush(file, pool)); 2465251881Speter 2466251881Speter apr_os_file_get(&filehand, file); 2467251881Speter 2468251881Speter /* Call the operating system specific function to actually force the 2469251881Speter data to disk. */ 2470251881Speter { 2471251881Speter#ifdef WIN32 2472251881Speter 2473251881Speter if (! FlushFileBuffers(filehand)) 2474251881Speter return svn_error_wrap_apr(apr_get_os_error(), 2475251881Speter _("Can't flush file to disk")); 2476251881Speter 2477251881Speter#else 2478251881Speter int rv; 2479251881Speter 2480251881Speter do { 2481299742Sdim#ifdef F_FULLFSYNC 2482299742Sdim rv = fcntl(filehand, F_FULLFSYNC, 0); 2483299742Sdim#else 2484251881Speter rv = fsync(filehand); 2485299742Sdim#endif 2486251881Speter } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); 2487251881Speter 2488251881Speter /* If the file is in a memory filesystem, fsync() may return 2489251881Speter EINVAL. Presumably the user knows the risks, and we can just 2490251881Speter ignore the error. */ 2491251881Speter if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error())) 2492251881Speter return SVN_NO_ERROR; 2493251881Speter 2494251881Speter if (rv == -1) 2495251881Speter return svn_error_wrap_apr(apr_get_os_error(), 2496251881Speter _("Can't flush file to disk")); 2497251881Speter 2498251881Speter#endif 2499251881Speter } 2500251881Speter return SVN_NO_ERROR; 2501251881Speter} 2502251881Speter 2503251881Speter 2504251881Speter 2505251881Speter/* TODO write test for these two functions, then refactor. */ 2506251881Speter 2507251881Speter/* Set RESULT to an svn_stringbuf_t containing the contents of FILE. 2508251881Speter FILENAME is the FILE's on-disk APR-safe name, or NULL if that name 2509251881Speter isn't known. If CHECK_SIZE is TRUE, the function will attempt to 2510251881Speter first stat() the file to determine it's size before sucking its 2511251881Speter contents into the stringbuf. (Doing so can prevent unnecessary 2512251881Speter memory usage, an unwanted side effect of the stringbuf growth and 2513251881Speter reallocation mechanism.) */ 2514251881Speterstatic svn_error_t * 2515251881Speterstringbuf_from_aprfile(svn_stringbuf_t **result, 2516251881Speter const char *filename, 2517251881Speter apr_file_t *file, 2518251881Speter svn_boolean_t check_size, 2519251881Speter apr_pool_t *pool) 2520251881Speter{ 2521251881Speter apr_size_t len; 2522251881Speter svn_error_t *err; 2523251881Speter svn_stringbuf_t *res = NULL; 2524251881Speter apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE; 2525299742Sdim char *buf; 2526251881Speter 2527251881Speter /* If our caller wants us to check the size of the file for 2528251881Speter efficient memory handling, we'll try to do so. */ 2529251881Speter if (check_size) 2530251881Speter { 2531299742Sdim apr_finfo_t finfo = { 0 }; 2532251881Speter 2533299742Sdim /* In some cases we get size 0 and no error for non files, 2534299742Sdim so we also check for the name. (= cached in apr_file_t) */ 2535299742Sdim if (! apr_file_info_get(&finfo, APR_FINFO_SIZE, file) && finfo.fname) 2536251881Speter { 2537299742Sdim /* we've got the file length. Now, read it in one go. */ 2538299742Sdim svn_boolean_t eof; 2539299742Sdim res_initial_len = (apr_size_t)finfo.size; 2540299742Sdim res = svn_stringbuf_create_ensure(res_initial_len, pool); 2541299742Sdim SVN_ERR(svn_io_file_read_full2(file, res->data, 2542299742Sdim res_initial_len, &res->len, 2543299742Sdim &eof, pool)); 2544299742Sdim res->data[res->len] = 0; 2545251881Speter 2546299742Sdim *result = res; 2547299742Sdim return SVN_NO_ERROR; 2548251881Speter } 2549251881Speter } 2550251881Speter 2551251881Speter /* XXX: We should check the incoming data for being of type binary. */ 2552299742Sdim buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 2553251881Speter res = svn_stringbuf_create_ensure(res_initial_len, pool); 2554251881Speter 2555251881Speter /* apr_file_read will not return data and eof in the same call. So this loop 2556251881Speter * is safe from missing read data. */ 2557251881Speter len = SVN__STREAM_CHUNK_SIZE; 2558251881Speter err = svn_io_file_read(file, buf, &len, pool); 2559251881Speter while (! err) 2560251881Speter { 2561251881Speter svn_stringbuf_appendbytes(res, buf, len); 2562251881Speter len = SVN__STREAM_CHUNK_SIZE; 2563251881Speter err = svn_io_file_read(file, buf, &len, pool); 2564251881Speter } 2565251881Speter 2566251881Speter /* Having read all the data we *expect* EOF */ 2567251881Speter if (err && !APR_STATUS_IS_EOF(err->apr_err)) 2568299742Sdim return svn_error_trace(err); 2569251881Speter svn_error_clear(err); 2570251881Speter 2571251881Speter *result = res; 2572251881Speter return SVN_NO_ERROR; 2573251881Speter} 2574251881Speter 2575251881Spetersvn_error_t * 2576251881Spetersvn_stringbuf_from_file2(svn_stringbuf_t **result, 2577251881Speter const char *filename, 2578251881Speter apr_pool_t *pool) 2579251881Speter{ 2580251881Speter apr_file_t *f; 2581251881Speter 2582251881Speter if (filename[0] == '-' && filename[1] == '\0') 2583251881Speter { 2584251881Speter apr_status_t apr_err; 2585251881Speter if ((apr_err = apr_file_open_stdin(&f, pool))) 2586251881Speter return svn_error_wrap_apr(apr_err, _("Can't open stdin")); 2587251881Speter SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool)); 2588251881Speter } 2589251881Speter else 2590251881Speter { 2591251881Speter SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool)); 2592251881Speter SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool)); 2593251881Speter } 2594251881Speter return svn_io_file_close(f, pool); 2595251881Speter} 2596251881Speter 2597251881Speter 2598251881Spetersvn_error_t * 2599251881Spetersvn_stringbuf_from_file(svn_stringbuf_t **result, 2600251881Speter const char *filename, 2601251881Speter apr_pool_t *pool) 2602251881Speter{ 2603251881Speter if (filename[0] == '-' && filename[1] == '\0') 2604251881Speter return svn_error_create 2605251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2606251881Speter _("Reading from stdin is disallowed")); 2607251881Speter return svn_stringbuf_from_file2(result, filename, pool); 2608251881Speter} 2609251881Speter 2610251881Spetersvn_error_t * 2611251881Spetersvn_stringbuf_from_aprfile(svn_stringbuf_t **result, 2612251881Speter apr_file_t *file, 2613251881Speter apr_pool_t *pool) 2614251881Speter{ 2615251881Speter return stringbuf_from_aprfile(result, NULL, file, TRUE, pool); 2616251881Speter} 2617251881Speter 2618251881Speter 2619251881Speter 2620251881Speter/* Deletion. */ 2621251881Speter 2622251881Spetersvn_error_t * 2623251881Spetersvn_io_remove_file2(const char *path, 2624251881Speter svn_boolean_t ignore_enoent, 2625251881Speter apr_pool_t *scratch_pool) 2626251881Speter{ 2627251881Speter apr_status_t apr_err; 2628251881Speter const char *path_apr; 2629251881Speter 2630251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool)); 2631251881Speter 2632251881Speter apr_err = apr_file_remove(path_apr, scratch_pool); 2633251881Speter 2634251881Speter#ifdef WIN32 2635251881Speter /* If the target is read only NTFS reports EACCESS and FAT/FAT32 2636251881Speter reports EEXIST */ 2637251881Speter if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err)) 2638251881Speter { 2639251881Speter /* Set the destination file writable because Windows will not 2640251881Speter allow us to delete when path is read-only */ 2641251881Speter SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool)); 2642251881Speter apr_err = apr_file_remove(path_apr, scratch_pool); 2643251881Speter 2644251881Speter if (!apr_err) 2645251881Speter return SVN_NO_ERROR; 2646251881Speter } 2647251881Speter 2648299742Sdim /* Check to make sure we aren't trying to delete a directory */ 2649299742Sdim if (apr_err == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED) 2650299742Sdim || apr_err == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION)) 2651251881Speter { 2652299742Sdim apr_finfo_t finfo; 2653299742Sdim 2654299742Sdim if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool) 2655299742Sdim && finfo.filetype == APR_REG) 2656251881Speter { 2657299742Sdim WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr, scratch_pool)); 2658251881Speter } 2659299742Sdim } 2660251881Speter 2661299742Sdim /* Just return the delete error */ 2662251881Speter#endif 2663251881Speter 2664299742Sdim if (!apr_err) 2665299742Sdim { 2666299742Sdim return SVN_NO_ERROR; 2667299742Sdim } 2668299742Sdim else if (ignore_enoent && (APR_STATUS_IS_ENOENT(apr_err) 2669299742Sdim || SVN__APR_STATUS_IS_ENOTDIR(apr_err))) 2670299742Sdim { 2671299742Sdim return SVN_NO_ERROR; 2672299742Sdim } 2673299742Sdim else 2674299742Sdim { 2675299742Sdim return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"), 2676299742Sdim svn_dirent_local_style(path, scratch_pool)); 2677299742Sdim } 2678251881Speter} 2679251881Speter 2680251881Speter 2681251881Spetersvn_error_t * 2682251881Spetersvn_io_remove_dir(const char *path, apr_pool_t *pool) 2683251881Speter{ 2684251881Speter return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool); 2685251881Speter} 2686251881Speter 2687251881Speter/* 2688251881Speter Mac OS X has a bug where if you're reading the contents of a 2689251881Speter directory via readdir in a loop, and you remove one of the entries in 2690251881Speter the directory and the directory has 338 or more files in it you will 2691251881Speter skip over some of the entries in the directory. Needless to say, 2692251881Speter this causes problems if you are using this kind of loop inside a 2693251881Speter function that is recursively deleting a directory, because when you 2694251881Speter get around to removing the directory it will still have something in 2695251881Speter it. A similar problem has been observed in other BSDs. This bug has 2696251881Speter since been fixed. See http://www.vnode.ch/fixing_seekdir for details. 2697251881Speter 2698251881Speter The workaround is to delete the files only _after_ the initial 2699251881Speter directory scan. A previous workaround involving rewinddir is 2700251881Speter problematic on Win32 and some NFS clients, notably NetBSD. 2701251881Speter 2702251881Speter See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and 2703251881Speter http://subversion.tigris.org/issues/show_bug.cgi?id=3501. 2704251881Speter*/ 2705251881Speter 2706251881Speter/* Neither windows nor unix allows us to delete a non-empty 2707251881Speter directory. 2708251881Speter 2709251881Speter This is a function to perform the equivalent of 'rm -rf'. */ 2710251881Spetersvn_error_t * 2711251881Spetersvn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent, 2712251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 2713251881Speter apr_pool_t *pool) 2714251881Speter{ 2715251881Speter svn_error_t *err; 2716251881Speter apr_pool_t *subpool; 2717251881Speter apr_hash_t *dirents; 2718251881Speter apr_hash_index_t *hi; 2719251881Speter 2720251881Speter /* Check for pending cancellation request. 2721251881Speter If we need to bail out, do so early. */ 2722251881Speter 2723251881Speter if (cancel_func) 2724251881Speter SVN_ERR((*cancel_func)(cancel_baton)); 2725251881Speter 2726251881Speter subpool = svn_pool_create(pool); 2727251881Speter 2728251881Speter err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool); 2729251881Speter if (err) 2730251881Speter { 2731251881Speter /* if the directory doesn't exist, our mission is accomplished */ 2732299742Sdim if (ignore_enoent && (APR_STATUS_IS_ENOENT(err->apr_err) 2733299742Sdim || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 2734251881Speter { 2735251881Speter svn_error_clear(err); 2736251881Speter return SVN_NO_ERROR; 2737251881Speter } 2738251881Speter return svn_error_trace(err); 2739251881Speter } 2740251881Speter 2741251881Speter for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi)) 2742251881Speter { 2743299742Sdim const char *name = apr_hash_this_key(hi); 2744299742Sdim const svn_io_dirent2_t *dirent = apr_hash_this_val(hi); 2745251881Speter const char *fullpath; 2746251881Speter 2747251881Speter fullpath = svn_dirent_join(path, name, subpool); 2748251881Speter if (dirent->kind == svn_node_dir) 2749251881Speter { 2750251881Speter /* Don't check for cancellation, the callee will immediately do so */ 2751251881Speter SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func, 2752251881Speter cancel_baton, subpool)); 2753251881Speter } 2754251881Speter else 2755251881Speter { 2756251881Speter if (cancel_func) 2757251881Speter SVN_ERR((*cancel_func)(cancel_baton)); 2758251881Speter 2759251881Speter err = svn_io_remove_file2(fullpath, FALSE, subpool); 2760251881Speter if (err) 2761251881Speter return svn_error_createf 2762251881Speter (err->apr_err, err, _("Can't remove '%s'"), 2763251881Speter svn_dirent_local_style(fullpath, subpool)); 2764251881Speter } 2765251881Speter } 2766251881Speter 2767251881Speter svn_pool_destroy(subpool); 2768251881Speter 2769251881Speter return svn_io_dir_remove_nonrecursive(path, pool); 2770251881Speter} 2771251881Speter 2772251881Spetersvn_error_t * 2773251881Spetersvn_io_get_dir_filenames(apr_hash_t **dirents, 2774251881Speter const char *path, 2775251881Speter apr_pool_t *pool) 2776251881Speter{ 2777251881Speter return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE, 2778251881Speter pool, pool)); 2779251881Speter} 2780251881Speter 2781251881Spetersvn_io_dirent2_t * 2782251881Spetersvn_io_dirent2_create(apr_pool_t *result_pool) 2783251881Speter{ 2784251881Speter svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent)); 2785251881Speter 2786251881Speter /*dirent->kind = svn_node_none; 2787251881Speter dirent->special = FALSE;*/ 2788251881Speter dirent->filesize = SVN_INVALID_FILESIZE; 2789251881Speter /*dirent->mtime = 0;*/ 2790251881Speter 2791251881Speter return dirent; 2792251881Speter} 2793251881Speter 2794251881Spetersvn_io_dirent2_t * 2795251881Spetersvn_io_dirent2_dup(const svn_io_dirent2_t *item, 2796251881Speter apr_pool_t *result_pool) 2797251881Speter{ 2798251881Speter return apr_pmemdup(result_pool, 2799251881Speter item, 2800251881Speter sizeof(*item)); 2801251881Speter} 2802251881Speter 2803251881Spetersvn_error_t * 2804251881Spetersvn_io_get_dirents3(apr_hash_t **dirents, 2805251881Speter const char *path, 2806251881Speter svn_boolean_t only_check_type, 2807251881Speter apr_pool_t *result_pool, 2808251881Speter apr_pool_t *scratch_pool) 2809251881Speter{ 2810251881Speter apr_status_t status; 2811251881Speter apr_dir_t *this_dir; 2812251881Speter apr_finfo_t this_entry; 2813251881Speter apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; 2814251881Speter 2815251881Speter if (!only_check_type) 2816251881Speter flags |= APR_FINFO_SIZE | APR_FINFO_MTIME; 2817251881Speter 2818251881Speter *dirents = apr_hash_make(result_pool); 2819251881Speter 2820251881Speter SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool)); 2821251881Speter 2822251881Speter for (status = apr_dir_read(&this_entry, flags, this_dir); 2823251881Speter status == APR_SUCCESS; 2824251881Speter status = apr_dir_read(&this_entry, flags, this_dir)) 2825251881Speter { 2826251881Speter if ((this_entry.name[0] == '.') 2827251881Speter && ((this_entry.name[1] == '\0') 2828251881Speter || ((this_entry.name[1] == '.') 2829251881Speter && (this_entry.name[2] == '\0')))) 2830251881Speter { 2831251881Speter continue; 2832251881Speter } 2833251881Speter else 2834251881Speter { 2835251881Speter const char *name; 2836251881Speter svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool); 2837251881Speter 2838251881Speter SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool)); 2839251881Speter 2840251881Speter map_apr_finfo_to_node_kind(&(dirent->kind), 2841251881Speter &(dirent->special), 2842251881Speter &this_entry); 2843251881Speter 2844251881Speter if (!only_check_type) 2845251881Speter { 2846251881Speter dirent->filesize = this_entry.size; 2847251881Speter dirent->mtime = this_entry.mtime; 2848251881Speter } 2849251881Speter 2850251881Speter svn_hash_sets(*dirents, name, dirent); 2851251881Speter } 2852251881Speter } 2853251881Speter 2854251881Speter if (! (APR_STATUS_IS_ENOENT(status))) 2855251881Speter return svn_error_wrap_apr(status, _("Can't read directory '%s'"), 2856251881Speter svn_dirent_local_style(path, scratch_pool)); 2857251881Speter 2858251881Speter status = apr_dir_close(this_dir); 2859251881Speter if (status) 2860251881Speter return svn_error_wrap_apr(status, _("Error closing directory '%s'"), 2861251881Speter svn_dirent_local_style(path, scratch_pool)); 2862251881Speter 2863251881Speter return SVN_NO_ERROR; 2864251881Speter} 2865251881Speter 2866251881Spetersvn_error_t * 2867251881Spetersvn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p, 2868251881Speter const char *path, 2869251881Speter svn_boolean_t verify_truename, 2870251881Speter svn_boolean_t ignore_enoent, 2871251881Speter apr_pool_t *result_pool, 2872251881Speter apr_pool_t *scratch_pool) 2873251881Speter{ 2874251881Speter apr_finfo_t finfo; 2875251881Speter svn_io_dirent2_t *dirent; 2876251881Speter svn_error_t *err; 2877251881Speter apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK 2878251881Speter | APR_FINFO_SIZE | APR_FINFO_MTIME; 2879251881Speter 2880251881Speter#if defined(WIN32) || defined(__OS2__) 2881251881Speter if (verify_truename) 2882251881Speter wanted |= APR_FINFO_NAME; 2883251881Speter#endif 2884251881Speter 2885251881Speter err = svn_io_stat(&finfo, path, wanted, scratch_pool); 2886251881Speter 2887251881Speter if (err && ignore_enoent && 2888251881Speter (APR_STATUS_IS_ENOENT(err->apr_err) 2889251881Speter || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 2890251881Speter { 2891251881Speter svn_error_clear(err); 2892251881Speter dirent = svn_io_dirent2_create(result_pool); 2893251881Speter SVN_ERR_ASSERT(dirent->kind == svn_node_none); 2894251881Speter 2895251881Speter *dirent_p = dirent; 2896251881Speter return SVN_NO_ERROR; 2897251881Speter } 2898251881Speter SVN_ERR(err); 2899251881Speter 2900251881Speter#if defined(WIN32) || defined(__OS2__) || defined(DARWIN) 2901251881Speter if (verify_truename) 2902251881Speter { 2903251881Speter const char *requested_name = svn_dirent_basename(path, NULL); 2904251881Speter 2905251881Speter if (requested_name[0] == '\0') 2906251881Speter { 2907251881Speter /* No parent directory. No need to stat/verify */ 2908251881Speter } 2909251881Speter#if defined(WIN32) || defined(__OS2__) 2910251881Speter else if (finfo.name) 2911251881Speter { 2912251881Speter const char *name_on_disk; 2913251881Speter SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path, 2914251881Speter scratch_pool)); 2915251881Speter 2916251881Speter if (strcmp(name_on_disk, requested_name) /* != 0 */) 2917251881Speter { 2918251881Speter if (ignore_enoent) 2919251881Speter { 2920251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2921251881Speter return SVN_NO_ERROR; 2922251881Speter } 2923251881Speter else 2924251881Speter return svn_error_createf(APR_ENOENT, NULL, 2925251881Speter _("Path '%s' not found, case obstructed by '%s'"), 2926251881Speter svn_dirent_local_style(path, scratch_pool), 2927251881Speter name_on_disk); 2928251881Speter } 2929251881Speter } 2930251881Speter#elif defined(DARWIN) 2931251881Speter /* Currently apr doesn't set finfo.name on DARWIN, returning 2932251881Speter APR_INCOMPLETE. 2933251881Speter ### Can we optimize this in another way? */ 2934251881Speter else 2935251881Speter { 2936251881Speter apr_hash_t *dirents; 2937251881Speter 2938251881Speter err = svn_io_get_dirents3(&dirents, 2939251881Speter svn_dirent_dirname(path, scratch_pool), 2940251881Speter TRUE /* only_check_type */, 2941251881Speter scratch_pool, scratch_pool); 2942251881Speter 2943251881Speter if (err && ignore_enoent 2944251881Speter && (APR_STATUS_IS_ENOENT(err->apr_err) 2945251881Speter || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 2946251881Speter { 2947251881Speter svn_error_clear(err); 2948251881Speter 2949251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2950251881Speter return SVN_NO_ERROR; 2951251881Speter } 2952251881Speter else 2953251881Speter SVN_ERR(err); 2954251881Speter 2955251881Speter if (! svn_hash_gets(dirents, requested_name)) 2956251881Speter { 2957251881Speter if (ignore_enoent) 2958251881Speter { 2959251881Speter *dirent_p = svn_io_dirent2_create(result_pool); 2960251881Speter return SVN_NO_ERROR; 2961251881Speter } 2962251881Speter else 2963251881Speter return svn_error_createf(APR_ENOENT, NULL, 2964251881Speter _("Path '%s' not found"), 2965251881Speter svn_dirent_local_style(path, scratch_pool)); 2966251881Speter } 2967251881Speter } 2968251881Speter#endif 2969251881Speter } 2970251881Speter#endif 2971251881Speter 2972251881Speter dirent = svn_io_dirent2_create(result_pool); 2973251881Speter map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo); 2974251881Speter 2975251881Speter dirent->filesize = finfo.size; 2976251881Speter dirent->mtime = finfo.mtime; 2977251881Speter 2978251881Speter *dirent_p = dirent; 2979251881Speter 2980251881Speter return SVN_NO_ERROR; 2981251881Speter} 2982251881Speter 2983251881Speter/* Pool userdata key for the error file passed to svn_io_start_cmd(). */ 2984251881Speter#define ERRFILE_KEY "svn-io-start-cmd-errfile" 2985251881Speter 2986251881Speter/* Handle an error from the child process (before command execution) by 2987251881Speter printing DESC and the error string corresponding to STATUS to stderr. */ 2988251881Speterstatic void 2989251881Speterhandle_child_process_error(apr_pool_t *pool, apr_status_t status, 2990251881Speter const char *desc) 2991251881Speter{ 2992251881Speter char errbuf[256]; 2993251881Speter apr_file_t *errfile; 2994251881Speter void *p; 2995251881Speter 2996251881Speter /* We can't do anything if we get an error here, so just return. */ 2997251881Speter if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool)) 2998251881Speter return; 2999251881Speter errfile = p; 3000251881Speter 3001251881Speter if (errfile) 3002251881Speter /* What we get from APR is in native encoding. */ 3003251881Speter apr_file_printf(errfile, "%s: %s", 3004251881Speter desc, apr_strerror(status, errbuf, 3005251881Speter sizeof(errbuf))); 3006251881Speter} 3007251881Speter 3008251881Speter 3009251881Spetersvn_error_t * 3010251881Spetersvn_io_start_cmd3(apr_proc_t *cmd_proc, 3011251881Speter const char *path, 3012251881Speter const char *cmd, 3013251881Speter const char *const *args, 3014251881Speter const char *const *env, 3015251881Speter svn_boolean_t inherit, 3016251881Speter svn_boolean_t infile_pipe, 3017251881Speter apr_file_t *infile, 3018251881Speter svn_boolean_t outfile_pipe, 3019251881Speter apr_file_t *outfile, 3020251881Speter svn_boolean_t errfile_pipe, 3021251881Speter apr_file_t *errfile, 3022251881Speter apr_pool_t *pool) 3023251881Speter{ 3024251881Speter apr_status_t apr_err; 3025251881Speter apr_procattr_t *cmdproc_attr; 3026251881Speter int num_args; 3027251881Speter const char **args_native; 3028251881Speter const char *cmd_apr; 3029251881Speter 3030251881Speter SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe)); 3031251881Speter SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe)); 3032251881Speter SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe)); 3033251881Speter 3034251881Speter /* Create the process attributes. */ 3035251881Speter apr_err = apr_procattr_create(&cmdproc_attr, pool); 3036251881Speter if (apr_err) 3037251881Speter return svn_error_wrap_apr(apr_err, 3038251881Speter _("Can't create process '%s' attributes"), 3039251881Speter cmd); 3040251881Speter 3041251881Speter /* Make sure we invoke cmd directly, not through a shell. */ 3042251881Speter apr_err = apr_procattr_cmdtype_set(cmdproc_attr, 3043251881Speter inherit ? APR_PROGRAM_PATH : APR_PROGRAM); 3044251881Speter if (apr_err) 3045251881Speter return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"), 3046251881Speter cmd); 3047251881Speter 3048251881Speter /* Set the process's working directory. */ 3049251881Speter if (path) 3050251881Speter { 3051251881Speter const char *path_apr; 3052251881Speter 3053299742Sdim /* APR doesn't like our canonical path format for current directory */ 3054299742Sdim if (path[0] == '\0') 3055299742Sdim path = "."; 3056299742Sdim 3057251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 3058251881Speter apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr); 3059251881Speter if (apr_err) 3060251881Speter return svn_error_wrap_apr(apr_err, 3061251881Speter _("Can't set process '%s' directory"), 3062251881Speter cmd); 3063251881Speter } 3064251881Speter 3065251881Speter /* Use requested inputs and outputs. 3066251881Speter 3067251881Speter ### Unfortunately each of these apr functions creates a pipe and then 3068251881Speter overwrites the pipe file descriptor with the descriptor we pass 3069251881Speter in. The pipes can then never be closed. This is an APR bug. */ 3070251881Speter if (infile) 3071251881Speter { 3072251881Speter apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL); 3073251881Speter if (apr_err) 3074251881Speter return svn_error_wrap_apr(apr_err, 3075251881Speter _("Can't set process '%s' child input"), 3076251881Speter cmd); 3077251881Speter } 3078251881Speter if (outfile) 3079251881Speter { 3080251881Speter apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL); 3081251881Speter if (apr_err) 3082251881Speter return svn_error_wrap_apr(apr_err, 3083251881Speter _("Can't set process '%s' child outfile"), 3084251881Speter cmd); 3085251881Speter } 3086251881Speter if (errfile) 3087251881Speter { 3088251881Speter apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL); 3089251881Speter if (apr_err) 3090251881Speter return svn_error_wrap_apr(apr_err, 3091251881Speter _("Can't set process '%s' child errfile"), 3092251881Speter cmd); 3093251881Speter } 3094251881Speter 3095251881Speter /* Forward request for pipes to APR. */ 3096251881Speter if (infile_pipe || outfile_pipe || errfile_pipe) 3097251881Speter { 3098251881Speter apr_err = apr_procattr_io_set(cmdproc_attr, 3099251881Speter infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE, 3100251881Speter outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE, 3101251881Speter errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE); 3102251881Speter 3103251881Speter if (apr_err) 3104251881Speter return svn_error_wrap_apr(apr_err, 3105251881Speter _("Can't set process '%s' stdio pipes"), 3106251881Speter cmd); 3107251881Speter } 3108251881Speter 3109251881Speter /* Have the child print any problems executing its program to errfile. */ 3110251881Speter apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool); 3111251881Speter if (apr_err) 3112251881Speter return svn_error_wrap_apr(apr_err, 3113251881Speter _("Can't set process '%s' child errfile for " 3114251881Speter "error handler"), 3115251881Speter cmd); 3116251881Speter apr_err = apr_procattr_child_errfn_set(cmdproc_attr, 3117251881Speter handle_child_process_error); 3118251881Speter if (apr_err) 3119251881Speter return svn_error_wrap_apr(apr_err, 3120251881Speter _("Can't set process '%s' error handler"), 3121251881Speter cmd); 3122251881Speter 3123251881Speter /* Convert cmd and args from UTF-8 */ 3124251881Speter SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool)); 3125251881Speter for (num_args = 0; args[num_args]; num_args++) 3126251881Speter ; 3127251881Speter args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *)); 3128251881Speter args_native[num_args] = NULL; 3129251881Speter while (num_args--) 3130251881Speter { 3131251881Speter /* ### Well, it turns out that on APR on Windows expects all 3132251881Speter program args to be in UTF-8. Callers of svn_io_run_cmd 3133251881Speter should be aware of that. */ 3134251881Speter SVN_ERR(cstring_from_utf8(&args_native[num_args], 3135251881Speter args[num_args], pool)); 3136251881Speter } 3137251881Speter 3138251881Speter 3139251881Speter /* Start the cmd command. */ 3140251881Speter apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native, 3141251881Speter inherit ? NULL : env, cmdproc_attr, pool); 3142251881Speter if (apr_err) 3143251881Speter return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd); 3144251881Speter 3145251881Speter return SVN_NO_ERROR; 3146251881Speter} 3147251881Speter 3148251881Speter#undef ERRFILE_KEY 3149251881Speter 3150251881Spetersvn_error_t * 3151251881Spetersvn_io_wait_for_cmd(apr_proc_t *cmd_proc, 3152251881Speter const char *cmd, 3153251881Speter int *exitcode, 3154251881Speter apr_exit_why_e *exitwhy, 3155251881Speter apr_pool_t *pool) 3156251881Speter{ 3157251881Speter apr_status_t apr_err; 3158251881Speter apr_exit_why_e exitwhy_val; 3159251881Speter int exitcode_val; 3160251881Speter 3161251881Speter /* The Win32 apr_proc_wait doesn't set this... */ 3162251881Speter exitwhy_val = APR_PROC_EXIT; 3163251881Speter 3164251881Speter /* Wait for the cmd command to finish. */ 3165251881Speter apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT); 3166251881Speter if (!APR_STATUS_IS_CHILD_DONE(apr_err)) 3167251881Speter return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"), 3168251881Speter cmd); 3169251881Speter 3170251881Speter if (exitwhy) 3171251881Speter *exitwhy = exitwhy_val; 3172251881Speter else if (APR_PROC_CHECK_SIGNALED(exitwhy_val) 3173251881Speter && APR_PROC_CHECK_CORE_DUMP(exitwhy_val)) 3174251881Speter return svn_error_createf 3175251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 3176251881Speter _("Process '%s' failed (signal %d, core dumped)"), 3177251881Speter cmd, exitcode_val); 3178251881Speter else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)) 3179251881Speter return svn_error_createf 3180251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 3181251881Speter _("Process '%s' failed (signal %d)"), 3182251881Speter cmd, exitcode_val); 3183251881Speter else if (! APR_PROC_CHECK_EXIT(exitwhy_val)) 3184251881Speter /* Don't really know what happened here. */ 3185251881Speter return svn_error_createf 3186251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 3187251881Speter _("Process '%s' failed (exitwhy %d, exitcode %d)"), 3188251881Speter cmd, exitwhy_val, exitcode_val); 3189251881Speter 3190251881Speter if (exitcode) 3191251881Speter *exitcode = exitcode_val; 3192251881Speter else if (exitcode_val != 0) 3193251881Speter return svn_error_createf 3194251881Speter (SVN_ERR_EXTERNAL_PROGRAM, NULL, 3195251881Speter _("Process '%s' returned error exitcode %d"), cmd, exitcode_val); 3196251881Speter 3197251881Speter return SVN_NO_ERROR; 3198251881Speter} 3199251881Speter 3200251881Speter 3201251881Spetersvn_error_t * 3202251881Spetersvn_io_run_cmd(const char *path, 3203251881Speter const char *cmd, 3204251881Speter const char *const *args, 3205251881Speter int *exitcode, 3206251881Speter apr_exit_why_e *exitwhy, 3207251881Speter svn_boolean_t inherit, 3208251881Speter apr_file_t *infile, 3209251881Speter apr_file_t *outfile, 3210251881Speter apr_file_t *errfile, 3211251881Speter apr_pool_t *pool) 3212251881Speter{ 3213251881Speter apr_proc_t cmd_proc; 3214251881Speter 3215251881Speter SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit, 3216251881Speter FALSE, infile, FALSE, outfile, FALSE, errfile, 3217251881Speter pool)); 3218251881Speter 3219251881Speter return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool); 3220251881Speter} 3221251881Speter 3222251881Speter 3223251881Spetersvn_error_t * 3224251881Spetersvn_io_run_diff2(const char *dir, 3225251881Speter const char *const *user_args, 3226251881Speter int num_user_args, 3227251881Speter const char *label1, 3228251881Speter const char *label2, 3229251881Speter const char *from, 3230251881Speter const char *to, 3231251881Speter int *pexitcode, 3232251881Speter apr_file_t *outfile, 3233251881Speter apr_file_t *errfile, 3234251881Speter const char *diff_cmd, 3235251881Speter apr_pool_t *pool) 3236251881Speter{ 3237251881Speter const char **args; 3238251881Speter int i; 3239251881Speter int exitcode; 3240251881Speter int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */ 3241251881Speter apr_pool_t *subpool = svn_pool_create(pool); 3242251881Speter 3243251881Speter if (pexitcode == NULL) 3244251881Speter pexitcode = &exitcode; 3245251881Speter 3246251881Speter if (user_args != NULL) 3247251881Speter nargs += num_user_args; 3248251881Speter else 3249251881Speter nargs += 1; /* -u */ 3250251881Speter 3251251881Speter if (label1 != NULL) 3252251881Speter nargs += 2; /* the -L and the label itself */ 3253251881Speter if (label2 != NULL) 3254251881Speter nargs += 2; /* the -L and the label itself */ 3255251881Speter 3256251881Speter args = apr_palloc(subpool, nargs * sizeof(char *)); 3257251881Speter 3258251881Speter i = 0; 3259251881Speter args[i++] = diff_cmd; 3260251881Speter 3261251881Speter if (user_args != NULL) 3262251881Speter { 3263251881Speter int j; 3264251881Speter for (j = 0; j < num_user_args; ++j) 3265251881Speter args[i++] = user_args[j]; 3266251881Speter } 3267251881Speter else 3268251881Speter args[i++] = "-u"; /* assume -u if the user didn't give us any args */ 3269251881Speter 3270251881Speter if (label1 != NULL) 3271251881Speter { 3272251881Speter args[i++] = "-L"; 3273251881Speter args[i++] = label1; 3274251881Speter } 3275251881Speter if (label2 != NULL) 3276251881Speter { 3277251881Speter args[i++] = "-L"; 3278251881Speter args[i++] = label2; 3279251881Speter } 3280251881Speter 3281251881Speter args[i++] = svn_dirent_local_style(from, subpool); 3282251881Speter args[i++] = svn_dirent_local_style(to, subpool); 3283251881Speter args[i++] = NULL; 3284251881Speter 3285251881Speter SVN_ERR_ASSERT(i == nargs); 3286251881Speter 3287251881Speter SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE, 3288251881Speter NULL, outfile, errfile, subpool)); 3289251881Speter 3290251881Speter /* The man page for (GNU) diff describes the return value as: 3291251881Speter 3292251881Speter "An exit status of 0 means no differences were found, 1 means 3293251881Speter some differences were found, and 2 means trouble." 3294251881Speter 3295251881Speter A return value of 2 typically occurs when diff cannot read its input 3296251881Speter or write to its output, but in any case we probably ought to return an 3297251881Speter error for anything other than 0 or 1 as the output is likely to be 3298251881Speter corrupt. 3299251881Speter */ 3300251881Speter if (*pexitcode != 0 && *pexitcode != 1) 3301251881Speter return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 3302251881Speter _("'%s' returned %d"), 3303251881Speter svn_dirent_local_style(diff_cmd, pool), 3304251881Speter *pexitcode); 3305251881Speter 3306251881Speter svn_pool_destroy(subpool); 3307251881Speter 3308251881Speter return SVN_NO_ERROR; 3309251881Speter} 3310251881Speter 3311251881Speter 3312251881Spetersvn_error_t * 3313251881Spetersvn_io_run_diff3_3(int *exitcode, 3314251881Speter const char *dir, 3315251881Speter const char *mine, 3316251881Speter const char *older, 3317251881Speter const char *yours, 3318251881Speter const char *mine_label, 3319251881Speter const char *older_label, 3320251881Speter const char *yours_label, 3321251881Speter apr_file_t *merged, 3322251881Speter const char *diff3_cmd, 3323251881Speter const apr_array_header_t *user_args, 3324251881Speter apr_pool_t *pool) 3325251881Speter{ 3326251881Speter const char **args = apr_palloc(pool, 3327251881Speter sizeof(char*) * (13 3328251881Speter + (user_args 3329251881Speter ? user_args->nelts 3330251881Speter : 1))); 3331251881Speter#ifndef NDEBUG 3332251881Speter int nargs = 12; 3333251881Speter#endif 3334251881Speter int i = 0; 3335251881Speter 3336251881Speter /* Labels fall back to sensible defaults if not specified. */ 3337251881Speter if (mine_label == NULL) 3338251881Speter mine_label = ".working"; 3339251881Speter if (older_label == NULL) 3340251881Speter older_label = ".old"; 3341251881Speter if (yours_label == NULL) 3342251881Speter yours_label = ".new"; 3343251881Speter 3344251881Speter /* Set up diff3 command line. */ 3345251881Speter args[i++] = diff3_cmd; 3346251881Speter if (user_args) 3347251881Speter { 3348251881Speter int j; 3349251881Speter for (j = 0; j < user_args->nelts; ++j) 3350251881Speter args[i++] = APR_ARRAY_IDX(user_args, j, const char *); 3351251881Speter#ifndef NDEBUG 3352251881Speter nargs += user_args->nelts; 3353251881Speter#endif 3354251881Speter } 3355251881Speter else 3356251881Speter { 3357251881Speter args[i++] = "-E"; /* We tried "-A" here, but that caused 3358251881Speter overlapping identical changes to 3359251881Speter conflict. See issue #682. */ 3360251881Speter#ifndef NDEBUG 3361251881Speter ++nargs; 3362251881Speter#endif 3363251881Speter } 3364251881Speter args[i++] = "-m"; 3365251881Speter args[i++] = "-L"; 3366251881Speter args[i++] = mine_label; 3367251881Speter args[i++] = "-L"; 3368251881Speter args[i++] = older_label; /* note: this label is ignored if 3369251881Speter using 2-part markers, which is the 3370251881Speter case with "-E". */ 3371251881Speter args[i++] = "-L"; 3372251881Speter args[i++] = yours_label; 3373251881Speter#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG 3374251881Speter { 3375251881Speter svn_boolean_t has_arg; 3376251881Speter 3377251881Speter /* ### FIXME: we really shouldn't be reading the config here; 3378251881Speter instead, the necessary bits should be passed in by the caller. 3379251881Speter But should we add another parameter to this function, when the 3380251881Speter whole external diff3 thing might eventually go away? */ 3381251881Speter apr_hash_t *config; 3382251881Speter svn_config_t *cfg; 3383251881Speter 3384251881Speter SVN_ERR(svn_config_get_config(&config, pool)); 3385251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 3386251881Speter SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS, 3387251881Speter SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG, 3388251881Speter TRUE)); 3389251881Speter if (has_arg) 3390251881Speter { 3391251881Speter const char *diff_cmd, *diff_utf8; 3392251881Speter svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS, 3393251881Speter SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF); 3394251881Speter SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool)); 3395299742Sdim args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, 3396299742Sdim SVN_VA_NULL); 3397251881Speter#ifndef NDEBUG 3398251881Speter ++nargs; 3399251881Speter#endif 3400251881Speter } 3401251881Speter } 3402251881Speter#endif 3403251881Speter args[i++] = svn_dirent_local_style(mine, pool); 3404251881Speter args[i++] = svn_dirent_local_style(older, pool); 3405251881Speter args[i++] = svn_dirent_local_style(yours, pool); 3406251881Speter args[i++] = NULL; 3407251881Speter#ifndef NDEBUG 3408251881Speter SVN_ERR_ASSERT(i == nargs); 3409251881Speter#endif 3410251881Speter 3411251881Speter /* Run diff3, output the merged text into the scratch file. */ 3412251881Speter SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args, 3413251881Speter exitcode, NULL, 3414251881Speter TRUE, /* keep environment */ 3415251881Speter NULL, merged, NULL, 3416251881Speter pool)); 3417251881Speter 3418251881Speter /* According to the diff3 docs, a '0' means the merge was clean, and 3419251881Speter '1' means conflict markers were found. Anything else is real 3420251881Speter error. */ 3421251881Speter if ((*exitcode != 0) && (*exitcode != 1)) 3422251881Speter return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 3423251881Speter _("Error running '%s': exitcode was %d, " 3424251881Speter "args were:" 3425251881Speter "\nin directory '%s', basenames:\n%s\n%s\n%s"), 3426251881Speter svn_dirent_local_style(diff3_cmd, pool), 3427251881Speter *exitcode, 3428251881Speter svn_dirent_local_style(dir, pool), 3429251881Speter /* Don't call svn_path_local_style() on 3430251881Speter the basenames. We don't want them to 3431251881Speter be absolute, and we don't need the 3432251881Speter separator conversion. */ 3433251881Speter mine, older, yours); 3434251881Speter 3435251881Speter return SVN_NO_ERROR; 3436251881Speter} 3437251881Speter 3438251881Speter 3439251881Speter/* Canonicalize a string for hashing. Modifies KEY in place. */ 3440251881Speterstatic APR_INLINE char * 3441251881Speterfileext_tolower(char *key) 3442251881Speter{ 3443251881Speter register char *p; 3444251881Speter for (p = key; *p != 0; ++p) 3445251881Speter *p = (char)apr_tolower(*p); 3446251881Speter return key; 3447251881Speter} 3448251881Speter 3449251881Speter 3450251881Spetersvn_error_t * 3451251881Spetersvn_io_parse_mimetypes_file(apr_hash_t **type_map, 3452251881Speter const char *mimetypes_file, 3453251881Speter apr_pool_t *pool) 3454251881Speter{ 3455251881Speter svn_error_t *err = SVN_NO_ERROR; 3456251881Speter apr_hash_t *types = apr_hash_make(pool); 3457251881Speter svn_boolean_t eof = FALSE; 3458251881Speter svn_stringbuf_t *buf; 3459251881Speter apr_pool_t *subpool = svn_pool_create(pool); 3460251881Speter apr_file_t *types_file; 3461251881Speter svn_stream_t *mimetypes_stream; 3462251881Speter 3463251881Speter SVN_ERR(svn_io_file_open(&types_file, mimetypes_file, 3464251881Speter APR_READ, APR_OS_DEFAULT, pool)); 3465251881Speter mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool); 3466251881Speter 3467251881Speter while (1) 3468251881Speter { 3469251881Speter apr_array_header_t *tokens; 3470251881Speter const char *type; 3471251881Speter 3472251881Speter svn_pool_clear(subpool); 3473251881Speter 3474251881Speter /* Read a line. */ 3475251881Speter if ((err = svn_stream_readline(mimetypes_stream, &buf, 3476251881Speter APR_EOL_STR, &eof, subpool))) 3477251881Speter break; 3478251881Speter 3479251881Speter /* Only pay attention to non-empty, non-comment lines. */ 3480251881Speter if (buf->len) 3481251881Speter { 3482251881Speter int i; 3483251881Speter 3484251881Speter if (buf->data[0] == '#') 3485251881Speter continue; 3486251881Speter 3487251881Speter /* Tokenize (into our return pool). */ 3488251881Speter tokens = svn_cstring_split(buf->data, " \t", TRUE, pool); 3489251881Speter if (tokens->nelts < 2) 3490251881Speter continue; 3491251881Speter 3492251881Speter /* The first token in a multi-token line is the media type. 3493251881Speter Subsequent tokens are filename extensions associated with 3494251881Speter that media type. */ 3495251881Speter type = APR_ARRAY_IDX(tokens, 0, const char *); 3496251881Speter for (i = 1; i < tokens->nelts; i++) 3497251881Speter { 3498251881Speter /* We can safely address 'ext' as a non-const string because 3499251881Speter * we know svn_cstring_split() allocated it in 'pool' for us. */ 3500251881Speter char *ext = APR_ARRAY_IDX(tokens, i, char *); 3501251881Speter fileext_tolower(ext); 3502251881Speter svn_hash_sets(types, ext, type); 3503251881Speter } 3504251881Speter } 3505251881Speter if (eof) 3506251881Speter break; 3507251881Speter } 3508251881Speter svn_pool_destroy(subpool); 3509251881Speter 3510251881Speter /* If there was an error above, close the file (ignoring any error 3511251881Speter from *that*) and return the originally error. */ 3512251881Speter if (err) 3513251881Speter { 3514251881Speter svn_error_clear(svn_stream_close(mimetypes_stream)); 3515251881Speter return err; 3516251881Speter } 3517251881Speter 3518251881Speter /* Close the stream (which closes the underlying file, too). */ 3519251881Speter SVN_ERR(svn_stream_close(mimetypes_stream)); 3520251881Speter 3521251881Speter *type_map = types; 3522251881Speter return SVN_NO_ERROR; 3523251881Speter} 3524251881Speter 3525251881Speter 3526251881Spetersvn_error_t * 3527251881Spetersvn_io_detect_mimetype2(const char **mimetype, 3528251881Speter const char *file, 3529251881Speter apr_hash_t *mimetype_map, 3530251881Speter apr_pool_t *pool) 3531251881Speter{ 3532251881Speter static const char * const generic_binary = "application/octet-stream"; 3533251881Speter 3534251881Speter svn_node_kind_t kind; 3535251881Speter apr_file_t *fh; 3536251881Speter svn_error_t *err; 3537251881Speter unsigned char block[1024]; 3538251881Speter apr_size_t amt_read = sizeof(block); 3539251881Speter 3540251881Speter /* Default return value is NULL. */ 3541251881Speter *mimetype = NULL; 3542251881Speter 3543251881Speter /* If there is a mimetype_map provided, we'll first try to look up 3544251881Speter our file's extension in the map. Failing that, we'll run the 3545251881Speter heuristic. */ 3546251881Speter if (mimetype_map) 3547251881Speter { 3548251881Speter const char *type_from_map; 3549251881Speter char *path_ext; /* Can point to physical const memory but only when 3550251881Speter svn_path_splitext sets it to "". */ 3551251881Speter svn_path_splitext(NULL, (const char **)&path_ext, file, pool); 3552251881Speter fileext_tolower(path_ext); 3553251881Speter if ((type_from_map = svn_hash_gets(mimetype_map, path_ext))) 3554251881Speter { 3555251881Speter *mimetype = type_from_map; 3556251881Speter return SVN_NO_ERROR; 3557251881Speter } 3558251881Speter } 3559251881Speter 3560251881Speter /* See if this file even exists, and make sure it really is a file. */ 3561251881Speter SVN_ERR(svn_io_check_path(file, &kind, pool)); 3562251881Speter if (kind != svn_node_file) 3563251881Speter return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, 3564251881Speter _("Can't detect MIME type of non-file '%s'"), 3565251881Speter svn_dirent_local_style(file, pool)); 3566251881Speter 3567251881Speter SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool)); 3568251881Speter 3569251881Speter /* Read a block of data from FILE. */ 3570251881Speter err = svn_io_file_read(fh, block, &amt_read, pool); 3571251881Speter if (err && ! APR_STATUS_IS_EOF(err->apr_err)) 3572251881Speter return err; 3573251881Speter svn_error_clear(err); 3574251881Speter 3575251881Speter /* Now close the file. No use keeping it open any more. */ 3576251881Speter SVN_ERR(svn_io_file_close(fh, pool)); 3577251881Speter 3578251881Speter if (svn_io_is_binary_data(block, amt_read)) 3579251881Speter *mimetype = generic_binary; 3580251881Speter 3581251881Speter return SVN_NO_ERROR; 3582251881Speter} 3583251881Speter 3584251881Speter 3585251881Spetersvn_boolean_t 3586251881Spetersvn_io_is_binary_data(const void *data, apr_size_t len) 3587251881Speter{ 3588251881Speter const unsigned char *buf = data; 3589251881Speter 3590251881Speter if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) 3591251881Speter { 3592251881Speter /* This is an empty UTF-8 file which only contains the UTF-8 BOM. 3593251881Speter * Treat it as plain text. */ 3594251881Speter return FALSE; 3595251881Speter } 3596251881Speter 3597251881Speter /* Right now, this function is going to be really stupid. It's 3598251881Speter going to examine the block of data, and make sure that 15% 3599251881Speter of the bytes are such that their value is in the ranges 0x07-0x0D 3600251881Speter or 0x20-0x7F, and that none of those bytes is 0x00. If those 3601251881Speter criteria are not met, we're calling it binary. 3602251881Speter 3603251881Speter NOTE: Originally, I intended to target 85% of the bytes being in 3604251881Speter the specified ranges, but I flubbed the condition. At any rate, 3605251881Speter folks aren't complaining, so I'm not sure that it's worth 3606251881Speter adjusting this retroactively now. --cmpilato */ 3607251881Speter if (len > 0) 3608251881Speter { 3609251881Speter apr_size_t i; 3610251881Speter apr_size_t binary_count = 0; 3611251881Speter 3612251881Speter /* Run through the data we've read, counting the 'binary-ish' 3613251881Speter bytes. HINT: If we see a 0x00 byte, we'll set our count to its 3614251881Speter max and stop reading the file. */ 3615251881Speter for (i = 0; i < len; i++) 3616251881Speter { 3617251881Speter if (buf[i] == 0) 3618251881Speter { 3619251881Speter binary_count = len; 3620251881Speter break; 3621251881Speter } 3622251881Speter if ((buf[i] < 0x07) 3623251881Speter || ((buf[i] > 0x0D) && (buf[i] < 0x20)) 3624251881Speter || (buf[i] > 0x7F)) 3625251881Speter { 3626251881Speter binary_count++; 3627251881Speter } 3628251881Speter } 3629251881Speter 3630251881Speter return (((binary_count * 1000) / len) > 850); 3631251881Speter } 3632251881Speter 3633251881Speter return FALSE; 3634251881Speter} 3635251881Speter 3636251881Speter 3637251881Spetersvn_error_t * 3638251881Spetersvn_io_detect_mimetype(const char **mimetype, 3639251881Speter const char *file, 3640251881Speter apr_pool_t *pool) 3641251881Speter{ 3642251881Speter return svn_io_detect_mimetype2(mimetype, file, NULL, pool); 3643251881Speter} 3644251881Speter 3645251881Speter 3646251881Spetersvn_error_t * 3647251881Spetersvn_io_file_open(apr_file_t **new_file, const char *fname, 3648251881Speter apr_int32_t flag, apr_fileperms_t perm, 3649251881Speter apr_pool_t *pool) 3650251881Speter{ 3651251881Speter const char *fname_apr; 3652251881Speter apr_status_t status; 3653251881Speter 3654251881Speter SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); 3655251881Speter status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE, 3656251881Speter pool); 3657251881Speter 3658251881Speter if (status) 3659251881Speter return svn_error_wrap_apr(status, _("Can't open file '%s'"), 3660251881Speter svn_dirent_local_style(fname, pool)); 3661251881Speter else 3662251881Speter return SVN_NO_ERROR; 3663251881Speter} 3664251881Speter 3665251881Speter 3666251881Speterstatic APR_INLINE svn_error_t * 3667251881Speterdo_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status, 3668251881Speter const char *msg, const char *msg_no_name, 3669251881Speter apr_pool_t *pool) 3670251881Speter{ 3671251881Speter const char *name; 3672251881Speter svn_error_t *err; 3673251881Speter 3674251881Speter if (! status) 3675251881Speter return SVN_NO_ERROR; 3676251881Speter 3677251881Speter err = svn_io_file_name_get(&name, file, pool); 3678251881Speter if (err) 3679251881Speter name = NULL; 3680251881Speter svn_error_clear(err); 3681251881Speter 3682251881Speter /* ### Issue #3014: Return a specific error for broken pipes, 3683251881Speter * ### with a single element in the error chain. */ 3684262253Speter if (SVN__APR_STATUS_IS_EPIPE(status)) 3685251881Speter return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); 3686251881Speter 3687251881Speter if (name) 3688251881Speter return svn_error_wrap_apr(status, _(msg), 3689251881Speter try_utf8_from_internal_style(name, pool)); 3690251881Speter else 3691251881Speter return svn_error_wrap_apr(status, "%s", _(msg_no_name)); 3692251881Speter} 3693251881Speter 3694251881Speter 3695251881Spetersvn_error_t * 3696251881Spetersvn_io_file_close(apr_file_t *file, apr_pool_t *pool) 3697251881Speter{ 3698251881Speter return do_io_file_wrapper_cleanup(file, apr_file_close(file), 3699251881Speter N_("Can't close file '%s'"), 3700251881Speter N_("Can't close stream"), 3701251881Speter pool); 3702251881Speter} 3703251881Speter 3704251881Speter 3705251881Spetersvn_error_t * 3706251881Spetersvn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool) 3707251881Speter{ 3708251881Speter return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file), 3709251881Speter N_("Can't read file '%s'"), 3710251881Speter N_("Can't read stream"), 3711251881Speter pool); 3712251881Speter} 3713251881Speter 3714251881Speter 3715251881Spetersvn_error_t * 3716251881Spetersvn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool) 3717251881Speter{ 3718251881Speter return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file), 3719251881Speter N_("Can't write file '%s'"), 3720251881Speter N_("Can't write stream"), 3721251881Speter pool); 3722251881Speter} 3723251881Speter 3724251881Speter 3725251881Spetersvn_error_t * 3726251881Spetersvn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted, 3727251881Speter apr_file_t *file, apr_pool_t *pool) 3728251881Speter{ 3729251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 3730251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 3731251881Speter 3732251881Speter return do_io_file_wrapper_cleanup( 3733251881Speter file, apr_file_info_get(finfo, wanted, file), 3734251881Speter N_("Can't get attribute information from file '%s'"), 3735251881Speter N_("Can't get attribute information from stream"), 3736251881Speter pool); 3737251881Speter} 3738251881Speter 3739251881Speter 3740251881Spetersvn_error_t * 3741251881Spetersvn_io_file_read(apr_file_t *file, void *buf, 3742251881Speter apr_size_t *nbytes, apr_pool_t *pool) 3743251881Speter{ 3744251881Speter return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes), 3745251881Speter N_("Can't read file '%s'"), 3746251881Speter N_("Can't read stream"), 3747251881Speter pool); 3748251881Speter} 3749251881Speter 3750251881Speter 3751251881Spetersvn_error_t * 3752251881Spetersvn_io_file_read_full2(apr_file_t *file, void *buf, 3753251881Speter apr_size_t nbytes, apr_size_t *bytes_read, 3754251881Speter svn_boolean_t *hit_eof, 3755251881Speter apr_pool_t *pool) 3756251881Speter{ 3757251881Speter apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read); 3758251881Speter if (hit_eof) 3759251881Speter { 3760251881Speter if (APR_STATUS_IS_EOF(status)) 3761251881Speter { 3762251881Speter *hit_eof = TRUE; 3763251881Speter return SVN_NO_ERROR; 3764251881Speter } 3765251881Speter else 3766251881Speter *hit_eof = FALSE; 3767251881Speter } 3768251881Speter 3769251881Speter return do_io_file_wrapper_cleanup(file, status, 3770251881Speter N_("Can't read file '%s'"), 3771251881Speter N_("Can't read stream"), 3772251881Speter pool); 3773251881Speter} 3774251881Speter 3775251881Speter 3776251881Spetersvn_error_t * 3777251881Spetersvn_io_file_seek(apr_file_t *file, apr_seek_where_t where, 3778251881Speter apr_off_t *offset, apr_pool_t *pool) 3779251881Speter{ 3780251881Speter return do_io_file_wrapper_cleanup( 3781251881Speter file, apr_file_seek(file, where, offset), 3782251881Speter N_("Can't set position pointer in file '%s'"), 3783251881Speter N_("Can't set position pointer in stream"), 3784251881Speter pool); 3785251881Speter} 3786251881Speter 3787299742Sdimsvn_error_t * 3788299742Sdimsvn_io_file_aligned_seek(apr_file_t *file, 3789299742Sdim apr_off_t block_size, 3790299742Sdim apr_off_t *buffer_start, 3791299742Sdim apr_off_t offset, 3792299742Sdim apr_pool_t *scratch_pool) 3793299742Sdim{ 3794299742Sdim const apr_size_t apr_default_buffer_size = 4096; 3795299742Sdim apr_size_t file_buffer_size = apr_default_buffer_size; 3796299742Sdim apr_off_t desired_offset = 0; 3797299742Sdim apr_off_t current = 0; 3798299742Sdim apr_off_t aligned_offset = 0; 3799299742Sdim svn_boolean_t fill_buffer = FALSE; 3800251881Speter 3801299742Sdim /* paranoia check: huge blocks on 32 bit machines may cause overflows */ 3802299742Sdim SVN_ERR_ASSERT(block_size == (apr_size_t)block_size); 3803299742Sdim 3804299742Sdim /* default for invalid block sizes */ 3805299742Sdim if (block_size == 0) 3806299742Sdim block_size = apr_default_buffer_size; 3807299742Sdim 3808299742Sdim file_buffer_size = apr_file_buffer_size_get(file); 3809299742Sdim 3810299742Sdim /* don't try to set a buffer size for non-buffered files! */ 3811299742Sdim if (file_buffer_size == 0) 3812299742Sdim { 3813299742Sdim aligned_offset = offset; 3814299742Sdim } 3815299742Sdim else if (file_buffer_size != (apr_size_t)block_size) 3816299742Sdim { 3817299742Sdim /* FILE has the wrong buffer size. correct it */ 3818299742Sdim char *buffer; 3819299742Sdim file_buffer_size = (apr_size_t)block_size; 3820299742Sdim buffer = apr_palloc(apr_file_pool_get(file), file_buffer_size); 3821299742Sdim apr_file_buffer_set(file, buffer, file_buffer_size); 3822299742Sdim 3823299742Sdim /* seek to the start of the block and cause APR to read 1 block */ 3824299742Sdim aligned_offset = offset - (offset % block_size); 3825299742Sdim fill_buffer = TRUE; 3826299742Sdim } 3827299742Sdim else 3828299742Sdim { 3829299742Sdim aligned_offset = offset - (offset % file_buffer_size); 3830299742Sdim 3831299742Sdim /* We have no way to determine the block start of an APR file. 3832299742Sdim Furthermore, we don't want to throw away the current buffer 3833299742Sdim contents. Thus, we re-align the buffer only if the CURRENT 3834299742Sdim offset definitely lies outside the desired, aligned buffer. 3835299742Sdim This covers the typical case of linear reads getting very 3836299742Sdim close to OFFSET but reading the previous / following block. 3837299742Sdim 3838299742Sdim Note that ALIGNED_OFFSET may still be within the current 3839299742Sdim buffer and no I/O will actually happen in the FILL_BUFFER 3840299742Sdim section below. 3841299742Sdim */ 3842299742Sdim SVN_ERR(svn_io_file_seek(file, APR_CUR, ¤t, scratch_pool)); 3843299742Sdim fill_buffer = aligned_offset + file_buffer_size <= current 3844299742Sdim || current <= aligned_offset; 3845299742Sdim } 3846299742Sdim 3847299742Sdim if (fill_buffer) 3848299742Sdim { 3849299742Sdim char dummy; 3850299742Sdim apr_status_t status; 3851299742Sdim 3852299742Sdim /* seek to the start of the block and cause APR to read 1 block */ 3853299742Sdim SVN_ERR(svn_io_file_seek(file, APR_SET, &aligned_offset, 3854299742Sdim scratch_pool)); 3855299742Sdim status = apr_file_getc(&dummy, file); 3856299742Sdim 3857299742Sdim /* read may fail if we seek to or behind EOF. That's ok then. */ 3858299742Sdim if (status != APR_SUCCESS && !APR_STATUS_IS_EOF(status)) 3859299742Sdim return do_io_file_wrapper_cleanup(file, status, 3860299742Sdim N_("Can't read file '%s'"), 3861299742Sdim N_("Can't read stream"), 3862299742Sdim scratch_pool); 3863299742Sdim } 3864299742Sdim 3865299742Sdim /* finally, seek to the OFFSET the caller wants */ 3866299742Sdim desired_offset = offset; 3867299742Sdim SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool)); 3868299742Sdim if (desired_offset != offset) 3869299742Sdim return do_io_file_wrapper_cleanup(file, APR_EOF, 3870299742Sdim N_("Can't seek in file '%s'"), 3871299742Sdim N_("Can't seek in stream"), 3872299742Sdim scratch_pool); 3873299742Sdim 3874299742Sdim /* return the buffer start that we (probably) enforced */ 3875299742Sdim if (buffer_start) 3876299742Sdim *buffer_start = aligned_offset; 3877299742Sdim 3878299742Sdim return SVN_NO_ERROR; 3879299742Sdim} 3880299742Sdim 3881299742Sdim 3882251881Spetersvn_error_t * 3883251881Spetersvn_io_file_write(apr_file_t *file, const void *buf, 3884251881Speter apr_size_t *nbytes, apr_pool_t *pool) 3885251881Speter{ 3886251881Speter return svn_error_trace(do_io_file_wrapper_cleanup( 3887251881Speter file, apr_file_write(file, buf, nbytes), 3888251881Speter N_("Can't write to file '%s'"), 3889251881Speter N_("Can't write to stream"), 3890251881Speter pool)); 3891251881Speter} 3892251881Speter 3893299742Sdimsvn_error_t * 3894299742Sdimsvn_io_file_flush(apr_file_t *file, 3895299742Sdim apr_pool_t *scratch_pool) 3896299742Sdim{ 3897299742Sdim return svn_error_trace(do_io_file_wrapper_cleanup( 3898299742Sdim file, apr_file_flush(file), 3899299742Sdim N_("Can't flush file '%s'"), 3900299742Sdim N_("Can't flush stream"), 3901299742Sdim scratch_pool)); 3902299742Sdim} 3903251881Speter 3904251881Spetersvn_error_t * 3905251881Spetersvn_io_file_write_full(apr_file_t *file, const void *buf, 3906251881Speter apr_size_t nbytes, apr_size_t *bytes_written, 3907251881Speter apr_pool_t *pool) 3908251881Speter{ 3909251881Speter /* We cannot simply call apr_file_write_full on Win32 as it may fail 3910251881Speter for larger values of NBYTES. In that case, we have to emulate the 3911251881Speter "_full" part here. Thus, always call apr_file_write directly on 3912251881Speter Win32 as this minimizes overhead for small data buffers. */ 3913251881Speter#ifdef WIN32 3914251881Speter#define MAXBUFSIZE 30*1024 3915251881Speter apr_size_t bw = nbytes; 3916251881Speter apr_size_t to_write = nbytes; 3917251881Speter 3918251881Speter /* try a simple "write everything at once" first */ 3919251881Speter apr_status_t rv = apr_file_write(file, buf, &bw); 3920251881Speter buf = (char *)buf + bw; 3921251881Speter to_write -= bw; 3922251881Speter 3923251881Speter /* if the OS cannot handle that, use smaller chunks */ 3924251881Speter if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY) 3925251881Speter && nbytes > MAXBUFSIZE) 3926251881Speter { 3927251881Speter do { 3928251881Speter bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write; 3929251881Speter rv = apr_file_write(file, buf, &bw); 3930251881Speter buf = (char *)buf + bw; 3931251881Speter to_write -= bw; 3932251881Speter } while (rv == APR_SUCCESS && to_write > 0); 3933251881Speter } 3934251881Speter 3935251881Speter /* bytes_written may actually be NULL */ 3936251881Speter if (bytes_written) 3937251881Speter *bytes_written = nbytes - to_write; 3938251881Speter#undef MAXBUFSIZE 3939251881Speter#else 3940251881Speter apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written); 3941251881Speter#endif 3942251881Speter 3943251881Speter return svn_error_trace(do_io_file_wrapper_cleanup( 3944251881Speter file, rv, 3945251881Speter N_("Can't write to file '%s'"), 3946251881Speter N_("Can't write to stream"), 3947251881Speter pool)); 3948251881Speter} 3949251881Speter 3950251881Speter 3951251881Spetersvn_error_t * 3952251881Spetersvn_io_write_unique(const char **tmp_path, 3953251881Speter const char *dirpath, 3954251881Speter const void *buf, 3955251881Speter apr_size_t nbytes, 3956251881Speter svn_io_file_del_t delete_when, 3957251881Speter apr_pool_t *pool) 3958251881Speter{ 3959251881Speter apr_file_t *new_file; 3960251881Speter svn_error_t *err; 3961251881Speter 3962251881Speter SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath, 3963251881Speter delete_when, pool, pool)); 3964251881Speter 3965251881Speter err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool); 3966251881Speter 3967251881Speter if (!err) 3968299742Sdim { 3969299742Sdim /* svn_io_file_flush_to_disk() can be very expensive, so use the 3970299742Sdim cheaper standard flush if the file is created as temporary file 3971299742Sdim anyway */ 3972299742Sdim if (delete_when == svn_io_file_del_none) 3973299742Sdim err = svn_io_file_flush_to_disk(new_file, pool); 3974299742Sdim else 3975299742Sdim err = svn_io_file_flush(new_file, pool); 3976299742Sdim } 3977251881Speter 3978251881Speter return svn_error_trace( 3979251881Speter svn_error_compose_create(err, 3980251881Speter svn_io_file_close(new_file, pool))); 3981251881Speter} 3982251881Speter 3983299742Sdimsvn_error_t * 3984299742Sdimsvn_io_write_atomic(const char *final_path, 3985299742Sdim const void *buf, 3986299742Sdim apr_size_t nbytes, 3987299742Sdim const char *copy_perms_path, 3988299742Sdim apr_pool_t *scratch_pool) 3989299742Sdim{ 3990299742Sdim apr_file_t *tmp_file; 3991299742Sdim const char *tmp_path; 3992299742Sdim svn_error_t *err; 3993299742Sdim const char *dirname = svn_dirent_dirname(final_path, scratch_pool); 3994251881Speter 3995299742Sdim SVN_ERR(svn_io_open_unique_file3(&tmp_file, &tmp_path, dirname, 3996299742Sdim svn_io_file_del_none, 3997299742Sdim scratch_pool, scratch_pool)); 3998299742Sdim 3999299742Sdim err = svn_io_file_write_full(tmp_file, buf, nbytes, NULL, scratch_pool); 4000299742Sdim 4001299742Sdim if (!err) 4002299742Sdim err = svn_io_file_flush_to_disk(tmp_file, scratch_pool); 4003299742Sdim 4004299742Sdim err = svn_error_compose_create(err, 4005299742Sdim svn_io_file_close(tmp_file, scratch_pool)); 4006299742Sdim 4007299742Sdim if (!err && copy_perms_path) 4008299742Sdim err = svn_io_copy_perms(copy_perms_path, tmp_path, scratch_pool); 4009299742Sdim 4010299742Sdim if (!err) 4011299742Sdim err = svn_io_file_rename(tmp_path, final_path, scratch_pool); 4012299742Sdim 4013299742Sdim if (err) 4014299742Sdim { 4015299742Sdim err = svn_error_compose_create(err, 4016299742Sdim svn_io_remove_file2(tmp_path, TRUE, 4017299742Sdim scratch_pool)); 4018299742Sdim 4019299742Sdim return svn_error_createf(err->apr_err, err, 4020299742Sdim _("Can't write '%s' atomically"), 4021299742Sdim svn_dirent_local_style(final_path, 4022299742Sdim scratch_pool)); 4023299742Sdim } 4024299742Sdim 4025299742Sdim#ifdef __linux__ 4026299742Sdim { 4027299742Sdim /* Linux has the unusual feature that fsync() on a file is not 4028299742Sdim enough to ensure that a file's directory entries have been 4029299742Sdim flushed to disk; you have to fsync the directory as well. 4030299742Sdim On other operating systems, we'd only be asking for trouble 4031299742Sdim by trying to open and fsync a directory. */ 4032299742Sdim apr_file_t *file; 4033299742Sdim 4034299742Sdim SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT, 4035299742Sdim scratch_pool)); 4036299742Sdim SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool)); 4037299742Sdim SVN_ERR(svn_io_file_close(file, scratch_pool)); 4038299742Sdim } 4039299742Sdim#endif 4040299742Sdim 4041299742Sdim return SVN_NO_ERROR; 4042299742Sdim} 4043299742Sdim 4044251881Spetersvn_error_t * 4045251881Spetersvn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool) 4046251881Speter{ 4047251881Speter /* This is a work-around. APR would flush the write buffer 4048251881Speter _after_ truncating the file causing now invalid buffered 4049251881Speter data to be written behind OFFSET. */ 4050251881Speter SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file), 4051251881Speter N_("Can't flush file '%s'"), 4052251881Speter N_("Can't flush stream"), 4053251881Speter pool)); 4054251881Speter 4055251881Speter return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset), 4056251881Speter N_("Can't truncate file '%s'"), 4057251881Speter N_("Can't truncate stream"), 4058251881Speter pool); 4059251881Speter} 4060251881Speter 4061251881Speter 4062251881Spetersvn_error_t * 4063251881Spetersvn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit, 4064251881Speter apr_pool_t *pool) 4065251881Speter{ 4066251881Speter /* variables */ 4067251881Speter apr_size_t total_read = 0; 4068251881Speter svn_boolean_t eof = FALSE; 4069251881Speter const char *name; 4070251881Speter svn_error_t *err; 4071251881Speter apr_size_t buf_size = *limit; 4072251881Speter 4073251881Speter while (buf_size > 0) 4074251881Speter { 4075251881Speter /* read a fair chunk of data at once. But don't get too ambitious 4076251881Speter * as that would result in too much waste. Also make sure we can 4077251881Speter * put a NUL after the last byte read. 4078251881Speter */ 4079251881Speter apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128; 4080251881Speter apr_size_t bytes_read = 0; 4081251881Speter char *eol; 4082251881Speter 4083253734Speter if (to_read == 0) 4084253734Speter break; 4085253734Speter 4086251881Speter /* read data block (or just a part of it) */ 4087251881Speter SVN_ERR(svn_io_file_read_full2(file, buf, to_read, 4088251881Speter &bytes_read, &eof, pool)); 4089251881Speter 4090251881Speter /* look or a newline char */ 4091251881Speter buf[bytes_read] = 0; 4092251881Speter eol = strchr(buf, '\n'); 4093251881Speter if (eol) 4094251881Speter { 4095251881Speter apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read; 4096251881Speter 4097251881Speter *eol = 0; 4098251881Speter *limit = total_read + (eol - buf); 4099251881Speter 4100251881Speter /* correct the file pointer: 4101251881Speter * appear as though we just had read the newline char 4102251881Speter */ 4103251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool)); 4104251881Speter 4105251881Speter return SVN_NO_ERROR; 4106251881Speter } 4107251881Speter else if (eof) 4108251881Speter { 4109251881Speter /* no EOL found but we hit the end of the file. 4110251881Speter * Generate a nice EOF error object and return it. 4111251881Speter */ 4112251881Speter char dummy; 4113251881Speter SVN_ERR(svn_io_file_getc(&dummy, file, pool)); 4114251881Speter } 4115251881Speter 4116251881Speter /* next data chunk */ 4117251881Speter buf_size -= bytes_read; 4118251881Speter buf += bytes_read; 4119251881Speter total_read += bytes_read; 4120251881Speter } 4121251881Speter 4122251881Speter /* buffer limit has been exceeded without finding the EOL */ 4123251881Speter err = svn_io_file_name_get(&name, file, pool); 4124251881Speter if (err) 4125251881Speter name = NULL; 4126251881Speter svn_error_clear(err); 4127251881Speter 4128251881Speter if (name) 4129251881Speter return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, 4130251881Speter _("Can't read length line in file '%s'"), 4131251881Speter svn_dirent_local_style(name, pool)); 4132251881Speter else 4133251881Speter return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, 4134251881Speter _("Can't read length line in stream")); 4135251881Speter} 4136251881Speter 4137251881Speter 4138251881Spetersvn_error_t * 4139251881Spetersvn_io_stat(apr_finfo_t *finfo, const char *fname, 4140251881Speter apr_int32_t wanted, apr_pool_t *pool) 4141251881Speter{ 4142251881Speter apr_status_t status; 4143251881Speter const char *fname_apr; 4144251881Speter 4145251881Speter /* APR doesn't like "" directories */ 4146251881Speter if (fname[0] == '\0') 4147251881Speter fname = "."; 4148251881Speter 4149251881Speter SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); 4150251881Speter 4151251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 4152251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 4153251881Speter 4154251881Speter status = apr_stat(finfo, fname_apr, wanted, pool); 4155251881Speter if (status) 4156251881Speter return svn_error_wrap_apr(status, _("Can't stat '%s'"), 4157251881Speter svn_dirent_local_style(fname, pool)); 4158251881Speter 4159251881Speter return SVN_NO_ERROR; 4160251881Speter} 4161251881Speter 4162251881Speter 4163251881Spetersvn_error_t * 4164251881Spetersvn_io_file_rename(const char *from_path, const char *to_path, 4165251881Speter apr_pool_t *pool) 4166251881Speter{ 4167251881Speter apr_status_t status = APR_SUCCESS; 4168251881Speter const char *from_path_apr, *to_path_apr; 4169251881Speter 4170251881Speter SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool)); 4171251881Speter SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool)); 4172251881Speter 4173251881Speter status = apr_file_rename(from_path_apr, to_path_apr, pool); 4174251881Speter 4175251881Speter#if defined(WIN32) || defined(__OS2__) 4176251881Speter /* If the target file is read only NTFS reports EACCESS and 4177251881Speter FAT/FAT32 reports EEXIST */ 4178251881Speter if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status)) 4179251881Speter { 4180251881Speter /* Set the destination file writable because Windows will not 4181251881Speter allow us to rename when to_path is read-only, but will 4182251881Speter allow renaming when from_path is read only. */ 4183251881Speter SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool)); 4184251881Speter 4185251881Speter status = apr_file_rename(from_path_apr, to_path_apr, pool); 4186251881Speter } 4187251881Speter WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool)); 4188251881Speter#endif /* WIN32 || __OS2__ */ 4189251881Speter 4190251881Speter if (status) 4191251881Speter return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"), 4192251881Speter svn_dirent_local_style(from_path, pool), 4193251881Speter svn_dirent_local_style(to_path, pool)); 4194251881Speter 4195251881Speter return SVN_NO_ERROR; 4196251881Speter} 4197251881Speter 4198251881Speter 4199251881Spetersvn_error_t * 4200251881Spetersvn_io_file_move(const char *from_path, const char *to_path, 4201251881Speter apr_pool_t *pool) 4202251881Speter{ 4203251881Speter svn_error_t *err = svn_io_file_rename(from_path, to_path, pool); 4204251881Speter 4205251881Speter if (err && APR_STATUS_IS_EXDEV(err->apr_err)) 4206251881Speter { 4207251881Speter const char *tmp_to_path; 4208251881Speter 4209251881Speter svn_error_clear(err); 4210251881Speter 4211251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path, 4212251881Speter svn_dirent_dirname(to_path, pool), 4213251881Speter svn_io_file_del_none, 4214251881Speter pool, pool)); 4215251881Speter 4216251881Speter err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool); 4217251881Speter if (err) 4218251881Speter goto failed_tmp; 4219251881Speter 4220251881Speter err = svn_io_file_rename(tmp_to_path, to_path, pool); 4221251881Speter if (err) 4222251881Speter goto failed_tmp; 4223251881Speter 4224251881Speter err = svn_io_remove_file2(from_path, FALSE, pool); 4225251881Speter if (! err) 4226251881Speter return SVN_NO_ERROR; 4227251881Speter 4228251881Speter svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool)); 4229251881Speter 4230251881Speter return err; 4231251881Speter 4232251881Speter failed_tmp: 4233251881Speter svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool)); 4234251881Speter } 4235251881Speter 4236251881Speter return err; 4237251881Speter} 4238251881Speter 4239251881Speter/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden. 4240251881Speter HIDDEN determines if the hidden attribute 4241251881Speter should be set on the newly created directory. */ 4242251881Speterstatic svn_error_t * 4243251881Speterdir_make(const char *path, apr_fileperms_t perm, 4244251881Speter svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool) 4245251881Speter{ 4246251881Speter apr_status_t status; 4247251881Speter const char *path_apr; 4248251881Speter 4249251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 4250251881Speter 4251251881Speter /* APR doesn't like "" directories */ 4252251881Speter if (path_apr[0] == '\0') 4253251881Speter path_apr = "."; 4254251881Speter 4255251881Speter#if (APR_OS_DEFAULT & APR_WSTICKY) 4256251881Speter /* The APR shipped with httpd 2.0.50 contains a bug where 4257251881Speter APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits. 4258251881Speter There is a special case for file creation, but not directory 4259251881Speter creation, so directories wind up getting created with the sticky 4260251881Speter bit set. (There is no such thing as a setuid directory, and the 4261251881Speter setgid bit is apparently ignored at mkdir() time.) If we detect 4262251881Speter this problem, work around it by unsetting those bits if we are 4263251881Speter passed APR_OS_DEFAULT. */ 4264251881Speter if (perm == APR_OS_DEFAULT) 4265251881Speter perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY); 4266251881Speter#endif 4267251881Speter 4268251881Speter status = apr_dir_make(path_apr, perm, pool); 4269251881Speter 4270299742Sdim#ifdef WIN32 4271299742Sdim /* Don't retry on ERROR_ACCESS_DENIED, as that typically signals a 4272299742Sdim permanent error */ 4273299742Sdim if (status == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION)) 4274299742Sdim WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool)); 4275299742Sdim#endif 4276299742Sdim 4277251881Speter if (status) 4278251881Speter return svn_error_wrap_apr(status, _("Can't create directory '%s'"), 4279251881Speter svn_dirent_local_style(path, pool)); 4280251881Speter 4281251881Speter#ifdef APR_FILE_ATTR_HIDDEN 4282251881Speter if (hidden) 4283251881Speter { 4284251881Speter#ifndef WIN32 4285251881Speter status = apr_file_attrs_set(path_apr, 4286251881Speter APR_FILE_ATTR_HIDDEN, 4287251881Speter APR_FILE_ATTR_HIDDEN, 4288251881Speter pool); 4289299742Sdim if (status) 4290299742Sdim return svn_error_wrap_apr(status, _("Can't hide directory '%s'"), 4291299742Sdim svn_dirent_local_style(path, pool)); 4292251881Speter#else 4293251881Speter /* on Windows, use our wrapper so we can also set the 4294251881Speter FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */ 4295299742Sdim svn_error_t *err = 4296299742Sdim io_win_file_attrs_set(path_apr, 4297299742Sdim FILE_ATTRIBUTE_HIDDEN | 4298299742Sdim FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 4299299742Sdim FILE_ATTRIBUTE_HIDDEN | 4300299742Sdim FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 4301299742Sdim pool); 4302299742Sdim if (err) 4303299742Sdim return svn_error_createf(err->apr_err, err, 4304299742Sdim _("Can't hide directory '%s'"), 4305299742Sdim svn_dirent_local_style(path, pool)); 4306299742Sdim#endif /* WIN32 */ 4307251881Speter } 4308299742Sdim#endif /* APR_FILE_ATTR_HIDDEN */ 4309251881Speter 4310251881Speter/* Windows does not implement sgid. Skip here because retrieving 4311251881Speter the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented 4312251881Speter to be 'incredibly expensive'. */ 4313251881Speter#ifndef WIN32 4314251881Speter if (sgid) 4315251881Speter { 4316251881Speter apr_finfo_t finfo; 4317251881Speter 4318251881Speter /* Per our contract, don't do error-checking. Some filesystems 4319251881Speter * don't support the sgid bit, and that's okay. */ 4320251881Speter status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool); 4321251881Speter 4322251881Speter if (!status) 4323251881Speter apr_file_perms_set(path_apr, finfo.protection | APR_GSETID); 4324251881Speter } 4325251881Speter#endif 4326251881Speter 4327251881Speter return SVN_NO_ERROR; 4328251881Speter} 4329251881Speter 4330251881Spetersvn_error_t * 4331251881Spetersvn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool) 4332251881Speter{ 4333251881Speter return dir_make(path, perm, FALSE, FALSE, pool); 4334251881Speter} 4335251881Speter 4336251881Spetersvn_error_t * 4337251881Spetersvn_io_dir_make_hidden(const char *path, apr_fileperms_t perm, 4338251881Speter apr_pool_t *pool) 4339251881Speter{ 4340251881Speter return dir_make(path, perm, TRUE, FALSE, pool); 4341251881Speter} 4342251881Speter 4343251881Spetersvn_error_t * 4344251881Spetersvn_io_dir_make_sgid(const char *path, apr_fileperms_t perm, 4345251881Speter apr_pool_t *pool) 4346251881Speter{ 4347251881Speter return dir_make(path, perm, FALSE, TRUE, pool); 4348251881Speter} 4349251881Speter 4350251881Speter 4351251881Spetersvn_error_t * 4352251881Spetersvn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool) 4353251881Speter{ 4354251881Speter apr_status_t status; 4355251881Speter const char *dirname_apr; 4356251881Speter 4357251881Speter /* APR doesn't like "" directories */ 4358251881Speter if (dirname[0] == '\0') 4359251881Speter dirname = "."; 4360251881Speter 4361251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 4362251881Speter 4363251881Speter status = apr_dir_open(new_dir, dirname_apr, pool); 4364251881Speter if (status) 4365251881Speter return svn_error_wrap_apr(status, _("Can't open directory '%s'"), 4366251881Speter svn_dirent_local_style(dirname, pool)); 4367251881Speter 4368251881Speter return SVN_NO_ERROR; 4369251881Speter} 4370251881Speter 4371251881Spetersvn_error_t * 4372251881Spetersvn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool) 4373251881Speter{ 4374251881Speter apr_status_t status; 4375251881Speter const char *dirname_apr; 4376251881Speter 4377251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 4378251881Speter 4379251881Speter status = apr_dir_remove(dirname_apr, pool); 4380251881Speter 4381251881Speter#ifdef WIN32 4382251881Speter { 4383251881Speter svn_boolean_t retry = TRUE; 4384251881Speter 4385251881Speter if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY) 4386251881Speter { 4387251881Speter apr_status_t empty_status = dir_is_empty(dirname_apr, pool); 4388251881Speter 4389251881Speter if (APR_STATUS_IS_ENOTEMPTY(empty_status)) 4390251881Speter retry = FALSE; 4391251881Speter } 4392251881Speter 4393251881Speter if (retry) 4394251881Speter { 4395251881Speter WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool)); 4396251881Speter } 4397251881Speter } 4398251881Speter#endif 4399251881Speter if (status) 4400251881Speter return svn_error_wrap_apr(status, _("Can't remove directory '%s'"), 4401251881Speter svn_dirent_local_style(dirname, pool)); 4402251881Speter 4403251881Speter return SVN_NO_ERROR; 4404251881Speter} 4405251881Speter 4406251881Speter 4407251881Spetersvn_error_t * 4408251881Spetersvn_io_dir_read(apr_finfo_t *finfo, 4409251881Speter apr_int32_t wanted, 4410251881Speter apr_dir_t *thedir, 4411251881Speter apr_pool_t *pool) 4412251881Speter{ 4413251881Speter apr_status_t status; 4414251881Speter 4415251881Speter status = apr_dir_read(finfo, wanted, thedir); 4416251881Speter 4417251881Speter if (status) 4418251881Speter return svn_error_wrap_apr(status, _("Can't read directory")); 4419251881Speter 4420251881Speter /* It would be nice to use entry_name_to_utf8() below, but can we 4421251881Speter get the dir's path out of an apr_dir_t? I don't see a reliable 4422251881Speter way to do it. */ 4423251881Speter 4424251881Speter if (finfo->fname) 4425251881Speter SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool)); 4426251881Speter 4427251881Speter if (finfo->name) 4428251881Speter SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool)); 4429251881Speter 4430251881Speter return SVN_NO_ERROR; 4431251881Speter} 4432251881Speter 4433251881Spetersvn_error_t * 4434251881Spetersvn_io_dir_close(apr_dir_t *thedir) 4435251881Speter{ 4436251881Speter apr_status_t apr_err = apr_dir_close(thedir); 4437251881Speter if (apr_err) 4438251881Speter return svn_error_wrap_apr(apr_err, _("Error closing directory")); 4439251881Speter 4440251881Speter return SVN_NO_ERROR; 4441251881Speter} 4442251881Speter 4443251881Spetersvn_error_t * 4444251881Spetersvn_io_dir_walk2(const char *dirname, 4445251881Speter apr_int32_t wanted, 4446251881Speter svn_io_walk_func_t walk_func, 4447251881Speter void *walk_baton, 4448251881Speter apr_pool_t *pool) 4449251881Speter{ 4450251881Speter apr_status_t apr_err; 4451251881Speter apr_dir_t *handle; 4452251881Speter apr_pool_t *subpool; 4453251881Speter const char *dirname_apr; 4454251881Speter apr_finfo_t finfo; 4455251881Speter 4456251881Speter wanted |= APR_FINFO_TYPE | APR_FINFO_NAME; 4457251881Speter 4458251881Speter /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ 4459251881Speter wanted &= ~SVN__APR_FINFO_MASK_OUT; 4460251881Speter 4461251881Speter /* The documentation for apr_dir_read used to state that "." and ".." 4462251881Speter will be returned as the first two files, but it doesn't 4463251881Speter work that way in practice, in particular ext3 on Linux-2.6 doesn't 4464251881Speter follow the rules. For details see 4465251881Speter http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666 4466251881Speter 4467251881Speter If APR ever does implement "dot-first" then it would be possible to 4468251881Speter remove the svn_io_stat and walk_func calls and use the walk_func 4469251881Speter inside the loop. 4470251881Speter 4471251881Speter Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is 4472251881Speter documented to provide it, so we have to do a bit extra. */ 4473251881Speter SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool)); 4474251881Speter SVN_ERR(cstring_from_utf8(&finfo.name, 4475251881Speter svn_dirent_basename(dirname, pool), 4476251881Speter pool)); 4477251881Speter finfo.valid |= APR_FINFO_NAME; 4478251881Speter SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool)); 4479251881Speter 4480251881Speter SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); 4481251881Speter 4482251881Speter /* APR doesn't like "" directories */ 4483251881Speter if (dirname_apr[0] == '\0') 4484251881Speter dirname_apr = "."; 4485251881Speter 4486251881Speter apr_err = apr_dir_open(&handle, dirname_apr, pool); 4487251881Speter if (apr_err) 4488251881Speter return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"), 4489251881Speter svn_dirent_local_style(dirname, pool)); 4490251881Speter 4491251881Speter /* iteration subpool */ 4492251881Speter subpool = svn_pool_create(pool); 4493251881Speter 4494251881Speter while (1) 4495251881Speter { 4496251881Speter const char *name_utf8; 4497251881Speter const char *full_path; 4498251881Speter 4499251881Speter svn_pool_clear(subpool); 4500251881Speter 4501251881Speter apr_err = apr_dir_read(&finfo, wanted, handle); 4502251881Speter if (APR_STATUS_IS_ENOENT(apr_err)) 4503251881Speter break; 4504251881Speter else if (apr_err) 4505251881Speter { 4506251881Speter return svn_error_wrap_apr(apr_err, 4507251881Speter _("Can't read directory entry in '%s'"), 4508251881Speter svn_dirent_local_style(dirname, pool)); 4509251881Speter } 4510251881Speter 4511251881Speter if (finfo.filetype == APR_DIR) 4512251881Speter { 4513251881Speter if (finfo.name[0] == '.' 4514251881Speter && (finfo.name[1] == '\0' 4515251881Speter || (finfo.name[1] == '.' && finfo.name[2] == '\0'))) 4516251881Speter /* skip "." and ".." */ 4517251881Speter continue; 4518251881Speter 4519251881Speter /* some other directory. recurse. it will be passed to the 4520251881Speter callback inside the recursion. */ 4521251881Speter SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname, 4522251881Speter subpool)); 4523251881Speter full_path = svn_dirent_join(dirname, name_utf8, subpool); 4524251881Speter SVN_ERR(svn_io_dir_walk2(full_path, 4525251881Speter wanted, 4526251881Speter walk_func, 4527251881Speter walk_baton, 4528251881Speter subpool)); 4529251881Speter } 4530251881Speter else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) 4531251881Speter { 4532251881Speter /* some other directory. pass it to the callback. */ 4533251881Speter SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname, 4534251881Speter subpool)); 4535251881Speter full_path = svn_dirent_join(dirname, name_utf8, subpool); 4536251881Speter SVN_ERR((*walk_func)(walk_baton, 4537251881Speter full_path, 4538251881Speter &finfo, 4539251881Speter subpool)); 4540251881Speter } 4541251881Speter /* else: 4542251881Speter Some other type of file; skip it for now. We've reserved the 4543251881Speter right to expand our coverage here in the future, though, 4544251881Speter without revving this API. 4545251881Speter */ 4546251881Speter } 4547251881Speter 4548251881Speter svn_pool_destroy(subpool); 4549251881Speter 4550251881Speter apr_err = apr_dir_close(handle); 4551251881Speter if (apr_err) 4552251881Speter return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"), 4553251881Speter svn_dirent_local_style(dirname, pool)); 4554251881Speter 4555251881Speter return SVN_NO_ERROR; 4556251881Speter} 4557251881Speter 4558251881Speter 4559251881Speter 4560251881Speter/** 4561251881Speter * Determine if a directory is empty or not. 4562251881Speter * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not. 4563251881Speter * @param path The directory. 4564251881Speter * @param pool Used for temporary allocation. 4565251881Speter * @remark If path is not a directory, or some other error occurs, 4566251881Speter * then return the appropriate apr status code. 4567251881Speter * 4568251881Speter * (This function is written in APR style, in anticipation of 4569251881Speter * perhaps someday being moved to APR as 'apr_dir_is_empty'.) 4570251881Speter */ 4571251881Speterstatic apr_status_t 4572251881Speterdir_is_empty(const char *dir, apr_pool_t *pool) 4573251881Speter{ 4574251881Speter apr_status_t apr_err; 4575251881Speter apr_dir_t *dir_handle; 4576251881Speter apr_finfo_t finfo; 4577251881Speter apr_status_t retval = APR_SUCCESS; 4578251881Speter 4579251881Speter /* APR doesn't like "" directories */ 4580251881Speter if (dir[0] == '\0') 4581251881Speter dir = "."; 4582251881Speter 4583251881Speter apr_err = apr_dir_open(&dir_handle, dir, pool); 4584251881Speter if (apr_err != APR_SUCCESS) 4585251881Speter return apr_err; 4586251881Speter 4587251881Speter for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle); 4588251881Speter apr_err == APR_SUCCESS; 4589251881Speter apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle)) 4590251881Speter { 4591251881Speter /* Ignore entries for this dir and its parent, robustly. 4592251881Speter (APR promises that they'll come first, so technically 4593251881Speter this guard could be moved outside the loop. But Ryan Bloom 4594251881Speter says he doesn't believe it, and I believe him. */ 4595251881Speter if (! (finfo.name[0] == '.' 4596251881Speter && (finfo.name[1] == '\0' 4597251881Speter || (finfo.name[1] == '.' && finfo.name[2] == '\0')))) 4598251881Speter { 4599251881Speter retval = APR_ENOTEMPTY; 4600251881Speter break; 4601251881Speter } 4602251881Speter } 4603251881Speter 4604251881Speter /* Make sure we broke out of the loop for the right reason. */ 4605251881Speter if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err)) 4606251881Speter return apr_err; 4607251881Speter 4608251881Speter apr_err = apr_dir_close(dir_handle); 4609251881Speter if (apr_err != APR_SUCCESS) 4610251881Speter return apr_err; 4611251881Speter 4612251881Speter return retval; 4613251881Speter} 4614251881Speter 4615251881Speter 4616251881Spetersvn_error_t * 4617251881Spetersvn_io_dir_empty(svn_boolean_t *is_empty_p, 4618251881Speter const char *path, 4619251881Speter apr_pool_t *pool) 4620251881Speter{ 4621251881Speter apr_status_t status; 4622251881Speter const char *path_apr; 4623251881Speter 4624251881Speter SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); 4625251881Speter 4626251881Speter status = dir_is_empty(path_apr, pool); 4627251881Speter 4628251881Speter if (!status) 4629251881Speter *is_empty_p = TRUE; 4630251881Speter else if (APR_STATUS_IS_ENOTEMPTY(status)) 4631251881Speter *is_empty_p = FALSE; 4632251881Speter else 4633251881Speter return svn_error_wrap_apr(status, _("Can't check directory '%s'"), 4634251881Speter svn_dirent_local_style(path, pool)); 4635251881Speter 4636251881Speter return SVN_NO_ERROR; 4637251881Speter} 4638251881Speter 4639251881Speter 4640251881Speter 4641251881Speter/*** Version/format files ***/ 4642251881Speter 4643251881Spetersvn_error_t * 4644251881Spetersvn_io_write_version_file(const char *path, 4645251881Speter int version, 4646251881Speter apr_pool_t *pool) 4647251881Speter{ 4648251881Speter const char *path_tmp; 4649251881Speter const char *format_contents = apr_psprintf(pool, "%d\n", version); 4650251881Speter 4651251881Speter SVN_ERR_ASSERT(version >= 0); 4652251881Speter 4653251881Speter SVN_ERR(svn_io_write_unique(&path_tmp, 4654251881Speter svn_dirent_dirname(path, pool), 4655251881Speter format_contents, strlen(format_contents), 4656251881Speter svn_io_file_del_none, pool)); 4657251881Speter 4658251881Speter#if defined(WIN32) || defined(__OS2__) 4659251881Speter /* make the destination writable, but only on Windows, because 4660251881Speter Windows does not let us replace read-only files. */ 4661251881Speter SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool)); 4662251881Speter#endif /* WIN32 || __OS2__ */ 4663251881Speter 4664251881Speter /* rename the temp file as the real destination */ 4665251881Speter SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); 4666251881Speter 4667251881Speter /* And finally remove the perms to make it read only */ 4668251881Speter return svn_io_set_file_read_only(path, FALSE, pool); 4669251881Speter} 4670251881Speter 4671251881Speter 4672251881Spetersvn_error_t * 4673251881Spetersvn_io_read_version_file(int *version, 4674251881Speter const char *path, 4675251881Speter apr_pool_t *pool) 4676251881Speter{ 4677251881Speter apr_file_t *format_file; 4678251881Speter char buf[80]; 4679251881Speter apr_size_t len; 4680251881Speter svn_error_t *err; 4681251881Speter 4682251881Speter /* Read a chunk of data from PATH */ 4683251881Speter SVN_ERR(svn_io_file_open(&format_file, path, APR_READ, 4684251881Speter APR_OS_DEFAULT, pool)); 4685251881Speter len = sizeof(buf); 4686251881Speter err = svn_io_file_read(format_file, buf, &len, pool); 4687251881Speter 4688251881Speter /* Close the file. */ 4689251881Speter SVN_ERR(svn_error_compose_create(err, 4690251881Speter svn_io_file_close(format_file, pool))); 4691251881Speter 4692251881Speter /* If there was no data in PATH, return an error. */ 4693251881Speter if (len == 0) 4694251881Speter return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, 4695251881Speter _("Reading '%s'"), 4696251881Speter svn_dirent_local_style(path, pool)); 4697251881Speter 4698251881Speter /* Check that the first line contains only digits. */ 4699251881Speter { 4700251881Speter apr_size_t i; 4701251881Speter 4702251881Speter for (i = 0; i < len; ++i) 4703251881Speter { 4704251881Speter char c = buf[i]; 4705251881Speter 4706251881Speter if (i > 0 && (c == '\r' || c == '\n')) 4707251881Speter { 4708251881Speter buf[i] = '\0'; 4709251881Speter break; 4710251881Speter } 4711251881Speter if (! svn_ctype_isdigit(c)) 4712251881Speter return svn_error_createf 4713251881Speter (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 4714251881Speter _("First line of '%s' contains non-digit"), 4715251881Speter svn_dirent_local_style(path, pool)); 4716251881Speter } 4717251881Speter } 4718251881Speter 4719251881Speter /* Convert to integer. */ 4720251881Speter SVN_ERR(svn_cstring_atoi(version, buf)); 4721251881Speter 4722251881Speter return SVN_NO_ERROR; 4723251881Speter} 4724251881Speter 4725251881Speter 4726251881Speter/* Do a byte-for-byte comparison of FILE1 and FILE2. */ 4727251881Speterstatic svn_error_t * 4728251881Spetercontents_identical_p(svn_boolean_t *identical_p, 4729251881Speter const char *file1, 4730251881Speter const char *file2, 4731251881Speter apr_pool_t *pool) 4732251881Speter{ 4733251881Speter svn_error_t *err; 4734251881Speter apr_size_t bytes_read1, bytes_read2; 4735251881Speter char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 4736251881Speter char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); 4737251881Speter apr_file_t *file1_h; 4738251881Speter apr_file_t *file2_h; 4739251881Speter svn_boolean_t eof1 = FALSE; 4740251881Speter svn_boolean_t eof2 = FALSE; 4741251881Speter 4742251881Speter SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT, 4743251881Speter pool)); 4744251881Speter 4745251881Speter err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT, 4746251881Speter pool); 4747251881Speter 4748251881Speter if (err) 4749251881Speter return svn_error_trace( 4750251881Speter svn_error_compose_create(err, 4751251881Speter svn_io_file_close(file1_h, pool))); 4752251881Speter 4753251881Speter *identical_p = TRUE; /* assume TRUE, until disproved below */ 4754251881Speter while (!err && !eof1 && !eof2) 4755251881Speter { 4756251881Speter err = svn_io_file_read_full2(file1_h, buf1, 4757251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read1, 4758251881Speter &eof1, pool); 4759251881Speter if (err) 4760251881Speter break; 4761251881Speter 4762251881Speter err = svn_io_file_read_full2(file2_h, buf2, 4763251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read2, 4764251881Speter &eof2, pool); 4765251881Speter if (err) 4766251881Speter break; 4767251881Speter 4768251881Speter if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1)) 4769251881Speter { 4770251881Speter *identical_p = FALSE; 4771251881Speter break; 4772251881Speter } 4773251881Speter } 4774251881Speter 4775251881Speter /* Special case: one file being a prefix of the other and the shorter 4776251881Speter * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */ 4777251881Speter if (!err && (eof1 != eof2)) 4778251881Speter *identical_p = FALSE; 4779251881Speter 4780251881Speter return svn_error_trace( 4781251881Speter svn_error_compose_create( 4782251881Speter err, 4783251881Speter svn_error_compose_create(svn_io_file_close(file1_h, pool), 4784251881Speter svn_io_file_close(file2_h, pool)))); 4785251881Speter} 4786251881Speter 4787251881Speter 4788251881Speter 4789251881Speter/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */ 4790251881Speterstatic svn_error_t * 4791251881Spetercontents_three_identical_p(svn_boolean_t *identical_p12, 4792251881Speter svn_boolean_t *identical_p23, 4793251881Speter svn_boolean_t *identical_p13, 4794251881Speter const char *file1, 4795251881Speter const char *file2, 4796251881Speter const char *file3, 4797251881Speter apr_pool_t *scratch_pool) 4798251881Speter{ 4799251881Speter svn_error_t *err; 4800251881Speter char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4801251881Speter char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4802251881Speter char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); 4803251881Speter apr_file_t *file1_h; 4804251881Speter apr_file_t *file2_h; 4805251881Speter apr_file_t *file3_h; 4806251881Speter svn_boolean_t eof1 = FALSE; 4807251881Speter svn_boolean_t eof2 = FALSE; 4808251881Speter svn_boolean_t eof3 = FALSE; 4809251881Speter 4810251881Speter SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT, 4811251881Speter scratch_pool)); 4812251881Speter 4813251881Speter err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT, 4814251881Speter scratch_pool); 4815251881Speter 4816251881Speter if (err) 4817251881Speter return svn_error_trace( 4818251881Speter svn_error_compose_create(err, 4819251881Speter svn_io_file_close(file1_h, scratch_pool))); 4820251881Speter 4821251881Speter err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT, 4822251881Speter scratch_pool); 4823251881Speter 4824251881Speter if (err) 4825251881Speter return svn_error_trace( 4826251881Speter svn_error_compose_create( 4827251881Speter err, 4828251881Speter svn_error_compose_create(svn_io_file_close(file1_h, 4829251881Speter scratch_pool), 4830251881Speter svn_io_file_close(file2_h, 4831251881Speter scratch_pool)))); 4832251881Speter 4833251881Speter /* assume TRUE, until disproved below */ 4834251881Speter *identical_p12 = *identical_p23 = *identical_p13 = TRUE; 4835251881Speter /* We need to read as long as no error occurs, and as long as one of the 4836251881Speter * flags could still change due to a read operation */ 4837251881Speter while (!err 4838251881Speter && ((*identical_p12 && !eof1 && !eof2) 4839251881Speter || (*identical_p23 && !eof2 && !eof3) 4840251881Speter || (*identical_p13 && !eof1 && !eof3))) 4841251881Speter { 4842299742Sdim apr_size_t bytes_read1, bytes_read2, bytes_read3; 4843299742Sdim svn_boolean_t read_1, read_2, read_3; 4844299742Sdim 4845251881Speter read_1 = read_2 = read_3 = FALSE; 4846251881Speter 4847251881Speter /* As long as a file is not at the end yet, and it is still 4848251881Speter * potentially identical to another file, we read the next chunk.*/ 4849262253Speter if (!eof1 && (*identical_p12 || *identical_p13)) 4850251881Speter { 4851251881Speter err = svn_io_file_read_full2(file1_h, buf1, 4852251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read1, 4853251881Speter &eof1, scratch_pool); 4854251881Speter if (err) 4855251881Speter break; 4856251881Speter read_1 = TRUE; 4857251881Speter } 4858251881Speter 4859262253Speter if (!eof2 && (*identical_p12 || *identical_p23)) 4860251881Speter { 4861251881Speter err = svn_io_file_read_full2(file2_h, buf2, 4862251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read2, 4863251881Speter &eof2, scratch_pool); 4864251881Speter if (err) 4865251881Speter break; 4866251881Speter read_2 = TRUE; 4867251881Speter } 4868251881Speter 4869262253Speter if (!eof3 && (*identical_p13 || *identical_p23)) 4870251881Speter { 4871251881Speter err = svn_io_file_read_full2(file3_h, buf3, 4872251881Speter SVN__STREAM_CHUNK_SIZE, &bytes_read3, 4873251881Speter &eof3, scratch_pool); 4874251881Speter if (err) 4875251881Speter break; 4876251881Speter read_3 = TRUE; 4877251881Speter } 4878251881Speter 4879251881Speter /* If the files are still marked identical, and at least one of them 4880251881Speter * is not at the end of file, we check whether they differ, and set 4881251881Speter * their flag to false then. */ 4882251881Speter if (*identical_p12 4883251881Speter && (read_1 || read_2) 4884251881Speter && ((eof1 != eof2) 4885251881Speter || (bytes_read1 != bytes_read2) 4886251881Speter || memcmp(buf1, buf2, bytes_read1))) 4887251881Speter { 4888251881Speter *identical_p12 = FALSE; 4889251881Speter } 4890251881Speter 4891251881Speter if (*identical_p23 4892251881Speter && (read_2 || read_3) 4893251881Speter && ((eof2 != eof3) 4894251881Speter || (bytes_read2 != bytes_read3) 4895251881Speter || memcmp(buf2, buf3, bytes_read2))) 4896251881Speter { 4897251881Speter *identical_p23 = FALSE; 4898251881Speter } 4899251881Speter 4900251881Speter if (*identical_p13 4901251881Speter && (read_1 || read_3) 4902251881Speter && ((eof1 != eof3) 4903251881Speter || (bytes_read1 != bytes_read3) 4904251881Speter || memcmp(buf1, buf3, bytes_read3))) 4905251881Speter { 4906251881Speter *identical_p13 = FALSE; 4907251881Speter } 4908251881Speter } 4909251881Speter 4910251881Speter return svn_error_trace( 4911251881Speter svn_error_compose_create( 4912251881Speter err, 4913251881Speter svn_error_compose_create( 4914251881Speter svn_io_file_close(file1_h, scratch_pool), 4915251881Speter svn_error_compose_create( 4916251881Speter svn_io_file_close(file2_h, scratch_pool), 4917251881Speter svn_io_file_close(file3_h, scratch_pool))))); 4918251881Speter} 4919251881Speter 4920251881Speter 4921251881Speter 4922251881Spetersvn_error_t * 4923251881Spetersvn_io_files_contents_same_p(svn_boolean_t *same, 4924251881Speter const char *file1, 4925251881Speter const char *file2, 4926251881Speter apr_pool_t *pool) 4927251881Speter{ 4928251881Speter svn_boolean_t q; 4929251881Speter 4930251881Speter SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool)); 4931251881Speter 4932251881Speter if (q) 4933251881Speter { 4934251881Speter *same = FALSE; 4935251881Speter return SVN_NO_ERROR; 4936251881Speter } 4937251881Speter 4938251881Speter SVN_ERR(contents_identical_p(&q, file1, file2, pool)); 4939251881Speter 4940251881Speter if (q) 4941251881Speter *same = TRUE; 4942251881Speter else 4943251881Speter *same = FALSE; 4944251881Speter 4945251881Speter return SVN_NO_ERROR; 4946251881Speter} 4947251881Speter 4948251881Spetersvn_error_t * 4949251881Spetersvn_io_files_contents_three_same_p(svn_boolean_t *same12, 4950251881Speter svn_boolean_t *same23, 4951251881Speter svn_boolean_t *same13, 4952251881Speter const char *file1, 4953251881Speter const char *file2, 4954251881Speter const char *file3, 4955251881Speter apr_pool_t *scratch_pool) 4956251881Speter{ 4957251881Speter svn_boolean_t diff_size12, diff_size23, diff_size13; 4958251881Speter 4959251881Speter SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12, 4960251881Speter &diff_size23, 4961251881Speter &diff_size13, 4962251881Speter file1, 4963251881Speter file2, 4964251881Speter file3, 4965251881Speter scratch_pool)); 4966251881Speter 4967251881Speter if (diff_size12 && diff_size23 && diff_size13) 4968251881Speter { 4969251881Speter *same12 = *same23 = *same13 = FALSE; 4970251881Speter } 4971251881Speter else if (diff_size12 && diff_size23) 4972251881Speter { 4973251881Speter *same12 = *same23 = FALSE; 4974251881Speter SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool)); 4975251881Speter } 4976251881Speter else if (diff_size23 && diff_size13) 4977251881Speter { 4978251881Speter *same23 = *same13 = FALSE; 4979251881Speter SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool)); 4980251881Speter } 4981251881Speter else if (diff_size12 && diff_size13) 4982251881Speter { 4983251881Speter *same12 = *same13 = FALSE; 4984251881Speter SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool)); 4985251881Speter } 4986251881Speter else 4987251881Speter { 4988251881Speter SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13); 4989251881Speter SVN_ERR(contents_three_identical_p(same12, same23, same13, 4990251881Speter file1, file2, file3, 4991251881Speter scratch_pool)); 4992251881Speter } 4993251881Speter 4994251881Speter return SVN_NO_ERROR; 4995251881Speter} 4996251881Speter 4997251881Speter#ifdef WIN32 4998251881Speter/* Counter value of file_mktemp request (used in a threadsafe way), to make 4999251881Speter sure that a single process normally never generates the same tempname 5000251881Speter twice */ 5001251881Speterstatic volatile apr_uint32_t tempname_counter = 0; 5002251881Speter#endif 5003251881Speter 5004251881Speter/* Creates a new temporary file in DIRECTORY with apr flags FLAGS. 5005251881Speter Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name. 5006251881Speter Perform temporary allocations in SCRATCH_POOL and the result in 5007251881Speter RESULT_POOL. */ 5008251881Speterstatic svn_error_t * 5009251881Spetertemp_file_create(apr_file_t **new_file, 5010251881Speter const char **new_file_name, 5011251881Speter const char *directory, 5012251881Speter apr_int32_t flags, 5013251881Speter apr_pool_t *result_pool, 5014251881Speter apr_pool_t *scratch_pool) 5015251881Speter{ 5016251881Speter#ifndef WIN32 5017251881Speter const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool); 5018251881Speter const char *templ_apr; 5019251881Speter apr_status_t status; 5020251881Speter 5021251881Speter SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool)); 5022251881Speter 5023251881Speter /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the 5024251881Speter data available in POOL and we need a non-const pointer here, 5025251881Speter as apr changes the template to return the new filename. */ 5026251881Speter status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool); 5027251881Speter 5028251881Speter if (status) 5029251881Speter return svn_error_wrap_apr(status, _("Can't create temporary file from " 5030251881Speter "template '%s'"), templ); 5031251881Speter 5032251881Speter /* Translate the returned path back to utf-8 before returning it */ 5033251881Speter return svn_error_trace(svn_path_cstring_to_utf8(new_file_name, 5034251881Speter templ_apr, 5035251881Speter result_pool)); 5036251881Speter#else 5037251881Speter /* The Windows implementation of apr_file_mktemp doesn't handle access 5038251881Speter denied errors correctly. Therefore we implement our own temp file 5039251881Speter creation function here. */ 5040251881Speter 5041251881Speter /* ### Most of this is borrowed from the svn_io_open_uniquely_named(), 5042251881Speter ### the function we used before. But we try to guess a more unique 5043251881Speter ### name before trying if it exists. */ 5044251881Speter 5045251881Speter /* Offset by some time value and a unique request nr to make the number 5046251881Speter +- unique for both this process and on the computer */ 5047251881Speter int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter) 5048251881Speter + GetCurrentProcessId(); 5049251881Speter int i; 5050251881Speter 5051251881Speter /* ### Maybe use an iterpool? */ 5052251881Speter for (i = 0; i <= 99999; i++) 5053251881Speter { 5054251881Speter apr_uint32_t unique_nr; 5055251881Speter const char *unique_name; 5056251881Speter const char *unique_name_apr; 5057251881Speter apr_file_t *try_file; 5058251881Speter apr_status_t apr_err; 5059251881Speter 5060251881Speter /* Generate a number that should be unique for this application and 5061251881Speter usually for the entire computer to reduce the number of cycles 5062251881Speter through this loop. (A bit of calculation is much cheaper then 5063251881Speter disk io) */ 5064251881Speter unique_nr = baseNr + 3 * i; 5065251881Speter 5066251881Speter unique_name = svn_dirent_join(directory, 5067251881Speter apr_psprintf(scratch_pool, "svn-%X", 5068251881Speter unique_nr), 5069251881Speter scratch_pool); 5070251881Speter 5071251881Speter SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool)); 5072251881Speter 5073251881Speter apr_err = file_open(&try_file, unique_name_apr, flags, 5074251881Speter APR_OS_DEFAULT, FALSE, scratch_pool); 5075251881Speter 5076251881Speter if (APR_STATUS_IS_EEXIST(apr_err)) 5077251881Speter continue; 5078251881Speter else if (apr_err) 5079251881Speter { 5080251881Speter /* On Win32, CreateFile fails with an "Access Denied" error 5081251881Speter code, rather than "File Already Exists", if the colliding 5082251881Speter name belongs to a directory. */ 5083251881Speter 5084251881Speter if (APR_STATUS_IS_EACCES(apr_err)) 5085251881Speter { 5086251881Speter apr_finfo_t finfo; 5087251881Speter apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, 5088251881Speter APR_FINFO_TYPE, scratch_pool); 5089251881Speter 5090251881Speter if (!apr_err_2 && finfo.filetype == APR_DIR) 5091251881Speter continue; 5092251881Speter 5093251881Speter apr_err_2 = APR_TO_OS_ERROR(apr_err); 5094251881Speter 5095251881Speter if (apr_err_2 == ERROR_ACCESS_DENIED || 5096251881Speter apr_err_2 == ERROR_SHARING_VIOLATION) 5097251881Speter { 5098251881Speter /* The file is in use by another process or is hidden; 5099251881Speter create a new name, but don't do this 99999 times in 5100251881Speter case the folder is not writable */ 5101251881Speter i += 797; 5102251881Speter continue; 5103251881Speter } 5104251881Speter 5105251881Speter /* Else fall through and return the original error. */ 5106251881Speter } 5107251881Speter 5108251881Speter return svn_error_wrap_apr(apr_err, _("Can't open '%s'"), 5109251881Speter svn_dirent_local_style(unique_name, 5110251881Speter scratch_pool)); 5111251881Speter } 5112251881Speter else 5113251881Speter { 5114251881Speter /* Move file to the right pool */ 5115251881Speter apr_err = apr_file_setaside(new_file, try_file, result_pool); 5116251881Speter 5117251881Speter if (apr_err) 5118251881Speter return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"), 5119251881Speter svn_dirent_local_style(unique_name, 5120251881Speter scratch_pool)); 5121251881Speter 5122251881Speter *new_file_name = apr_pstrdup(result_pool, unique_name); 5123251881Speter 5124251881Speter return SVN_NO_ERROR; 5125251881Speter } 5126251881Speter } 5127251881Speter 5128251881Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 5129251881Speter NULL, 5130251881Speter _("Unable to make name in '%s'"), 5131251881Speter svn_dirent_local_style(directory, scratch_pool)); 5132251881Speter#endif 5133251881Speter} 5134251881Speter 5135251881Speter/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */ 5136251881Spetersvn_error_t * 5137251881Spetersvn_io_file_name_get(const char **filename, 5138251881Speter apr_file_t *file, 5139251881Speter apr_pool_t *pool) 5140251881Speter{ 5141251881Speter const char *fname_apr; 5142251881Speter apr_status_t status; 5143251881Speter 5144251881Speter status = apr_file_name_get(&fname_apr, file); 5145251881Speter if (status) 5146251881Speter return svn_error_wrap_apr(status, _("Can't get file name")); 5147251881Speter 5148251881Speter if (fname_apr) 5149251881Speter SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool)); 5150251881Speter else 5151251881Speter *filename = NULL; 5152251881Speter 5153251881Speter return SVN_NO_ERROR; 5154251881Speter} 5155251881Speter 5156251881Speter 5157251881Spetersvn_error_t * 5158251881Spetersvn_io_open_unique_file3(apr_file_t **file, 5159251881Speter const char **unique_path, 5160251881Speter const char *dirpath, 5161251881Speter svn_io_file_del_t delete_when, 5162251881Speter apr_pool_t *result_pool, 5163251881Speter apr_pool_t *scratch_pool) 5164251881Speter{ 5165251881Speter apr_file_t *tempfile; 5166251881Speter const char *tempname; 5167251881Speter struct temp_file_cleanup_s *baton = NULL; 5168251881Speter apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL | 5169251881Speter APR_BUFFERED | APR_BINARY); 5170251881Speter#if !defined(WIN32) && !defined(__OS2__) 5171251881Speter apr_fileperms_t perms; 5172251881Speter svn_boolean_t using_system_temp_dir = FALSE; 5173251881Speter#endif 5174251881Speter 5175251881Speter SVN_ERR_ASSERT(file || unique_path); 5176251881Speter if (file) 5177251881Speter *file = NULL; 5178251881Speter if (unique_path) 5179251881Speter *unique_path = NULL; 5180251881Speter 5181251881Speter if (dirpath == NULL) 5182251881Speter { 5183251881Speter#if !defined(WIN32) && !defined(__OS2__) 5184251881Speter using_system_temp_dir = TRUE; 5185251881Speter#endif 5186251881Speter SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool)); 5187251881Speter } 5188251881Speter 5189251881Speter switch (delete_when) 5190251881Speter { 5191251881Speter case svn_io_file_del_on_pool_cleanup: 5192251881Speter baton = apr_palloc(result_pool, sizeof(*baton)); 5193251881Speter baton->pool = result_pool; 5194251881Speter baton->fname_apr = NULL; 5195251881Speter 5196251881Speter /* Because cleanups are run LIFO, we need to make sure to register 5197251881Speter our cleanup before the apr_file_close cleanup: 5198251881Speter 5199251881Speter On Windows, you can't remove an open file. 5200251881Speter */ 5201251881Speter apr_pool_cleanup_register(result_pool, baton, 5202251881Speter temp_file_plain_cleanup_handler, 5203251881Speter temp_file_child_cleanup_handler); 5204251881Speter 5205251881Speter break; 5206251881Speter case svn_io_file_del_on_close: 5207251881Speter flags |= APR_DELONCLOSE; 5208251881Speter break; 5209251881Speter default: 5210251881Speter break; 5211251881Speter } 5212251881Speter 5213251881Speter SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags, 5214251881Speter result_pool, scratch_pool)); 5215251881Speter 5216251881Speter#if !defined(WIN32) && !defined(__OS2__) 5217251881Speter /* apr_file_mktemp() creates files with mode 0600. 5218251881Speter * This is appropriate if we're using a system temp dir since we don't 5219251881Speter * want to leak sensitive data into temp files other users can read. 5220251881Speter * If we're not using a system temp dir we're probably using the 5221251881Speter * .svn/tmp area and it's likely that the tempfile will end up being 5222251881Speter * copied or renamed into the working copy. 5223251881Speter * This would cause working files having mode 0600 while users might 5224251881Speter * expect to see 0644 or 0664. So we tweak perms of the tempfile in this 5225251881Speter * case, but only if the umask allows it. */ 5226251881Speter if (!using_system_temp_dir) 5227251881Speter { 5228289166Speter svn_error_t *err; 5229289166Speter 5230251881Speter SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool)); 5231289166Speter err = file_perms_set2(tempfile, perms, scratch_pool); 5232289166Speter if (err) 5233289166Speter { 5234289166Speter if (APR_STATUS_IS_INCOMPLETE(err->apr_err) || 5235289166Speter APR_STATUS_IS_ENOTIMPL(err->apr_err)) 5236289166Speter svn_error_clear(err); 5237289166Speter else 5238289166Speter { 5239299742Sdim return svn_error_quick_wrapf( 5240299742Sdim err, _("Can't set permissions on '%s'"), 5241299742Sdim svn_dirent_local_style(tempname, scratch_pool)); 5242289166Speter } 5243289166Speter } 5244251881Speter } 5245251881Speter#endif 5246251881Speter 5247251881Speter if (file) 5248251881Speter *file = tempfile; 5249251881Speter else 5250251881Speter SVN_ERR(svn_io_file_close(tempfile, scratch_pool)); 5251251881Speter 5252251881Speter if (unique_path) 5253251881Speter *unique_path = tempname; /* Was allocated in result_pool */ 5254251881Speter 5255251881Speter if (baton) 5256251881Speter SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool)); 5257251881Speter 5258251881Speter return SVN_NO_ERROR; 5259251881Speter} 5260251881Speter 5261251881Spetersvn_error_t * 5262251881Spetersvn_io_file_readline(apr_file_t *file, 5263251881Speter svn_stringbuf_t **stringbuf, 5264251881Speter const char **eol, 5265251881Speter svn_boolean_t *eof, 5266251881Speter apr_size_t max_len, 5267251881Speter apr_pool_t *result_pool, 5268251881Speter apr_pool_t *scratch_pool) 5269251881Speter{ 5270251881Speter svn_stringbuf_t *str; 5271251881Speter const char *eol_str; 5272251881Speter apr_size_t numbytes; 5273251881Speter char c; 5274251881Speter apr_size_t len; 5275251881Speter svn_boolean_t found_eof; 5276251881Speter 5277251881Speter str = svn_stringbuf_create_ensure(80, result_pool); 5278251881Speter 5279251881Speter /* Read bytes into STR up to and including, but not storing, 5280251881Speter * the next EOL sequence. */ 5281251881Speter eol_str = NULL; 5282251881Speter numbytes = 1; 5283251881Speter len = 0; 5284251881Speter found_eof = FALSE; 5285251881Speter while (!found_eof) 5286251881Speter { 5287251881Speter if (len < max_len) 5288251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 5289251881Speter &found_eof, scratch_pool)); 5290251881Speter len++; 5291251881Speter if (numbytes != 1 || len > max_len) 5292251881Speter { 5293251881Speter found_eof = TRUE; 5294251881Speter break; 5295251881Speter } 5296251881Speter 5297251881Speter if (c == '\n') 5298251881Speter { 5299251881Speter eol_str = "\n"; 5300251881Speter } 5301251881Speter else if (c == '\r') 5302251881Speter { 5303251881Speter eol_str = "\r"; 5304251881Speter 5305251881Speter if (!found_eof && len < max_len) 5306251881Speter { 5307251881Speter apr_off_t pos; 5308251881Speter 5309251881Speter /* Check for "\r\n" by peeking at the next byte. */ 5310251881Speter pos = 0; 5311251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); 5312251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 5313251881Speter &found_eof, scratch_pool)); 5314251881Speter if (numbytes == 1 && c == '\n') 5315251881Speter { 5316251881Speter eol_str = "\r\n"; 5317251881Speter len++; 5318251881Speter } 5319251881Speter else 5320251881Speter { 5321251881Speter /* Pretend we never peeked. */ 5322251881Speter SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); 5323251881Speter found_eof = FALSE; 5324251881Speter numbytes = 1; 5325251881Speter } 5326251881Speter } 5327251881Speter } 5328251881Speter else 5329251881Speter svn_stringbuf_appendbyte(str, c); 5330251881Speter 5331251881Speter if (eol_str) 5332251881Speter break; 5333251881Speter } 5334251881Speter 5335251881Speter if (eol) 5336251881Speter *eol = eol_str; 5337251881Speter if (eof) 5338251881Speter *eof = found_eof; 5339251881Speter *stringbuf = str; 5340251881Speter 5341251881Speter return SVN_NO_ERROR; 5342251881Speter} 5343