📄 io.c
字号:
/* * io.c: shared file reading, writing, and probing code. * * ==================================================================== * Copyright (c) 2000-2006 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://subversion.tigris.org/license-1.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://subversion.tigris.org/. * ==================================================================== */#include <stdio.h>#include <assert.h>#ifndef WIN32#include <unistd.h>#endif#ifndef APR_STATUS_IS_EPERM#include <errno.h>#ifdef EPERM#define APR_STATUS_IS_EPERM(s) ((s) == EPERM)#else#define APR_STATUS_IS_EPERM(s) (0)#endif#endif#include <apr_lib.h>#include <apr_pools.h>#include <apr_file_io.h>#include <apr_file_info.h>#include <apr_general.h>#include <apr_strings.h>#include <apr_portable.h>#include <apr_md5.h>#include "svn_types.h"#include "svn_path.h"#include "svn_string.h"#include "svn_error.h"#include "svn_io.h"#include "svn_pools.h"#include "svn_utf.h"#include "svn_config.h"#include "svn_private_config.h"/* Windows is 'aided' by a number of types of applications that follow other applications around and open up files they have changed for various reasons (the most intrusive are virus scanners). So, if one of these other apps has glommed onto our file we may get an 'access denied' error. This retry loop does not completely solve the problem (who knows how long the other app is going to hold onto it for), but goes a long way towards minimizing it. It is not an infinite loop because there might really be an error.*/#ifdef WIN32#define WIN32_RETRY_LOOP(err, expr) \ do \ { \ apr_status_t os_err = APR_TO_OS_ERROR(err); \ int sleep_count = 1000; \ int retries; \ for (retries = 0; \ retries < 100 && (os_err == ERROR_ACCESS_DENIED \ || os_err == ERROR_SHARING_VIOLATION); \ ++retries, os_err = APR_TO_OS_ERROR(err)) \ { \ apr_sleep(sleep_count); \ if (sleep_count < 128000) \ sleep_count *= 2; \ (err) = (expr); \ } \ } \ while (0)#else#define WIN32_RETRY_LOOP(err, expr) ((void)0)#endif/* Helper for svn_io_check_path() and svn_io_check_resolved_path(); essentially the same semantics as those two, with the obvious interpretation for RESOLVE_SYMLINKS. */static svn_error_t *io_check_path(const char *path, svn_boolean_t resolve_symlinks, svn_boolean_t *is_special_p, svn_node_kind_t *kind, apr_pool_t *pool){ apr_int32_t flags; apr_finfo_t finfo; apr_status_t apr_err; const char *path_apr; svn_boolean_t is_special = FALSE; if (path[0] == '\0') path = "."; /* Not using svn_io_stat() here because we want to check the apr_err return explicitly. */ SVN_ERR(svn_path_cstring_from_utf8(&path_apr, path, pool)); flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK); apr_err = apr_stat(&finfo, path_apr, flags, pool); if (APR_STATUS_IS_ENOENT(apr_err)) *kind = svn_node_none; else if (APR_STATUS_IS_ENOTDIR(apr_err)) *kind = svn_node_none; else if (apr_err) return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"), svn_path_local_style(path, pool)); else if (finfo.filetype == APR_NOFILE) *kind = svn_node_unknown; else if (finfo.filetype == APR_REG) *kind = svn_node_file; else if (finfo.filetype == APR_DIR) *kind = svn_node_dir; else if (finfo.filetype == APR_LNK) { is_special = TRUE; *kind = svn_node_file; } else *kind = svn_node_unknown; *is_special_p = is_special; return SVN_NO_ERROR;}/* Wrapper for apr_file_open() that handles CCSID problems on OS400 V5R4. */static apr_status_tfile_open(apr_file_t **f, const char *fname, apr_int32_t flag, apr_fileperms_t perm, apr_pool_t *pool) {#ifdef AS400/* All files in OS400 are tagged with a metadata CCSID (Coded Character Set * Identifier) which indicates the character encoding of the file's * contents. Even binary files are assigned a CCSID, typically the system * CCSID of the machine, which is some variant of EBCDIC (there are many * variants of EBCDIC: CCSID 37 - COM EUROPE EBCDIC, CCSID 273 - AUSTRIAN/ * GERMAN EBCDIC, CCSID 284 - SPANISH EBCDIC, etc.. In this comment the * assumed system CCSID is 37). * * APR on OS400 V5R4 is built with what IBM calls "UTF support" which means * that within the application text file contents are assumed to be in CCSID * 1208. * * On OS400 when using apr_file_open() to read, write, and/or create a file * there is an interplay between the APR_BINARY flag and the file's CCSID: * * File | APR_BINARY | Existing | Created | Conversion | Conversion * Exists? | Flag | File's | File's | When | When * | Passed | CCSID | CCSID | Writing | Reading * -------------------------------------------------------------------- * Yes | Yes | 1208 | N/A | None | None * Yes | Yes | 37 | N/A | None | None * Yes | No | 1208 | N/A | None | None * Yes | No | 37 | N/A | 1208-->37 | 37-->1208 * No | Yes | N/A | 37 | None | None * No | No | N/A | 1208 | None | None * * For example: If an existing file with CCSID 37 is opened for reading * without the APR_BINARY flag, the OS will attempt to convert * the file's contents from EBCDIC 37 to UTF-8. * * Now for the problem... * * - The files Subversion handles have either binary or UTF-8 content. * * - Subversion is not structured to differentiate between text files and * binary files. It just always passes the APR_BINARY flag when calling * apr_file_open(). * * So when Subversion creates a new file it always has a CCSID of 37 even * though the file *may* contain UTF-8 encoded text. This isn't a problem * for Subversion directly since it always passes APR_BINARY when opening * files, therefore the content is never converted when reading/writing the * file. * * The problem is that other OS400 applications/utilities rely on the CCSID * to represent the file's contents. For example, when a text editor opens * a svnserve.conf file tagged with CCSID 37 but actually containing UTF-8 * text, the OS will attempt to convert what it thinks is EBCDIC text to * UTF-8. Worse, if the file is empty, the text editor would save the * contents as EBCDIC. Later, when Subversion opens the conf file it's * reading in "UTF-8" data that is actually EBCDIC. * * The solution to this problem is to catch the case where Subversion wants * to create a file and make an initial call to apr_file_open() in text mode * (i.e. without the APR_BINARY flag), close the file, and then re-open the * file in binary mode (i.e. with the APR_BINARY flag). */ apr_status_t apr_err; if (flag & APR_CREATE) { /* If we are trying to create a file on OS400 ensure it's CCSID is * 1208. */ apr_err = apr_file_open(f, fname, flag & ~APR_BINARY, perm, pool); if (apr_err) return apr_err; apr_file_close(*f); /* Unset APR_EXCL so the next call to apr_file_open() doesn't * return an error. */ flag &= ~APR_EXCL; }#endif /* AS400 */ return apr_file_open(f, fname, flag, perm, pool);}svn_error_t *svn_io_check_resolved_path(const char *path, svn_node_kind_t *kind, apr_pool_t *pool){ svn_boolean_t ignored; return io_check_path(path, TRUE, &ignored, kind, pool);}svn_error_t *svn_io_check_path(const char *path, svn_node_kind_t *kind, apr_pool_t *pool){ svn_boolean_t ignored; return io_check_path(path, FALSE, &ignored, kind, pool);}svn_error_t *svn_io_check_special_path(const char *path, svn_node_kind_t *kind, svn_boolean_t *is_special, apr_pool_t *pool){ return io_check_path(path, FALSE, is_special, kind, pool);}struct temp_file_cleanup_s{ apr_pool_t *pool; const char *name;};static apr_status_ttemp_file_plain_cleanup_handler(void *baton){ struct temp_file_cleanup_s *b = baton; return (b->name) ? apr_file_remove(b->name, b->pool) : APR_SUCCESS;}static apr_status_ttemp_file_child_cleanup_handler(void *baton){ struct temp_file_cleanup_s *b = baton; apr_pool_cleanup_kill(b->pool, b, temp_file_plain_cleanup_handler); return APR_SUCCESS;}svn_error_t *svn_io_open_unique_file2(apr_file_t **f, const char **unique_name_p, const char *path, const char *suffix, svn_io_file_del_t delete_when, apr_pool_t *pool){ unsigned int i; apr_file_t *file; const char *unique_name; const char *unique_name_apr; struct temp_file_cleanup_s *baton = NULL; assert(f || unique_name_p); if (delete_when == svn_io_file_del_on_pool_cleanup) { baton = apr_palloc(pool, sizeof(*baton)); baton->pool = pool; baton->name = NULL; /* Because cleanups are run LIFO, we need to make sure to register our cleanup before the apr_file_close cleanup: On Windows, you can't remove an open file. */ apr_pool_cleanup_register(pool, baton, temp_file_plain_cleanup_handler, temp_file_child_cleanup_handler); } for (i = 1; i <= 99999; i++) { apr_status_t apr_err; apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL | APR_BUFFERED); if (delete_when == svn_io_file_del_on_close) flag |= APR_DELONCLOSE; /* Special case the first attempt -- if we can avoid having a generated numeric portion at all, that's best. So first we try with just the suffix; then future tries add a number before the suffix. (A do-while loop could avoid the repeated conditional, but it's not worth the clarity loss.) If the first attempt fails, the first number will be "2". This is good, since "1" would misleadingly imply that the second attempt was actually the first... and if someone's got conflicts on their conflicts, we probably don't want to add to their confusion :-). */ if (i == 1) unique_name = apr_psprintf(pool, "%s%s", path, suffix); else unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix); /* Hmmm. Ideally, we would append to a native-encoding buf before starting iteration, then convert back to UTF-8 for return. But I suppose that would make the appending code sensitive to i18n in a way it shouldn't be... Oh well. */ SVN_ERR(svn_path_cstring_from_utf8(&unique_name_apr, unique_name, pool)); apr_err = file_open(&file, unique_name_apr, flag | APR_BINARY, APR_OS_DEFAULT, pool); if (APR_STATUS_IS_EEXIST(apr_err)) continue; else if (apr_err) { /* On Win32, CreateFile failswith an "Access Denied" error code, rather than "File Already Exists", if the colliding name belongs to a directory. */ if (APR_STATUS_IS_EACCES(apr_err)) { apr_finfo_t finfo; apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, APR_FINFO_TYPE, pool); if (!apr_err_2 && (finfo.filetype == APR_DIR)) continue; /* Else ignore apr_err_2; better to fall through and return the original error. */ } if (f) *f = NULL; if (unique_name_p) *unique_name_p = NULL; return svn_error_wrap_apr(apr_err, _("Can't open '%s'"), svn_path_local_style(unique_name, pool)); } else { if (delete_when == svn_io_file_del_on_pool_cleanup) baton->name = unique_name_apr; if (f) *f = file; else apr_file_close(file); if (unique_name_p) *unique_name_p = unique_name; return SVN_NO_ERROR; } } if (f) *f = NULL; if (unique_name_p) *unique_name_p = NULL; return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, NULL, _("Unable to make name for '%s'"), svn_path_local_style(path, pool));}svn_error_t *svn_io_open_unique_file(apr_file_t **f, const char **unique_name_p, const char *path,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -