blob: 6fc78828758c11cfb4010d85fecb818185f3c8fd [file] [log] [blame]
/*
* io.c: shared file reading, writing, and probing code.
*
* ====================================================================
* Copyright (c) 2000-2002 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 <string.h>
#include <assert.h>
#include <errno.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_file_info.h>
#include <apr_strings.h>
#include <apr_thread_proc.h>
#include <apr_portable.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_private_config.h" /* for SVN_CLIENT_DIFF */
struct svn_stream_t {
void *baton;
svn_read_fn_t read_fn;
svn_write_fn_t write_fn;
svn_close_fn_t close_fn;
};
svn_error_t *
svn_io_check_path (const char *path,
enum svn_node_kind *kind,
apr_pool_t *pool)
{
apr_finfo_t finfo;
apr_status_t apr_err;
if (path[0] == '\0')
path = ".";
apr_err = apr_stat (&finfo, path, APR_FINFO_MIN, pool);
if (apr_err && !APR_STATUS_IS_ENOENT(apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_check_path: problem checking path \"%s\"", path);
else if (APR_STATUS_IS_ENOENT(apr_err))
*kind = svn_node_none;
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;
#if 0
else if (finfo.filetype == APR_LINK)
*kind = svn_node_symlink; /* we support symlinks someday, but not yet */
#endif /* 0 */
else
*kind = svn_node_unknown;
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_open_unique_file (apr_file_t **f,
svn_stringbuf_t **unique_name,
const char *path,
const char *suffix,
svn_boolean_t delete_on_close,
apr_pool_t *pool)
{
char number_buf[6];
int i;
apr_size_t iterating_portion_idx;
/* The random portion doesn't have to be very random; it's just to
avoid a series of collisions where someone has filename NAME and
also NAME.00001.tmp, NAME.00002.tmp, etc, under version control
already, which might conceivably happen. The random portion is a
last-ditch safeguard against that case. It's okay, and even
preferable, for tmp files to collide with each other, though, so
that the iterating portion changes instead. Taking the pointer
as an unsigned short int has more or less this effect. */
int random_portion_width;
char *random_portion = apr_psprintf
(pool, "%hu%n",
(unsigned int)unique_name,
&random_portion_width);
*unique_name = svn_stringbuf_create (path, pool);
/* Not sure of a portable PATH_MAX constant to use here, so just
guessing at 255. */
if ((*unique_name)->len >= 255)
{
int chop_amt = ((*unique_name)->len - 255)
+ random_portion_width
+ 3 /* 2 dots */
+ 5 /* 5 digits of iteration portion */
+ strlen (suffix);
svn_stringbuf_chop (*unique_name, chop_amt);
}
iterating_portion_idx = (*unique_name)->len + random_portion_width + 2;
svn_stringbuf_appendcstr (*unique_name,
apr_psprintf (pool, ".%s.00000%s",
random_portion, suffix));
for (i = 1; i <= 99999; i++)
{
apr_status_t apr_err;
apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL);
if (delete_on_close)
flag |= APR_DELONCLOSE;
/* Tweak last attempted name to get the next one. */
sprintf (number_buf, "%05d", i);
(*unique_name)->data[iterating_portion_idx + 0] = number_buf[0];
(*unique_name)->data[iterating_portion_idx + 1] = number_buf[1];
(*unique_name)->data[iterating_portion_idx + 2] = number_buf[2];
(*unique_name)->data[iterating_portion_idx + 3] = number_buf[3];
(*unique_name)->data[iterating_portion_idx + 4] = number_buf[4];
apr_err = apr_file_open (f, (*unique_name)->data, flag,
APR_OS_DEFAULT, pool);
if (APR_STATUS_IS_EEXIST(apr_err))
continue;
else if (apr_err)
{
char *filename = (*unique_name)->data;
*f = NULL;
*unique_name = NULL;
return svn_error_createf (apr_err,
0,
NULL,
pool,
"svn_io_open_unique_file: "
"error attempting %s",
filename);
}
else
return SVN_NO_ERROR;
}
*f = NULL;
*unique_name = NULL;
return svn_error_createf (SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
0,
NULL,
pool,
"svn_io_open_unique_file: unable to make name for "
"%s", path);
}
/*** Copying and appending files. ***/
svn_error_t *
svn_io_copy_file (const char *src,
const char *dst,
svn_boolean_t copy_perms,
apr_pool_t *pool)
{
apr_status_t apr_err;
/* ### FIXME: apr_file_copy with perms may fail on Win32. We need a
platform-specific implementation to get the permissions right. */
#ifndef SVN_WIN32
apr_int32_t options = copy_perms ? APR_FILE_SOURCE_PERMS : APR_OS_DEFAULT;
#else
apr_int32_t options = APR_OS_DEFAULT;
#endif
apr_err = apr_file_copy (src, dst, options, pool);
if (apr_err)
return svn_error_createf
(apr_err, 0, NULL, pool, "svn_io_copy_file: copying %s to %s", src, dst);
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_append_file (svn_stringbuf_t *src, svn_stringbuf_t *dst, apr_pool_t *pool)
{
apr_status_t apr_err;
apr_err = apr_file_append (src->data, dst->data, APR_OS_DEFAULT, pool);
if (apr_err)
{
const char *msg
= apr_psprintf (pool, "svn_io_append_file: appending %s to %s",
src->data, dst->data);
return svn_error_create (apr_err, 0, NULL, pool, msg);
}
return SVN_NO_ERROR;
}
svn_error_t *svn_io_copy_dir_recursively (svn_stringbuf_t *src,
svn_stringbuf_t *dst_parent,
svn_stringbuf_t *dst_basename,
svn_boolean_t copy_perms,
apr_pool_t *pool)
{
enum svn_node_kind kind;
apr_status_t status;
apr_hash_t *dirents;
apr_hash_index_t *hi;
svn_stringbuf_t *dst_path, *src_target, *dst_target;
/* Make a subpool for recursion */
apr_pool_t *subpool = svn_pool_create (pool);
/* The 'dst_path' is simply dst_parent/dst_basename */
dst_path = svn_stringbuf_dup (dst_parent, pool);
svn_path_add_component (dst_path, dst_basename);
/* Sanity checks: SRC and DST_PARENT are directories, and
DST_BASENAME doesn't already exist in DST_PARENT. */
SVN_ERR (svn_io_check_path (src->data, &kind, subpool));
if (kind != svn_node_dir)
return svn_error_createf (SVN_ERR_WC_UNEXPECTED_KIND, 0, NULL, subpool,
"svn_io_copy_dir: '%s' is not a directory.",
src->data);
SVN_ERR (svn_io_check_path (dst_parent->data, &kind, subpool));
if (kind != svn_node_dir)
return svn_error_createf (SVN_ERR_WC_UNEXPECTED_KIND, 0, NULL, subpool,
"svn_io_copy_dir: '%s' is not a directory.",
dst_parent->data);
SVN_ERR (svn_io_check_path (dst_path->data, &kind, subpool));
if (kind != svn_node_none)
return svn_error_createf (SVN_ERR_ENTRY_EXISTS, 0, NULL, subpool,
"'%s' already exists.", dst_path->data);
/* Create the new directory. */
/* ### TODO: copy permissions? */
status = apr_dir_make (dst_path->data, APR_OS_DEFAULT, pool);
if (status)
return svn_error_createf (status, 0, NULL, pool,
"Unable to create directory '%s'",
dst_path->data);
/* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
SVN_ERR (svn_io_get_dirents (&dirents, src, subpool));
src_target = svn_stringbuf_dup (src, subpool);
dst_target = svn_stringbuf_dup (dst_path, subpool);
for (hi = apr_hash_first (subpool, dirents); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t klen;
void *val;
const char *entryname;
enum svn_node_kind *entrykind;
/* Get next entry and its kind */
apr_hash_this (hi, &key, &klen, &val);
entryname = (char *) key;
entrykind = (enum svn_node_kind *) val;
/* Telescope the entryname onto the source dir. */
svn_path_add_component_nts (src_target, entryname);
/* If it's a file, just copy it over. */
if (*entrykind == svn_node_file)
{
/* Telescope and de-telescope the dst_target in here */
svn_path_add_component_nts (dst_target, entryname);
SVN_ERR (svn_io_copy_file (src_target->data, dst_target->data,
copy_perms, subpool));
svn_path_remove_component (dst_target);
}
/* If it's a directory, recurse. */
else if (*entrykind == svn_node_dir)
SVN_ERR (svn_io_copy_dir_recursively (src_target,
dst_target,
svn_stringbuf_create (entryname,
subpool),
copy_perms,
subpool));
/* ### someday deal with other node kinds? */
/* De-telescope the source dir for the next iteration. */
svn_path_remove_component (src_target);
}
/* Free any memory used by recursion */
apr_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/*** Modtime checking. ***/
svn_error_t *
svn_io_file_affected_time (apr_time_t *apr_time,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
apr_finfo_t finfo;
apr_status_t apr_err;
apr_err = apr_stat (&finfo, path->data, APR_FINFO_MIN, pool);
if (apr_err)
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_file_affected_time: cannot stat %s", path->data);
if (finfo.mtime > finfo.ctime)
*apr_time = finfo.mtime;
else
*apr_time = finfo.ctime;
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_filesizes_different_p (svn_boolean_t *different_p,
const char *file1,
const char *file2,
apr_pool_t *pool)
{
apr_finfo_t finfo1;
apr_finfo_t finfo2;
apr_status_t status;
/* Stat both files */
status = apr_stat (&finfo1, file1, APR_FINFO_MIN, pool);
if (status)
{
/* If we got an error stat'ing a file, it could be because the
file was removed... or who knows. Whatever the case, we
don't know if the filesizes are definitely different, so
assume that they're not. */
*different_p = FALSE;
return SVN_NO_ERROR;
}
status = apr_stat (&finfo2, file2, APR_FINFO_MIN, pool);
if (status)
{
/* See previous comment. */
*different_p = FALSE;
return SVN_NO_ERROR;
}
/* Examine file sizes */
if (finfo1.size == finfo2.size)
*different_p = FALSE;
else
*different_p = TRUE;
return SVN_NO_ERROR;
}
/*** Permissions and modes. ***/
svn_error_t *
svn_io_set_file_read_only (const char *path,
svn_boolean_t ignore_enoent,
apr_pool_t *pool)
{
apr_status_t status;
status = apr_file_attrs_set (path,
APR_FILE_ATTR_READONLY,
APR_FILE_ATTR_READONLY,
pool);
if (status && status != APR_ENOTIMPL)
if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
return svn_error_createf (status, 0, NULL, pool,
"failed to set file '%s' read-only", path);
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_set_file_read_write (const char *path,
svn_boolean_t ignore_enoent,
apr_pool_t *pool)
{
apr_status_t status;
status = apr_file_attrs_set (path,
0,
APR_FILE_ATTR_READONLY,
pool);
if (status && status != APR_ENOTIMPL)
if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
return svn_error_createf (status, 0, NULL, pool,
"failed to set file '%s' read-write", path);
return SVN_NO_ERROR;
}
/*** Generic streams. ***/
svn_stream_t *
svn_stream_create (void *baton, apr_pool_t *pool)
{
svn_stream_t *stream;
stream = apr_palloc (pool, sizeof (*stream));
stream->baton = baton;
stream->read_fn = NULL;
stream->write_fn = NULL;
stream->close_fn = NULL;
return stream;
}
svn_stream_t *
svn_stream_dup (svn_stream_t *stream, apr_pool_t *pool)
{
svn_stream_t *new_stream;
new_stream = apr_palloc (pool, sizeof (*new_stream));
new_stream->baton = stream->baton;
new_stream->read_fn = stream->read_fn;
new_stream->write_fn = stream->write_fn;
new_stream->close_fn = stream->close_fn;
return stream;
}
void
svn_stream_set_baton (svn_stream_t *stream, void *baton)
{
stream->baton = baton;
}
void
svn_stream_set_read (svn_stream_t *stream, svn_read_fn_t read_fn)
{
stream->read_fn = read_fn;
}
void
svn_stream_set_write (svn_stream_t *stream, svn_write_fn_t write_fn)
{
stream->write_fn = write_fn;
}
void
svn_stream_set_close (svn_stream_t *stream, svn_close_fn_t close_fn)
{
stream->close_fn = close_fn;
}
svn_error_t *
svn_stream_read (svn_stream_t *stream, char *buffer, apr_size_t *len)
{
assert (stream->read_fn != NULL);
return stream->read_fn (stream->baton, buffer, len);
}
svn_error_t *
svn_stream_write (svn_stream_t *stream, const char *data, apr_size_t *len)
{
assert (stream->write_fn != NULL);
return stream->write_fn (stream->baton, data, len);
}
svn_error_t *
svn_stream_close (svn_stream_t *stream)
{
if (stream->close_fn == NULL)
return SVN_NO_ERROR;
return stream->close_fn (stream->baton);
}
/*** Generic readable empty stream ***/
static svn_error_t *
read_handler_empty (void *baton, char *buffer, apr_size_t *len)
{
*len = 0;
return SVN_NO_ERROR;
}
svn_stream_t *
svn_stream_empty (apr_pool_t *pool)
{
svn_stream_t *stream;
stream = svn_stream_create (NULL, pool);
svn_stream_set_read (stream, read_handler_empty);
return stream;
}
/*** Generic stream for APR files ***/
struct baton_apr {
apr_file_t *file;
apr_pool_t *pool;
};
static svn_error_t *
read_handler_apr (void *baton, char *buffer, apr_size_t *len)
{
struct baton_apr *btn = baton;
apr_status_t status;
status = apr_file_read_full (btn->file, buffer, *len, len);
if (!APR_STATUS_IS_SUCCESS(status) && !APR_STATUS_IS_EOF(status))
return svn_error_create (status, 0, NULL, btn->pool, "reading file");
else
return SVN_NO_ERROR;
}
static svn_error_t *
write_handler_apr (void *baton, const char *data, apr_size_t *len)
{
struct baton_apr *btn = baton;
apr_status_t status;
status = apr_file_write_full (btn->file, data, *len, len);
if (!APR_STATUS_IS_SUCCESS(status))
return svn_error_create (status, 0, NULL, btn->pool, "writing file");
else
return SVN_NO_ERROR;
}
svn_stream_t *
svn_stream_from_aprfile (apr_file_t *file, apr_pool_t *pool)
{
struct baton_apr *baton;
svn_stream_t *stream;
if (file == NULL)
return svn_stream_empty(pool);
baton = apr_palloc (pool, sizeof (*baton));
baton->file = file;
baton->pool = pool;
stream = svn_stream_create (baton, pool);
svn_stream_set_read (stream, read_handler_apr);
svn_stream_set_write (stream, write_handler_apr);
return stream;
}
/*** Generic stream for stdio files ***/
struct baton_stdio {
FILE *fp;
apr_pool_t *pool;
};
static svn_error_t *
read_handler_stdio (void *baton, char *buffer, apr_size_t *len)
{
struct baton_stdio *btn = baton;
svn_error_t *err = SVN_NO_ERROR;
apr_size_t count;
count = fread (buffer, 1, *len, btn->fp);
if (count < *len && ferror(btn->fp))
err = svn_error_create (0, errno, NULL, btn->pool, "reading file");
*len = count;
return err;
}
static svn_error_t *
write_handler_stdio (void *baton, const char *data, apr_size_t *len)
{
struct baton_stdio *btn = baton;
svn_error_t *err = SVN_NO_ERROR;
apr_size_t count;
count = fwrite (data, 1, *len, btn->fp);
if (count < *len)
err = svn_error_create (0, errno, NULL, btn->pool, "reading file");
*len = count;
return err;
}
svn_stream_t *svn_stream_from_stdio (FILE *fp, apr_pool_t *pool)
{
struct baton_stdio *baton;
svn_stream_t *stream;
if (fp == NULL)
return svn_stream_empty (pool);
baton = apr_palloc (pool, sizeof (*baton));
baton->fp = fp;
baton->pool = pool;
stream = svn_stream_create (baton, pool);
svn_stream_set_read (stream, read_handler_stdio);
svn_stream_set_write (stream, write_handler_stdio);
return stream;
}
/* TODO write test for these two functions, then refactor. */
svn_error_t *
svn_string_from_file (svn_stringbuf_t **result,
const char *filename,
apr_pool_t *pool)
{
apr_status_t apr_err;
apr_file_t *f = NULL;
/* If user passed '-', use stdin. We could use apr_file_open_stdin here, and
* in fact, it does work. Problem is that if the same command invokes the
* editor, stdin is crap, and the editor acts funny (if not die outright). I
* wanted to just disallow stdin reading and invoking the editor, but there's
* no easy callback for that right now. */
if (filename[0] == '-' && filename[1] == '\0')
return svn_error_create
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"reading from stdin is currently broken, so disabled");
apr_err = apr_file_open (&f, filename, APR_READ, APR_OS_DEFAULT, pool);
if (apr_err)
return svn_error_createf (apr_err, 0, NULL, pool,
"read_from_file: failed to open '%s'",
filename);
SVN_ERR (svn_string_from_aprfile (result, f, pool));
apr_err = apr_file_close (f);
if (apr_err)
return svn_error_createf (apr_err, 0, NULL, pool,
"svn_string_from_file: failed to close '%s'",
filename);
return SVN_NO_ERROR;
}
svn_error_t *
svn_string_from_aprfile (svn_stringbuf_t **result,
apr_file_t *file,
apr_pool_t *pool)
{
apr_size_t len;
apr_status_t apr_err;
svn_stringbuf_t *res = svn_stringbuf_create("", pool);
const char *fname;
char buf[BUFSIZ];
/* XXX: We should check the incoming data for being of type binary. */
apr_err = apr_file_name_get (&fname, file);
if (!APR_STATUS_IS_SUCCESS(apr_err))
return svn_error_create
(apr_err, 0, NULL, pool,
"svn_string_from_aprfile: failed to get filename");
/* If the apr_file_t was opened with apr_file_open_std{in,out,err}, then we
* wont get a filename for it. We assume that since we are reading, that in
* this case we would only ever be using stdin. */
if (NULL == fname)
fname = "stdin";
/* apr_file_read will not return data and eof in the same call. So this loop
* is safe from missing read data. */
len = sizeof(buf);
apr_err = apr_file_read (file, buf, &len);
while (APR_STATUS_IS_SUCCESS(apr_err))
{
svn_stringbuf_appendbytes(res, buf, len);
len = sizeof(buf);
apr_err = apr_file_read (file, buf, &len);
}
/* Having read all the data we *expect* EOF */
if (!APR_STATUS_IS_EOF(apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_string_from_aprfile: EOF not seen for '%s'", fname);
/* Null terminate the stringbuf. */
res->data[res->len] = 0;
*result = res;
return SVN_NO_ERROR;
}
/* Deletion. */
svn_error_t *
svn_io_remove_file (const char *path, apr_pool_t *pool)
{
apr_status_t apr_err;
/* Remove read-only flag on terminated file. */
SVN_ERR (svn_io_set_file_read_write (path, TRUE, pool));
apr_err = apr_file_remove (path, pool);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_remove_file: failed to remove file \"%s\"",
path);
return SVN_NO_ERROR;
}
/* Neither windows nor unix allows us to delete a non-empty
directory.
This is a function to perform the equivalent of 'rm -rf'. */
apr_status_t
apr_dir_remove_recursively (const char *path, apr_pool_t *pool)
{
apr_status_t status;
apr_dir_t *this_dir;
apr_finfo_t this_entry;
apr_pool_t *subpool;
apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
status = apr_pool_create (&subpool, pool);
if (! (APR_STATUS_IS_SUCCESS (status))) return status;
status = apr_dir_open (&this_dir, path, subpool);
if (! (APR_STATUS_IS_SUCCESS (status))) return status;
for (status = apr_dir_read (&this_entry, flags, this_dir);
APR_STATUS_IS_SUCCESS (status);
status = apr_dir_read (&this_entry, flags, this_dir))
{
char *fullpath = apr_pstrcat (subpool, path, "/", this_entry.name, NULL);
if (this_entry.filetype == APR_DIR)
{
if ((strcmp (this_entry.name, ".") == 0)
|| (strcmp (this_entry.name, "..") == 0))
continue;
status = apr_dir_remove_recursively (fullpath, subpool);
if (! (APR_STATUS_IS_SUCCESS (status))) return status;
}
else if (this_entry.filetype == APR_REG)
{
status = apr_file_remove (fullpath, subpool);
if (! (APR_STATUS_IS_SUCCESS (status))) return status;
}
}
if (! (APR_STATUS_IS_ENOENT (status)))
return status;
else
{
status = apr_dir_close (this_dir);
if (! (APR_STATUS_IS_SUCCESS (status))) return status;
}
status = apr_dir_remove (path, subpool);
if (! (APR_STATUS_IS_SUCCESS (status))) return status;
apr_pool_destroy (subpool);
return APR_SUCCESS;
}
svn_error_t *
svn_io_get_dirents (apr_hash_t **dirents,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
apr_status_t status;
apr_dir_t *this_dir;
apr_finfo_t this_entry;
apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
/* These exist so we can use their addresses as hash values! */
static const enum svn_node_kind static_svn_node_file = svn_node_file;
static const enum svn_node_kind static_svn_node_dir = svn_node_dir;
static const enum svn_node_kind static_svn_node_unknown = svn_node_unknown;
*dirents = apr_hash_make (pool);
status = apr_dir_open (&this_dir, path->data, pool);
if (status)
return
svn_error_createf (status, 0, NULL, pool,
"svn_io_get_dirents: failed to open dir '%s'",
path->data);
for (status = apr_dir_read (&this_entry, flags, this_dir);
APR_STATUS_IS_SUCCESS (status);
status = apr_dir_read (&this_entry, flags, this_dir))
{
if ((strcmp (this_entry.name, "..") == 0)
|| (strcmp (this_entry.name, ".") == 0))
continue;
else
{
const char *name = apr_pstrdup (pool, this_entry.name);
if (this_entry.filetype == APR_REG)
apr_hash_set (*dirents, name, APR_HASH_KEY_STRING,
&static_svn_node_file);
else if (this_entry.filetype == APR_DIR)
apr_hash_set (*dirents, name, APR_HASH_KEY_STRING,
&static_svn_node_dir);
else
/* ### symlinks, etc. will fall into this category for now.
someday subversion will recognize them. :) */
apr_hash_set (*dirents, name, APR_HASH_KEY_STRING,
&static_svn_node_unknown);
}
}
if (! (APR_STATUS_IS_ENOENT (status)))
return
svn_error_createf (status, 0, NULL, pool,
"svn_io_get_dirents: error while reading dir '%s'",
path->data);
status = apr_dir_close (this_dir);
if (status)
return
svn_error_createf (status, 0, NULL, pool,
"svn_io_get_dirents: failed to close dir '%s'",
path->data);
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_run_cmd (const char *path,
const char *cmd,
const char *const *args,
int *exitcode,
apr_exit_why_e *exitwhy,
svn_boolean_t inherit,
apr_file_t *infile,
apr_file_t *outfile,
apr_file_t *errfile,
apr_pool_t *pool)
{
apr_status_t apr_err;
apr_proc_t cmd_proc;
apr_procattr_t *cmdproc_attr;
/* Create the process attributes. */
apr_err = apr_procattr_create (&cmdproc_attr, pool);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_run_cmd: error creating %s process attributes",
cmd);
/* Make sure we invoke cmd directly, not through a shell. */
apr_err = apr_procattr_cmdtype_set (cmdproc_attr,
inherit?APR_PROGRAM_PATH:APR_PROGRAM);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_run_cmd: error setting %s process cmdtype",
cmd);
/* Set the process's working directory. */
if (path)
{
apr_err = apr_procattr_dir_set (cmdproc_attr, path);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_run_cmd: error setting %s process directory",
cmd);
}
/* Use requested inputs and outputs.
### Unfortunately each of these apr functions creates a pipe and then
overwrites the pipe file descriptor with the descriptor we pass
in. The pipes can then never be closed. This is an APR bug. */
if (infile)
{
apr_err = apr_procattr_child_in_set (cmdproc_attr, infile, NULL);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_run_cmd: error setting %s process child input",
cmd);
}
if (outfile)
{
apr_err = apr_procattr_child_out_set (cmdproc_attr, outfile, NULL);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_run_cmd: error setting %s process child outfile",
cmd);
}
if (errfile)
{
apr_err = apr_procattr_child_err_set (cmdproc_attr, errfile, NULL);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_run_cmd: error setting %s process child errfile",
cmd);
}
/* Start the cmd command. */
apr_err = apr_proc_create (&cmd_proc, cmd, args, NULL, cmdproc_attr, pool);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_run_cmd: error starting %s process",
cmd);
/* Wait for the cmd command to finish. */
apr_err = apr_proc_wait (&cmd_proc, exitcode, exitwhy, APR_WAIT);
if (APR_STATUS_IS_CHILD_NOTDONE (apr_err))
return svn_error_createf
(apr_err, 0, NULL, pool,
"svn_io_run_cmd: error waiting for %s process",
cmd);
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_run_diff (const char *dir,
const char *const *user_args,
const int num_user_args,
const char *label,
const char *from,
const char *to,
int *pexitcode,
apr_file_t *outfile,
apr_file_t *errfile,
apr_pool_t *pool)
{
const char **args;
int i;
int exitcode;
int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */
apr_pool_t *subpool = svn_pool_create (pool);
if (pexitcode == NULL)
pexitcode = &exitcode;
if (user_args != NULL)
nargs += num_user_args;
else
nargs += 1; /* -u */
if (label != NULL)
nargs += 2; /* the -L and the label itself */
args = apr_palloc(subpool, nargs * sizeof(char *));
i = 0;
args[i++] = SVN_CLIENT_DIFF;
if (user_args != NULL)
{
int j;
for (j = 0; j < num_user_args; ++j)
args[i++] = user_args[j];
}
else
args[i++] = "-u"; /* assume -u if the user didn't give us any args */
if (label != NULL)
{
args[i++] = "-L";
args[i++] = label;
}
args[i++] = from;
args[i++] = to;
args[i++] = NULL;
assert (i == nargs);
SVN_ERR (svn_io_run_cmd (dir, SVN_CLIENT_DIFF, args, pexitcode, NULL, FALSE,
NULL, outfile, errfile, subpool));
if (*pexitcode < 0 || *pexitcode > 2)
return svn_error_createf (SVN_ERR_EXTERNAL_PROGRAM, 0, NULL, subpool,
"Error calling %s.", SVN_CLIENT_DIFF);
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_run_diff3 (const char *dir,
const char *mine,
const char *older,
const char *yours,
const char *mine_label,
const char *older_label,
const char *yours_label,
apr_file_t *merged,
int *exitcode,
apr_pool_t *pool)
{
const char *args[13];
/* Labels fall back to sensible defaults if not specified. */
if (mine_label == NULL)
mine_label = ".working";
if (older_label == NULL)
older_label = ".old";
if (yours_label == NULL)
yours_label = ".new";
/* Set up diff3 command line. */
args[0] = SVN_CLIENT_DIFF3;
args[1] = "-A"; /* this can be "-E" if we want 2-part
conflict markers instead of 3-part
ones. see issue #647 */
args[2] = "-m";
args[3] = "-L";
args[4] = mine_label;
args[5] = "-L";
args[6] = older_label; /* note: this label is ignored if
using 2-part markers. */
args[7] = "-L";
args[8] = yours_label;
args[9] = mine;
args[10] = older;
args[11] = yours;
args[12] = NULL;
/* Run diff3, output the merged text into the scratch file. */
SVN_ERR (svn_io_run_cmd (dir, SVN_CLIENT_DIFF3, args,
exitcode, NULL,
FALSE, /* clean environment */
NULL, merged, NULL,
pool));
/* According to the diff3 docs, a '0' means the merge was clean, and
'1' means conflict markers were found. Anything else is real
error. */
if ((*exitcode != 0) && (*exitcode != 1))
return svn_error_createf (SVN_ERR_EXTERNAL_PROGRAM, 0, NULL, pool,
"Error running %s: exitcode was %d, args were:"
"\nin directory %s, basenames:\n%s\n%s\n%s",
SVN_CLIENT_DIFF3, *exitcode,
dir, mine, older, yours);
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_detect_mimetype (const char **mimetype,
const char *file,
apr_pool_t *pool)
{
static const char * const generic_binary = "application/octet-stream";
enum svn_node_kind kind;
apr_file_t *fh;
apr_status_t apr_err;
unsigned char block[1024];
apr_size_t amt_read = sizeof (block);
/* Default return value is NULL. */
*mimetype = NULL;
/* See if this file even exists, and make sure it really is a file. */
SVN_ERR (svn_io_check_path (file, &kind, pool));
if (kind != svn_node_file)
return svn_error_createf (SVN_ERR_BAD_FILENAME, 0, NULL, pool,
"Can't detect mimetype of non-file '%s'",
file);
apr_err = apr_file_open (&fh, file, APR_READ, 0, pool);
if (apr_err)
return svn_error_createf (apr_err, 0, NULL, pool,
"svn_io_detect_mimetype: error opening '%s'",
file);
/* Read a block of data from FILE. */
apr_err = apr_file_read (fh, block, &amt_read);
if (apr_err && (apr_err != APR_EOF))
return svn_error_createf (apr_err, 0, NULL, pool,
"svn_io_detect_mimetype: error reading '%s'",
file);
/* Now close the file. No use keeping it open any more. */
apr_file_close (fh);
/* Right now, this function is going to be really stupid. It's
going to examine the first block of data, and make sure that 85%
of the bytes are such that their value is in the ranges 0x07-0x0D
or 0x20-0x7F, and that 100% of those bytes is not 0x00.
If those criteria are not met, we're calling it binary. */
if (amt_read > 0)
{
apr_size_t i;
int binary_count = 0;
/* Run through the data we've read, counting the 'binary-ish'
bytes. HINT: If we see a 0x00 byte, we'll set our count to its
max and stop reading the file. */
for (i = 0; i < amt_read; i++)
{
if (block[i] == 0)
{
binary_count = amt_read;
break;
}
if ((block[i] < 0x07)
|| ((block[i] > 0x0D) && (block[i] < 0x20))
|| (block[i] > 0x7F))
{
binary_count++;
}
}
if (((binary_count * 1000) / amt_read) > 850)
{
*mimetype = generic_binary;
return SVN_NO_ERROR;
}
}
return SVN_NO_ERROR;
}
/* FIXME: Dirty, ugly, abominable, but works. Beauty comes second for now. */
#include "svn_private_config.h"
#ifdef SVN_WIN32
#include <io.h>
static apr_status_t
close_file_descriptor (void *baton)
{
int fd = (int) baton;
_close (fd);
/* Ignore errors from close, because we can't do anything about them. */
return APR_SUCCESS;
}
#endif
apr_status_t
svn_io_fd_from_file (int *fd_p, apr_file_t *file)
{
apr_os_file_t fd;
apr_status_t status = apr_os_file_get (&fd, file);
if (status == APR_SUCCESS)
{
#ifndef SVN_WIN32
*fd_p = fd;
#else
*fd_p = _open_osfhandle ((long) fd, _O_RDWR);
/* We must close the file descriptor when the apr_file_t is
closed, otherwise we'll run out of them. What happens if the
underlyig file handle is closed first is anyone's guess, so
the pool cleanup just ignores errors from the close. I hope
the RTL frees the FD slot before closing the handle ... */
if (*fd_p < 0)
status = APR_EBADF;
else
{
/* FIXME: This bit of code assumes that the first element of
an apr_file_t on Win32 is a pool. It also assumes an int
will fit into a void*. Please, let's get rid of this ASAP! */
apr_pool_t *cntxt = *(apr_pool_t**) file;
apr_pool_cleanup_register (cntxt, (void*) *fd_p,
close_file_descriptor, NULL);
}
#endif
}
return status;
}
apr_status_t
apr_check_dir_empty (const char *path,
apr_pool_t *pool)
{
apr_status_t apr_err, retval;
apr_dir_t *dir;
apr_finfo_t finfo;
apr_err = apr_dir_open (&dir, path, pool);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return apr_err;
/* All systems return "." and ".." as the first two files, so read
past them unconditionally. */
apr_err = apr_dir_read (&finfo, APR_FINFO_NAME, dir);
if (! APR_STATUS_IS_SUCCESS (apr_err)) return apr_err;
apr_err = apr_dir_read (&finfo, APR_FINFO_NAME, dir);
if (! APR_STATUS_IS_SUCCESS (apr_err)) return apr_err;
/* Now, there should be nothing left. If there is something left,
return EGENERAL. */
apr_err = apr_dir_read (&finfo, APR_FINFO_NAME, dir);
if (APR_STATUS_IS_ENOENT (apr_err))
retval = APR_SUCCESS;
else if (APR_STATUS_IS_SUCCESS (apr_err))
retval = APR_EGENERAL;
else
retval = apr_err;
apr_err = apr_dir_close (dir);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return apr_err;
return retval;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */