/*
 * adm_files.c: helper routines for handling files & dirs in the
 *              working copy administrative area (creating,
 *              deleting, opening, and closing).  This is the only
 *              code that actually knows where administrative
 *              information is kept.  
 *
 * ====================================================================
 * 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 <stdarg.h>
#include <assert.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_strings.h>

#include "svn_types.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_path.h"
#include "svn_wc.h"

#include "wc.h"
#include "adm_files.h"
#include "entries.h"
#include "lock.h"

#include "svn_private_config.h"


/*** File names in the adm area. ***/

/* The default name of the WC admin directory. This name is always
   checked by svn_wc_is_adm_dir. */
static const char default_adm_dir_name[] = ".svn";

/* The name that is actually used for the WC admin directory.  The
   commonest case where this won't be the default is in Windows
   ASP.NET development environments, which choke on ".svn". */
static const char *adm_dir_name = default_adm_dir_name;


svn_boolean_t
svn_wc_is_adm_dir (const char *name, apr_pool_t *pool)
{
  (void)pool;  /* Silence compiler warnings about unused parameter. */
  return (0 == strcmp (name, adm_dir_name)
          || 0 == strcmp (name, default_adm_dir_name));
}


const char *
svn_wc_get_adm_dir (apr_pool_t *pool)
{
  (void)pool;  /* Silence compiler warnings about unused parameter. */
  return adm_dir_name;
}


svn_error_t *
svn_wc_set_adm_dir (const char *name, apr_pool_t *pool)
{
  /* This is the canonical list of administrative directory names.

     FIXME:
     An identical list is used in
       libsvn_subr/opt.c:svn_opt_args_to_target_array2(),
     but that function can't use this list, because that use would
     create a circular dependency between libsvn_wc and libsvn_subr.
     Make sure changes to the lists are always synchronized! */
  static const char *valid_dir_names[] = {
    default_adm_dir_name,
    "_svn",
    NULL
  };

  const char **dir_name;
  for (dir_name = valid_dir_names; *dir_name; ++dir_name)
    if (0 == strcmp (name, *dir_name))
      {
        /* Use the pointer to the statically allocated string
           constant, to avoid potential pool lifetime issues. */
        adm_dir_name = *dir_name;
        return SVN_NO_ERROR;
      }
  return svn_error_createf
    (SVN_ERR_BAD_FILENAME, NULL,
     _("'%s' is not a valid administrative directory name"),
     svn_path_local_style (name, pool));
}


/* Return the path to something in PATH's administrative area.
 *
 * First, the adm subdir is appended to PATH as a component, then the
 * "tmp" directory is added iff USE_TMP is set, then each of the
 * varargs in AP (char *'s) is appended as a path component.  The list
 * must be terminated with a NULL argument.
 *
 * Adding an empty component results in no effect (i.e., the separator
 * char is not doubled).
 *
 * If EXTENSION is non-null, it will be appended to the final string
 * without a separator character.
 */
static const char *
v_extend_with_adm_name (const char *path,
                        const char *extension,
                        svn_boolean_t use_tmp,
                        apr_pool_t *pool,
                        va_list ap)
{
  const char *this;

  /* Tack on the administrative subdirectory. */
  path = svn_path_join (path, adm_dir_name, pool);

  /* If this is a tmp file, name it into the tmp area. */
  if (use_tmp)
    path = svn_path_join (path, SVN_WC__ADM_TMP, pool);

  /* Tack on everything else. */
  while ((this = va_arg (ap, const char *)) != NULL)
    {
      if (this[0] == '\0')
        continue;

      path = svn_path_join (path, this, pool);
    }

  if (extension)
    path = apr_pstrcat (pool, path, extension, NULL);

  return path;
}


/* See v_extend_with_adm_name() for details. */
static const char *
extend_with_adm_name (const char *path,
                      const char *extension,
                      svn_boolean_t use_tmp,
                      apr_pool_t *pool,
                      ...)
{
  va_list ap;

  va_start (ap, pool);
  path = v_extend_with_adm_name (path, extension, use_tmp, pool, ap);
  va_end (ap);

  return path;
}


const char *
svn_wc__adm_path (const char *path,
                  svn_boolean_t tmp,
                  apr_pool_t *pool, 
                  ...)
{
  va_list ap;

  va_start (ap, pool);
  path = v_extend_with_adm_name (path, NULL, tmp, pool, ap);
  va_end (ap);

  return path;
}


svn_boolean_t
svn_wc__adm_path_exists (const char *path,
                         svn_boolean_t tmp,
                         apr_pool_t *pool, 
                         ...)
{
  svn_node_kind_t kind;
  svn_error_t *err;
  va_list ap;

  va_start (ap, pool);
  path = v_extend_with_adm_name (path, NULL, tmp, pool, ap);
  va_end (ap);

  err = svn_io_check_path (path, &kind, pool);
  if (err)
    {
      svn_error_clear (err);
      /* Return early, since kind is undefined in this case. */
      return FALSE;
    }

  if (kind == svn_node_none)
    return FALSE;
  else
    return TRUE;
}



/*** Making and using files in the adm area. ***/


/* Create an empty THING in the adm area with permissions set to PERMS. 
 * If TMP is non-zero, then create THING in the tmp dir.
 *
 * Does not check if THING already exists, so be careful -- THING will
 * be empty after this no matter what.
 */
svn_error_t *
svn_wc__make_adm_thing (svn_wc_adm_access_t *adm_access,
                        const char *thing,
                        svn_node_kind_t type,
                        apr_fileperms_t perms,
                        svn_boolean_t tmp,
                        apr_pool_t *pool)
{
  svn_error_t *err = SVN_NO_ERROR;
  apr_file_t *f = NULL;
  const char *path;

  SVN_ERR (svn_wc__adm_write_check (adm_access));

  path = extend_with_adm_name (svn_wc_adm_access_path (adm_access),
                               NULL, tmp, pool, thing, NULL);

  if (type == svn_node_file)
    {
      SVN_ERR (svn_io_file_open (&f, path,
                                 (APR_WRITE | APR_CREATE | APR_EXCL),
                                 perms,
                                 pool));

      /* Creation succeeded, so close immediately. */
      SVN_ERR (svn_io_file_close (f, pool));
    }
  else if (type == svn_node_dir)
    {
      SVN_ERR (svn_io_dir_make (path, perms, pool));
    }
  else   /* unknown type argument, wrongness */
    {
      /* We're only capturing this here because there wouldn't be a
         segfault or other obvious indicator that something went
         wrong.  Even so, not sure if it's appropriate.  Thoughts? */
      err = svn_error_create 
        (0, NULL, _("Bad type indicator"));
    }

  return err;
}



/*** Syncing files in the adm area. ***/

static svn_error_t *
sync_adm_file (const char *path,
               const char *extension,
               apr_pool_t *pool,
               ...)
{
  /* Some code duplication with close_adm_file() seems unavoidable,
     given how C va_lists work. */

  const char *tmp_path;
  va_list ap;
  
  /* Extend tmp name. */
  va_start (ap, pool);
  tmp_path = v_extend_with_adm_name (path, extension, 1, pool, ap);
  va_end (ap);
  
  /* Extend real name. */
  va_start (ap, pool);
  path = v_extend_with_adm_name (path, extension, 0, pool, ap);
  va_end (ap);
  
  /* Rename. */
  SVN_ERR (svn_wc__prep_file_for_replacement (path, TRUE, pool));
  SVN_ERR (svn_io_file_rename (tmp_path, path, pool));
  SVN_ERR (svn_io_set_file_read_only (path, FALSE, pool));

  return SVN_NO_ERROR;
}


/* Rename a tmp text-base file to its real text-base name.
   The file had better already be closed. */
svn_error_t *
svn_wc__sync_text_base (const char *path, apr_pool_t *pool)
{
  const char *parent_path, *base_name;
  svn_path_split (path, &parent_path, &base_name, pool);
  return sync_adm_file (parent_path,
                        SVN_WC__BASE_EXT,
                        pool,
                        SVN_WC__ADM_TEXT_BASE,
                        base_name,
                        NULL);
}

const char *
svn_wc__text_base_path (const char *path,
                        svn_boolean_t tmp,
                        apr_pool_t *pool)
{
  const char *newpath, *base_name;

  svn_path_split (path, &newpath, &base_name, pool);
  return extend_with_adm_name (newpath,
                               SVN_WC__BASE_EXT,
                               tmp,
                               pool,
                               SVN_WC__ADM_TEXT_BASE,
                               base_name,
                               NULL);
}


static svn_error_t *
prop_path_internal (const char **prop_path,
                    const char *path,
                    svn_wc_adm_access_t *adm_access,
                    svn_boolean_t base,
                    svn_boolean_t wcprop,
                    svn_boolean_t tmp,
                    apr_pool_t *pool)
{
  const svn_wc_entry_t *entry;
  const char *entry_name;

  SVN_ERR (svn_wc_entry (&entry, path, adm_access, FALSE, pool));

  if (entry && entry->kind == svn_node_dir)  /* It's a working copy dir */
    {
      *prop_path = extend_with_adm_name
        (path,
         NULL,
         tmp,
         pool,
         base ? SVN_WC__ADM_DIR_PROP_BASE
         : (wcprop ? SVN_WC__ADM_DIR_WCPROPS : SVN_WC__ADM_DIR_PROPS),
         NULL);
    }
  else  /* It's either a file, or a non-wc dir (i.e., maybe an ex-file) */
    {
      svn_path_split (path, prop_path, &entry_name, pool);
      *prop_path = extend_with_adm_name
        (*prop_path,
         base ? SVN_WC__BASE_EXT : SVN_WC__WORK_EXT,
         tmp,
         pool,
         base ? SVN_WC__ADM_PROP_BASE
         : (wcprop ? SVN_WC__ADM_WCPROPS : SVN_WC__ADM_PROPS),
         entry_name,
         NULL);
    }

  return SVN_NO_ERROR;
}



/* Return a path to the 'wcprop' file for PATH, possibly in TMP area.  */
svn_error_t *
svn_wc__wcprop_path (const char **wcprop_path,
                     const char *path,
                     svn_wc_adm_access_t *adm_access,
                     svn_boolean_t tmp,
                     apr_pool_t *pool)
{
  return prop_path_internal (wcprop_path, path, adm_access, FALSE, TRUE, tmp,
                             pool);
}




svn_error_t *
svn_wc__prop_path (const char **prop_path,
                   const char *path,
                   svn_wc_adm_access_t *adm_access,
                   svn_boolean_t tmp,
                   apr_pool_t *pool)
{
  return prop_path_internal (prop_path, path, adm_access, FALSE, FALSE, tmp,
                             pool);
}


svn_error_t *
svn_wc__prop_base_path (const char **prop_path,
                        const char *path,
                        svn_wc_adm_access_t *adm_access,
                        svn_boolean_t tmp,
                        apr_pool_t *pool)
{
  return prop_path_internal (prop_path, path, adm_access, TRUE, FALSE, tmp,
                             pool);
}




/*** Opening and closing files in the adm area. ***/

/* Open a file somewhere in the adm area for directory PATH.
 * First, add the adm subdir as the next component of PATH, then add
 * each of the varargs (they are char *'s), then add EXTENSION if it
 * is non-null, then open the resulting file as *HANDLE.
 *
 * If FLAGS indicates writing, open the file in the adm tmp area.
 * This means the file will probably need to be renamed from there,
 * either by passing the sync flag to close_adm_file() later, or with
 * an explicit call to sync_adm_file().
 */
static svn_error_t *
open_adm_file (apr_file_t **handle,
               const char *path,
               const char *extension,
               apr_fileperms_t protection,
               apr_int32_t flags,
               apr_pool_t *pool,
               ...)
{
  svn_error_t *err = SVN_NO_ERROR;
  va_list ap;

  /* If we're writing, always do it to a tmp file. */
  if (flags & APR_WRITE)
    {
      if (flags & APR_APPEND)
        {
          /* We don't handle append.  To do so we would need to copy the
             contents into the apr_file_t once it has been opened. */
          return svn_error_create
            (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
             _("APR_APPEND not supported for adm files"));
        }

      /* Need to own the temporary file, so don't reuse an existing one. */
      flags |= APR_EXCL | APR_CREATE;

      /* Extend with tmp name. */
      va_start (ap, pool);
      path = v_extend_with_adm_name (path, extension, 1, pool, ap);
      va_end (ap);
    }
  else
    {
      /* Extend with regular adm name. */
      va_start (ap, pool);
      path = v_extend_with_adm_name (path, extension, 0, pool, ap);
      va_end (ap);
    }

  err = svn_io_file_open (handle, path, flags, protection, pool);
  if ((flags & APR_WRITE) && err && APR_STATUS_IS_EEXIST(err->apr_err))
    {
      /* Exclusive open failed, delete and retry */
      svn_error_clear (err);
      SVN_ERR (svn_io_remove_file (path, pool));
      err = svn_io_file_open (handle, path, flags, protection, pool);
    }

  if (err)
    {
      /* Oddly enough, APR will set *HANDLE even if the open failed.
         You'll get a filehandle whose descriptor is -1.  There must
         be a reason this is useful... Anyway, we don't want the
         handle. */
      *handle = NULL;
      /* If we receive a failure to open a file in our temporary directory,
       * it may be because our temporary directories aren't created.
       * Older SVN clients did not create these directories.
       * 'svn cleanup' will fix this problem.
       */
      if (APR_STATUS_IS_ENOENT(err->apr_err) && (flags & APR_WRITE))
        {
          err = svn_error_quick_wrap(err,
                               _("Your .svn/tmp directory may be missing or "
                                 "corrupt; run 'svn cleanup' and try again"));
        }
    }

  return err;
}


/* Close the file indicated by FP (PATH is passed to make error
 * reporting better).  If SYNC is non-zero, then the file will be
 * sync'd from the adm tmp area to its permanent location, otherwise
 * it will remain in the tmp area.  See open_adm_file().
 */
static svn_error_t *
close_adm_file (apr_file_t *fp,
                const char *path,
                const char *extension,
                svn_boolean_t sync,
                apr_pool_t *pool,
                ...)
{
  const char *tmp_path;
  va_list ap;

  /* Get the full name of the thing we're closing. */
  va_start (ap, pool);
  tmp_path = v_extend_with_adm_name (path, extension, sync, pool, ap);
  va_end (ap);

  SVN_ERR (svn_io_file_close (fp, pool));

  /* If we're syncing a tmp file, it needs to be renamed after closing. */
  if (sync)
    {
      /* Some code duplication with sync_adm_file() seems unavoidable,
         given how C va_lists work. */

      /* Obtain dest name. */
      va_start (ap, pool);
      path = v_extend_with_adm_name (path, extension, 0, pool, ap);
      va_end (ap);
      
      /* Rename. */
      SVN_ERR (svn_wc__prep_file_for_replacement (path, TRUE, pool));
      SVN_ERR (svn_io_file_rename (tmp_path, path, pool));
      SVN_ERR (svn_io_set_file_read_only (path, FALSE, pool));
      
      return SVN_NO_ERROR;
    }

  return SVN_NO_ERROR;
}


svn_error_t *
svn_wc__open_adm_file (apr_file_t **handle,
                       const char *path,
                       const char *fname,
                       apr_int32_t flags,
                       apr_pool_t *pool)
{
  return open_adm_file (handle, path, NULL, APR_OS_DEFAULT, flags, pool,
                        fname, NULL);
}


svn_error_t *
svn_wc__close_adm_file (apr_file_t *fp,
                        const char *path,
                        const char *fname,
                        int sync,
                        apr_pool_t *pool)
{
  return close_adm_file (fp, path, NULL, sync, pool, fname, NULL);
}


svn_error_t *
svn_wc__remove_adm_file (const char *path, apr_pool_t *pool, ...)
{
  va_list ap;

  va_start (ap, pool);
  path = v_extend_with_adm_name (path, NULL, 0, pool, ap);
  va_end (ap);
      
  SVN_ERR(svn_io_remove_file (path, pool));

  return SVN_NO_ERROR;
}


const char *
svn_wc__empty_file_path (const char *path,
                         apr_pool_t *pool)
{
  const char *parent_path = svn_path_dirname (path, pool);
  return extend_with_adm_name (parent_path, NULL, 0, pool,
                               SVN_WC__ADM_EMPTY_FILE, NULL);
}


svn_error_t *
svn_wc__open_empty_file (apr_file_t **handle,
                         const char *path,
                         apr_pool_t *pool)
{
  const char *parent_path = svn_path_dirname (path, pool);
  return open_adm_file (handle, parent_path, NULL, APR_OS_DEFAULT, APR_READ,
                        pool, SVN_WC__ADM_EMPTY_FILE, NULL);
}


svn_error_t *
svn_wc__close_empty_file (apr_file_t *fp,
                          const char *path,
                          apr_pool_t *pool)
{
  const char *parent_path = svn_path_dirname (path, pool);
  return close_adm_file (fp, parent_path, NULL, 0, pool,
                         SVN_WC__ADM_EMPTY_FILE, NULL);
}


svn_error_t *
svn_wc__open_text_base (apr_file_t **handle,
                        const char *path,
                        apr_int32_t flags,
                        apr_pool_t *pool)
{
  const char *parent_path, *base_name;
  svn_path_split (path, &parent_path, &base_name, pool);
  return open_adm_file (handle, parent_path, SVN_WC__BASE_EXT, APR_OS_DEFAULT,
                        flags, pool, SVN_WC__ADM_TEXT_BASE, base_name, NULL);
}


svn_error_t *
svn_wc__close_text_base (apr_file_t *fp,
                         const char *path,
                         int write,
                         apr_pool_t *pool)
{
  const char *parent_path, *base_name;
  svn_path_split (path, &parent_path, &base_name, pool);
  return close_adm_file (fp, parent_path, SVN_WC__BASE_EXT, write, pool,
                         SVN_WC__ADM_TEXT_BASE, base_name, NULL);
}


svn_error_t *
svn_wc__open_props (apr_file_t **handle,
                    const char *path,
                    apr_int32_t flags,
                    svn_boolean_t base,
                    svn_boolean_t wcprops,
                    apr_pool_t *pool)
{
  const char *parent_dir, *base_name;
  svn_node_kind_t kind;
  int wc_format_version;

  SVN_ERR (svn_io_check_path (path, &kind, pool));
  if (kind == svn_node_dir)
    parent_dir = path;
  else
    svn_path_split (path, &parent_dir, &base_name, pool);
  
  /* At this point, we know we need to open a file in the admin area
     of parent_dir.  First check that parent_dir is a working copy: */
  SVN_ERR (svn_wc_check_wc (parent_dir, &wc_format_version, pool));
  if (wc_format_version == 0)
    return svn_error_createf
      (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
       _("'%s' is not a working copy"),
       svn_path_local_style (parent_dir, pool));

  /* Then examine the flags to know -which- kind of prop file to get. */

  if (base && wcprops)
    return svn_error_create (SVN_ERR_WC_PATH_NOT_FOUND, NULL,
                             _("No such thing as 'base' "
                               "working copy properties!"));

  else if (base)
    {
      if (kind == svn_node_dir)
        return open_adm_file (handle, parent_dir, NULL, APR_OS_DEFAULT, flags,
                              pool, SVN_WC__ADM_DIR_PROP_BASE, NULL);
      else
        return open_adm_file (handle, parent_dir, SVN_WC__BASE_EXT,
                              APR_OS_DEFAULT, flags, pool,
                              SVN_WC__ADM_PROP_BASE, base_name, NULL);
    }
  else if (wcprops)
    {
      if (kind == svn_node_dir)
        return open_adm_file (handle, parent_dir, NULL, APR_OS_DEFAULT, flags,
                              pool, SVN_WC__ADM_DIR_WCPROPS, NULL);
      else
        {
          return open_adm_file
            (handle, parent_dir,
             SVN_WC__WORK_EXT, APR_OS_DEFAULT,
             flags, pool, SVN_WC__ADM_WCPROPS, base_name, NULL);
        }
    }
  else /* plain old property file */
    {
      if (kind == svn_node_dir)
        return open_adm_file (handle, parent_dir, NULL, APR_OS_DEFAULT, flags,
                              pool, SVN_WC__ADM_DIR_PROPS, NULL);
      else
        {
          return open_adm_file
            (handle, parent_dir,
             SVN_WC__WORK_EXT, APR_OS_DEFAULT,
             flags, pool, SVN_WC__ADM_PROPS, base_name, NULL);
        }
    }
}



svn_error_t *
svn_wc__close_props (apr_file_t *fp,
                     const char *path,
                     svn_boolean_t base,
                     svn_boolean_t wcprops,
                     int sync,
                     apr_pool_t *pool)
{
  const char *parent_dir, *base_name;
  svn_node_kind_t kind;

  SVN_ERR (svn_io_check_path (path, &kind, pool));
  if (kind == svn_node_dir)
    parent_dir = path;
  else    
    svn_path_split (path, &parent_dir, &base_name, pool);
  
  /* At this point, we know we need to close a file in the admin area
     of parent_dir.  Since the file must be open already, we know that
     parent_dir is a working copy. */

  /* Then examine the flags to know -which- kind of prop file to get. */

  if (base && wcprops)
    return svn_error_create (SVN_ERR_WC_PATH_NOT_FOUND, NULL,
                             _("No such thing as 'base' "
                               "working copy properties!"));

  else if (base)
    {
      if (kind == svn_node_dir)
        return close_adm_file (fp, parent_dir, NULL, sync, pool,
                               SVN_WC__ADM_DIR_PROP_BASE, NULL);
      else
        return close_adm_file (fp, parent_dir, SVN_WC__BASE_EXT, sync, pool,
                               SVN_WC__ADM_PROP_BASE, base_name, NULL);
    }
  else if (wcprops)
    {
      if (kind == svn_node_dir)
        return close_adm_file (fp, parent_dir, NULL, sync, pool,
                               SVN_WC__ADM_DIR_WCPROPS, NULL);
      else
        return close_adm_file
          (fp, parent_dir,
           SVN_WC__WORK_EXT,
           sync, pool, SVN_WC__ADM_WCPROPS, base_name, NULL);
    }
  else /* plain old property file */
    {
      if (kind == svn_node_dir)
        return close_adm_file (fp, parent_dir, NULL, sync, pool,
                               SVN_WC__ADM_DIR_PROPS, NULL);
      else
        return close_adm_file
          (fp, parent_dir,
           SVN_WC__WORK_EXT,
           sync, pool, SVN_WC__ADM_PROPS, base_name, NULL);
    }
}



svn_error_t *
svn_wc__sync_props (const char *path,
                    svn_boolean_t base,
                    svn_boolean_t wcprops,
                    apr_pool_t *pool)
{
  const char *parent_dir, *base_name;
  svn_node_kind_t kind;

  /* Check if path is a file or a dir. */
  SVN_ERR (svn_io_check_path (path, &kind, pool));

  /* If file, split the path. */
  if (kind == svn_node_file)
    svn_path_split (path, &parent_dir, &base_name, pool);
  else    
    parent_dir = path;
  
  /* At this point, we know we need to open a file in the admin area
     of parent_dir.  Examine the flags to know -which- kind of prop
     file to get -- there are three types! */

  if (base && wcprops)
    return svn_error_create (SVN_ERR_WC_PATH_NOT_FOUND, NULL,
                             _("No such thing as 'base' "
                               "working copy properties!"));

  else if (base)
    {
      if (kind == svn_node_dir)
        return sync_adm_file (parent_dir, NULL, pool,
                              SVN_WC__ADM_DIR_PROP_BASE, NULL);
      else
        return sync_adm_file (parent_dir, SVN_WC__BASE_EXT, pool,
                              SVN_WC__ADM_PROP_BASE, base_name, NULL);
    }
  else if (wcprops)
    {
      if (kind == svn_node_dir)
        return sync_adm_file (parent_dir, NULL, pool,
                              SVN_WC__ADM_DIR_WCPROPS, NULL);
      else
        return sync_adm_file (parent_dir, SVN_WC__BASE_EXT, pool,
                              SVN_WC__ADM_WCPROPS, base_name, NULL);
    }
  else /* plain old property file */
    {
      if (kind == svn_node_dir)
        return sync_adm_file (parent_dir, NULL, pool,
                              SVN_WC__ADM_DIR_PROPS, NULL);
      else
        return sync_adm_file (parent_dir, SVN_WC__WORK_EXT, pool,
                              SVN_WC__ADM_PROPS, base_name, NULL);
    }

}




/*** Checking for and creating administrative subdirs. ***/

/* Set *EXISTS to iff there's an adm area for PATH, and it matches URL
 * and REVISION.  If there's no adm area, set *EXISTS to false; if
 * there's an adm area but it doesn't match URL and REVISION, then
 * return error and don't touch *EXISTS.
 *
 * ### These semantics are totally bizarre.  One wonders what the
 * ### callers' real needs are.  In the long term, this function
 * ### should probably be unified with svn_wc_check_wc.
 */
static svn_error_t *
check_adm_exists (svn_boolean_t *exists,
                  const char *path,
                  const char *url,
                  svn_revnum_t revision,
                  apr_pool_t *pool)
{
  svn_error_t *err = SVN_NO_ERROR;
  svn_node_kind_t kind;
  svn_boolean_t dir_exists = FALSE, wc_exists = FALSE;
  const char *tmp_path;

  /** Step 1: check that the directory exists. **/

  tmp_path = extend_with_adm_name (path, NULL, 0, pool, NULL);

  SVN_ERR (svn_io_check_path (tmp_path, &kind, pool));
  if (kind != svn_node_none && kind != svn_node_dir)
    {
      /* If got an error other than dir non-existence, then
         something's weird and we should return a genuine error. */
      return svn_error_createf (APR_ENOTDIR, NULL,
                                _("'%s' is not a directory"),
                                svn_path_local_style (tmp_path, pool));
    }
  else if (kind == svn_node_none)
    {
      dir_exists = FALSE;
    }
  else                      /* must be a dir. */
    {
      assert (kind == svn_node_dir);
      dir_exists = TRUE;
    }

  /** Step 1.  If no adm directory, then we're done. */
  if (! dir_exists)
    {
      *exists = FALSE;
      return SVN_NO_ERROR;
    }

  /** The directory exists, but is it a valid working copy yet?
      Try step 2: checking that SVN_WC__ADM_FORMAT exists and that
      it's not too high a format version for this code.  **/
  {
    int wc_format;

    err = svn_io_read_version_file
      (&wc_format, svn_path_join (tmp_path, SVN_WC__ADM_FORMAT, pool), pool);

    if (err)
      {
        svn_error_clear (err);
        wc_exists = FALSE;
      }
    else
      wc_exists = TRUE;
  }

  /** Step 3: now check that repos and ancestry are correct **/

  if (wc_exists)
    {
      /* This is a bit odd.  We have to open an access baton, which relies
         on this being a working copy, in order to determine if this is a
         working copy! */
      svn_wc_adm_access_t *adm_access;
      const svn_wc_entry_t *entry;

      SVN_ERR (svn_wc_adm_open3 (&adm_access, NULL, path, FALSE, 0,
                                 NULL, NULL, pool));
      SVN_ERR (svn_wc_entry (&entry, path, adm_access, FALSE, pool));
      SVN_ERR (svn_wc_adm_close (adm_access));
      if (!entry)
        return svn_error_createf (SVN_ERR_ENTRY_NOT_FOUND, NULL,
                                  _("No entry for '%s'"),
                                  svn_path_local_style (path, pool));

      /* The revisions must match except when adding a directory with a
         name that matches a directory scheduled for deletion. That's
         because the deleted directory's administrative dir will still be
         in place but will have an arbitrary revision. */
      if (entry->revision != revision
          && !(entry->schedule == svn_wc_schedule_delete && revision == 0))
        return
          svn_error_createf
          (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
           _("Revision %ld doesn't match existing revision %ld in '%s'"),
           revision, entry->revision,
           svn_path_local_style (path, pool));

      /** ### comparing URLs, should they be canonicalized first? */
      if (strcmp (entry->url, url) != 0)
        return
          svn_error_createf
          (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
           _("URL '%s' doesn't match existing URL '%s' in '%s'"),
           url, entry->url,
           svn_path_local_style (path, pool));
    }

  *exists = wc_exists;

  return SVN_NO_ERROR;
}


static svn_error_t *
make_empty_adm (const char *path, apr_pool_t *pool)
{
  path = extend_with_adm_name (path, NULL, 0, pool, NULL);
  SVN_ERR (svn_io_dir_make_hidden (path, APR_OS_DEFAULT, pool));
  return SVN_NO_ERROR;
}


/* Init an adm file with some contents. 
   Don't call this until a tmp area exists in adm. */
static svn_error_t *
init_adm_file (const char *path,
               const char *thing,
               const char *contents,
               apr_pool_t *pool)
{
  apr_file_t *f = NULL;

  SVN_ERR (svn_wc__open_adm_file (&f, path, thing, 
                                  APR_WRITE | APR_CREATE, pool));
  SVN_ERR (svn_io_file_write_full (f, contents, 
                                   strlen (contents), NULL, pool));
  SVN_ERR (svn_wc__close_adm_file (f, path, thing, 1, pool));
  
  return SVN_NO_ERROR;
}


static svn_error_t *
init_adm_tmp_area (svn_wc_adm_access_t *adm_access,
                   apr_pool_t *pool)
{
  /* Default perms */
  apr_fileperms_t perms = APR_OS_DEFAULT;

  /* SVN_WC__ADM_TMP */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_TMP,
                                   svn_node_dir, perms, 0, pool));
  
  /* SVN_WC__ADM_TMP/SVN_WC__ADM_TEXT_BASE */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_TEXT_BASE,
                                   svn_node_dir, perms, 1, pool));

  /* SVN_WC__ADM_TMP/SVN_WC__ADM_PROP_BASE */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_PROP_BASE,
                                   svn_node_dir, perms, 1, pool));

  /* SVN_WC__ADM_TMP/SVN_WC__ADM_PROPS */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_PROPS,
                                   svn_node_dir, perms, 1, pool));

  /* SVN_WC__ADM_TMP/SVN_WC__ADM_WCPROPS */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_WCPROPS,
                                   svn_node_dir, perms, 1, pool));

  return SVN_NO_ERROR;
}


/* Set up a new adm area for PATH, with URL as the ancestor url, and
   INITIAL_REV as the starting revision.  The entries file starts out
   marked as 'incomplete.  The adm area starts out locked; remember to
   unlock it when done. */
static svn_error_t *
init_adm (const char *path,
          const char *uuid,
          const char *url,
          const char *repos,
          svn_revnum_t initial_rev,
          apr_pool_t *pool)
{
  svn_wc_adm_access_t *adm_access;

  /* Default perms */
  apr_fileperms_t perms = APR_OS_DEFAULT;

  /* Initial contents for certain adm files. */
  const char *readme_contents =
    "This is a Subversion working copy administrative directory."
    APR_EOL_STR
    "Visit http://subversion.tigris.org/ for more information."
    APR_EOL_STR;

  /* First, make an empty administrative area. */
  SVN_ERR (make_empty_adm (path, pool));

  /* Lock it immediately.  Theoretically, no compliant wc library
     would ever consider this an adm area until a README file were
     present... but locking it is still appropriately paranoid. */
  SVN_ERR (svn_wc__adm_pre_open (&adm_access, path, pool));

  /** Make subdirectories. ***/

  /* SVN_WC__ADM_TEXT_BASE */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_TEXT_BASE,
                                   svn_node_dir, perms, 0, pool));

  /* SVN_WC__ADM_PROP_BASE */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_PROP_BASE,
                                   svn_node_dir, perms, 0, pool));

  /* SVN_WC__ADM_PROPS */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_PROPS,
                                   svn_node_dir, perms, 0, pool));

  /* SVN_WC__ADM_WCPROPS */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_WCPROPS,
                                   svn_node_dir, perms, 0, pool));

  /** Init the tmp area. ***/
  SVN_ERR (init_adm_tmp_area (adm_access, pool));
  
  /** Initialize each administrative file. */

  /* SVN_WC__ADM_ENTRIES */
  SVN_ERR (svn_wc__entries_init (path, uuid, url, repos, initial_rev, pool));

  /* SVN_WC__ADM_EMPTY_FILE exists because sometimes an readable, empty
     file is required (in the repository diff for example). Creating such a
     file temporarily, only to delete it again, would appear to be less
     efficient than just having one around. It doesn't take up much space
     after all. */
  SVN_ERR (svn_wc__make_adm_thing (adm_access, SVN_WC__ADM_EMPTY_FILE,
                                   svn_node_file,
                                   APR_UREAD | APR_GREAD | APR_WREAD,
                                   0, pool));

  /* SVN_WC__ADM_README */
  SVN_ERR (init_adm_file (path, SVN_WC__ADM_README, readme_contents, pool));

  /* THIS FILE MUST BE CREATED LAST: 
     After this exists, the dir is considered complete. */
  SVN_ERR (svn_io_write_version_file 
           (extend_with_adm_name (path, NULL, FALSE, pool,
                                  SVN_WC__ADM_FORMAT, NULL),
            SVN_WC__VERSION, pool));

  /* Now unlock it.  It's now a valid working copy directory, that
     just happens to be at revision 0. */
  SVN_ERR (svn_wc_adm_close (adm_access));

  /* Else no problems, we're outta here. */
  return SVN_NO_ERROR;
}


svn_error_t *
svn_wc_ensure_adm2 (const char *path,
                    const char *uuid,
                    const char *url,
                    const char *repos,
                    svn_revnum_t revision,
                    apr_pool_t *pool)
{
  svn_boolean_t exists_already;

  SVN_ERR (check_adm_exists (&exists_already, path, url, revision, pool));
  return (exists_already ? SVN_NO_ERROR :
          init_adm (path, uuid, url, repos, revision, pool));
}

svn_error_t *
svn_wc_ensure_adm (const char *path,
                   const char *uuid,
                   const char *url,
                   svn_revnum_t revision,
                   apr_pool_t *pool)
{
  return svn_wc_ensure_adm2 (path, uuid, url, NULL, revision, pool);
}

svn_error_t *
svn_wc__adm_destroy (svn_wc_adm_access_t *adm_access, 
                     apr_pool_t *pool)
{
  const char *path;

  SVN_ERR (svn_wc__adm_write_check (adm_access));

  /* Well, the coast is clear for blowing away the administrative
     directory, which also removes the lock file */
  path = extend_with_adm_name (svn_wc_adm_access_path (adm_access),
                               NULL, FALSE, pool, NULL);
  SVN_ERR (svn_io_remove_dir (path, pool));
  SVN_ERR (svn_wc_adm_close (adm_access));

  return SVN_NO_ERROR;
}


svn_error_t *
svn_wc__adm_cleanup_tmp_area (svn_wc_adm_access_t *adm_access, 
                              apr_pool_t *pool)
{
  const char *tmp_path;

  SVN_ERR (svn_wc__adm_write_check (adm_access));

  /* Get the path to the tmp area, and blow it away. */
  tmp_path = extend_with_adm_name (svn_wc_adm_access_path (adm_access),
                                   NULL, 0, pool, SVN_WC__ADM_TMP, NULL);
  SVN_ERR (svn_io_remove_dir (tmp_path, pool));

  /* Now, rebuild the tmp area. */
  SVN_ERR (init_adm_tmp_area (adm_access, pool));

  return SVN_NO_ERROR;
}



svn_error_t *
svn_wc_create_tmp_file (apr_file_t **fp,
                        const char *path,
                        svn_boolean_t delete_on_close,
                        apr_pool_t *pool)
{
  const char *ignored_filename;

  /* Use a self-explanatory name for the file :-) . */
  path = svn_wc__adm_path (path, TRUE, pool, "tempfile", NULL);

  /* Open a unique file;  use APR_DELONCLOSE. */  
  SVN_ERR (svn_io_open_unique_file (fp, &ignored_filename,
                                    path, ".tmp", delete_on_close, pool));

  return SVN_NO_ERROR;
}


svn_error_t *
svn_wc__prep_file_for_replacement (const char *path,
                                   svn_boolean_t ignore_enoent,
                                   apr_pool_t *pool)
{
   /* On Unix a read-only file can still be removed or replaced because
      this is really an edit of the parent directory, not of the file
      itself.  Windows apparently has different semantics, and so
      when the svn_io_set_file_read_write() call was temporarily
      removed in revision 5663, Subversion stopped working on Windows. 
      
      However, the svn_io_set_file_read_write() call sets all write
      permissions on Unix, which is undesireable.  Since it is unnecessary
      to make the file writeable, do nothing to prep the file for replacement
      on Unix. */

#ifdef WIN32
  return svn_io_set_file_read_write (path, ignore_enoent, pool);
#endif /* WIN32 */

  return SVN_NO_ERROR;
}
