blob: 4020a09bda914c3a441840844c6095fd7d97f492 [file] [log] [blame]
/*
* activity.c: DeltaV activity handling
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <errno.h>
#include <apr_md5.h>
#include <httpd.h>
#include <mod_dav.h>
#include "svn_hash.h"
#include "svn_checksum.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_fs.h"
#include "svn_props.h"
#include "svn_repos.h"
#include "svn_pools.h"
#include "private/svn_fs_private.h"
#include "dav_svn.h"
/* Escape ACTIVITY_ID to be safely usable as a filename. Simply
returns the MD5 checksum of the id.
*/
static const char *
escape_activity(const char *activity_id, apr_pool_t *pool)
{
svn_checksum_t *checksum;
svn_error_clear(svn_checksum(&checksum, svn_checksum_md5, activity_id,
strlen(activity_id), pool));
return svn_checksum_to_cstring_display(checksum, pool);
}
/* Return filename for ACTIVITY_ID under the repository in REPOS. */
static const char *
activity_pathname(const dav_svn_repos *repos, const char *activity_id)
{
return svn_dirent_join(repos->activities_db,
escape_activity(activity_id, repos->pool),
repos->pool);
}
/* Return the transaction name of the activity stored in file
PATHNAME, or NULL if PATHNAME cannot be read for any reason. */
static const char *
read_txn(const char *pathname, apr_pool_t *pool)
{
apr_file_t *activity_file;
apr_pool_t *iterpool = svn_pool_create(pool);
apr_size_t len;
svn_error_t *err = SVN_NO_ERROR;
char *txn_name = apr_palloc(pool, SVN_FS__TXN_MAX_LEN+1);
int i;
/* Try up to 10 times to read the txn name, retrying on ESTALE
(stale NFS file handle because of dav_svn__store_activity
renaming the activity file into place).
*/
for (i = 0; i < 10; i++)
{
svn_error_clear(err);
svn_pool_clear(iterpool);
err = svn_io_file_open(&activity_file, pathname,
APR_READ | APR_BUFFERED,
APR_OS_DEFAULT, iterpool);
if (err)
{
#ifdef ESTALE
if (APR_TO_OS_ERROR(err->apr_err) == ESTALE)
/* Retry on ESTALE... */
continue;
#endif
/* ...else bail. */
break;
}
len = SVN_FS__TXN_MAX_LEN;
err = svn_io_read_length_line(activity_file, txn_name, &len, iterpool);
if (err)
{
#ifdef ESTALE
if (APR_TO_OS_ERROR(err->apr_err) == ESTALE)
continue;
#endif
break;
}
err = svn_io_file_close(activity_file, iterpool);
#ifdef ESTALE
if (err)
{
if (APR_TO_OS_ERROR(err->apr_err) == ESTALE)
{
/* No retry, just completely ignore this ESTALE. */
svn_error_clear(err);
err = SVN_NO_ERROR;
}
}
#endif
/* We have a txn_name or had a non-ESTALE close failure; either
way, we're finished. */
break;
}
svn_pool_destroy(iterpool);
/* ### let's just assume that any error means the
### activity/transaction doesn't exist */
if (err)
{
svn_error_clear(err);
return NULL;
}
return txn_name;
}
const char *
dav_svn__get_txn(const dav_svn_repos *repos, const char *activity_id)
{
return read_txn(activity_pathname(repos, activity_id), repos->pool);
}
dav_error *
dav_svn__delete_activity(const dav_svn_repos *repos, const char *activity_id)
{
dav_error *err = NULL;
const char *pathname;
const char *txn_name;
svn_error_t *serr;
/* gstein sez: If the activity ID is not in the database, return a
404. If the transaction is not present or is immutable, return a
204. For all other failures, return a 500. */
pathname = activity_pathname(repos, activity_id);
txn_name = read_txn(pathname, repos->pool);
if (txn_name == NULL)
{
return dav_svn__new_error(repos->pool, HTTP_NOT_FOUND, 0, 0,
"could not find activity.");
}
/* After this point, we have to cleanup the value and database. */
if (*txn_name)
{
if ((err = dav_svn__abort_txn(repos, txn_name, repos->pool)))
return err;
}
/* Finally, we remove the activity from the activities database. */
serr = svn_io_remove_file2(pathname, FALSE, repos->pool);
if (serr)
err = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"unable to remove activity.",
repos->pool);
return err;
}
dav_error *
dav_svn__store_activity(const dav_svn_repos *repos,
const char *activity_id,
const char *txn_name)
{
const char *final_path;
const char *activity_contents;
svn_error_t *err;
/* Create activities directory if it does not yet exist. */
err = svn_io_make_dir_recursively(repos->activities_db, repos->pool);
if (err != NULL)
return dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR,
"could not initialize activity db.",
repos->pool);
final_path = activity_pathname(repos, activity_id);
activity_contents = apr_psprintf(repos->pool, "%s\n%s\n",
txn_name, activity_id);
err = svn_io_write_atomic2(final_path,
activity_contents, strlen(activity_contents),
NULL /* copy_perms path */, TRUE, repos->pool);
if (err)
{
svn_error_t *serr = svn_error_quick_wrap(err,
"Can't write activity db");
/* Try to remove the tmp file, but we already have an error... */
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not write files.",
repos->pool);
}
return NULL;
}
dav_error *
dav_svn__create_txn(dav_svn_repos *repos,
const char **ptxn_name,
apr_hash_t *revprops,
apr_pool_t *pool)
{
svn_revnum_t rev;
svn_fs_txn_t *txn;
svn_error_t *serr;
if (! revprops)
{
revprops = apr_hash_make(pool);
}
if (repos->username)
{
svn_hash_sets(revprops,
SVN_PROP_REVISION_AUTHOR,
svn_string_create(repos->username, pool));
}
serr = dav_svn__get_youngest_rev(&rev, repos, pool);
if (serr != NULL)
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not determine youngest revision",
repos->pool);
}
serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev,
revprops, repos->pool);
if (serr != NULL)
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not begin a transaction",
repos->pool);
}
serr = svn_fs_txn_name(ptxn_name, txn, pool);
if (serr != NULL)
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not fetch transaction name",
repos->pool);
}
return NULL;
}
dav_error *
dav_svn__abort_txn(const dav_svn_repos *repos,
const char *txn_name,
apr_pool_t *pool)
{
svn_error_t *serr;
svn_fs_txn_t *txn;
/* If we fail only because the transaction doesn't exist, don't
sweat it (but then, also don't try to remove it). */
if ((serr = svn_fs_open_txn(&txn, repos->fs, txn_name, pool)))
{
if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
{
svn_error_clear(serr);
serr = SVN_NO_ERROR;
}
else
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not open transaction.", pool);
}
}
else if ((serr = svn_fs_abort_txn(txn, pool)))
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not abort transaction.", pool);
}
return NULL;
}