/* arch-cache.c:
 *
 ****************************************************************
 * Copyright (C) 2004 Aaron Bentley
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


/**
 * \file libarch/arch-cache.c
 * \brief Functions for manipulating the Arch Cache
 * \see cached-archive.c for an archive implementation that uses the
 * Arch Cache
 */

/**
 * \defgroup archcache Arch Cache manipulation functionality
 * @{
 */
#include "hackerlab/vu/vu.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/char/str.h"
#include "hackerlab/mem/talloc.h"

#include "libfsutils/copy-file.h"
#include "libfsutils/ensure-dir.h"
#include "libfsutils/tmp-files.h"

#include "libarch/namespace.h"
#include "libarch/arch-cache.h"
#include "libarch/my.h"

static t_uchar *
full_query_path (t_uchar * rel_query_path);

/**
 * \brief Determine whether the cache is active
 * \return true if the cache is active, false if it is not active
 */
extern int
arch_cache_active (void)
{
  return arch_my_cache_path() != NULL;
}

/**
 * \brief Convert a fully-qualified revision name into a base path
 * \param fq_rvsn A fully-qualified revision name (patch-id)
 * \return a new-allocated string
 */
extern t_uchar * 
arch_cache_revision_path (t_uchar * fq_rvsn)
{
  invariant (arch_valid_package_name (fq_rvsn, arch_req_archive, arch_req_patch_level, 0));
  return str_alloc_cat(0, "/archives/", fq_rvsn);
}

/**
 * \brief Convert a relative query path into a full filesystem path
 * \param a cache query string
 * \return a newly-allocated string
 */
static t_uchar *
full_query_path (t_uchar * rel_query_path)
{
  t_uchar const * cache_path = arch_my_cache_path ();
  if (cache_path == 0)
    panic("Cannot use cache; no cache path set");
  return str_alloc_cat (0, cache_path, rel_query_path);
}


/**
 * \brief Determine whether the Arch Cache has an answer to the query
 * \param rel_query_path The cache query path to get the answer for
 * \return true if the cache has an answer, false if it does not (or if the
 * cache is disabled).
 */
extern int 
arch_cache_has_answer (t_uchar * rel_query_path)
{
  struct stat unused;
  int errn;
  int status;
  t_uchar * full_path = 0;
  if (!arch_cache_active())
    return 0;
  full_path = full_query_path (rel_query_path);
  status = vu_stat (&errn, full_path, &unused);
  lim_free (0, full_path);
  return (status == 0);
}


/**
 * \brief Produce an fd containing the answer to a query from the Arch Cache
 * \param rel_query_path The cache query path to get the answer for
 * \return fd, or -1 if the cache has no answer to the query
 */
extern int 
arch_cache_maybe_get (t_uchar * rel_query_path)
{
  if (!arch_cache_has_answer (rel_query_path))
    return -1;
  else 
    return arch_cache_get (rel_query_path);
}


/**
 * \brief Return an fd containing the answer to a query from the Arch Cache
 * \param rel_query_path The query to get the answer for
 * \return a file descriptor to get the answer from.
 */
extern int 
arch_cache_get (t_uchar * rel_query_path)
{
  t_uchar * full_path = full_query_path (rel_query_path);
  int fd = 0;
  if (rel_query_path == 0)
    return -1;
  fd = safe_open (full_path, O_RDONLY, 0);
  lim_free (0, full_path);
  return fd;
}


/**
 * \brief Produce a string containing the answer to the query
 * \param rel_query_path The query to get the answer for
 * \return a newly-allocated string, or NULL if the cache is disabled
 */
extern t_uchar *
arch_cache_get_str (t_uchar * rel_query_path)
{
  int err = 0;
  t_uchar * buf = 0;
  t_uchar * answer = 0;
  size_t len = 0;
  int fd = 0;
  if (rel_query_path == 0)
    return 0;
  fd = arch_cache_get (rel_query_path);
  invariant (vu_file_to_string (&err, &buf, &len, fd) != -1);
  answer = str_save_n (0, buf, len);
  lim_free (0, buf);
  vu_close(&err, fd);
  return answer;
}


/**
 * \brief Produce a string containing the answer to the query.
 *
 * A terminating newline will be clipped.
 * \param rel_query_path The query to get the answer for
 * \return a newly-allocated string, or NULL if the cache is disabled
 */
extern t_uchar *
arch_cache_get_line (t_uchar * rel_query_path)
{
  t_uchar * line = arch_cache_get_str(rel_query_path);
  int length = 0;
  if (line == 0)
    return 0;
  length = str_length (line);
  invariant (length > 0 && line[length -1] == '\n');
  line[length-1] = 0;
  invariant (str_chr_index (line, '\n') == NULL);
  return line;
}


/**
 * \brief Assign an answer to the Arch Cache
 *
 * \note arch_cache_commit must be invoked to finalize the answer
 * \param tmp_name the temporary name of the file
 * \param rel_query_path: The query this answer is for
 * \return the fd to write the answer to 
 */
extern int 
arch_cache_put (t_uchar **tmp_name, t_uchar *rel_query_path)
{
  t_uchar * full_path = full_query_path (rel_query_path);
  t_uchar * dir_path = file_name_directory_file (0, full_path);
  int fd = -1;
  if (rel_query_path != 0)
    {
      t_uchar *temp = talloc_tmp_file_name (talloc_context, dir_path, ",,temp");
      *tmp_name = str_save (0, temp);
      talloc_free (temp);

      ensure_directory_exists (dir_path);
      fd = safe_open (*tmp_name, O_WRONLY | O_CREAT | O_EXCL, 0666);
    }
  else
    {
      *tmp_name = 0;
      fd = safe_open ("/dev/null", O_WRONLY, 0666);
    }
  lim_free (0, full_path);
  lim_free (0, dir_path);
  return fd;
}

/**
 * \brief Finalize a storing a cache answer.
 *
 * Any previous value will be overwritten.  This is an atomic operation.
 * \param tmp_name The temporary filename that was written to
 * \param rel_query_path The cache query path to store the answer in
 */
extern void
arch_cache_commit (t_uchar *tmp_name, t_uchar *rel_query_path)
{
  t_uchar * full_path = 0;
  if (rel_query_path == 0)
    return;
  full_path = full_query_path (rel_query_path);
  safe_rename (tmp_name, full_path);
  lim_free (0, full_path);
}

/**
 * \brief Assign a string as the answer to the cache query.
 *
 * No commit necessary.
 * \param rel_query_path The cache query path to store data for
 * \param answer The query answer to store
 */
extern void 
arch_cache_put_str (t_uchar * rel_query_path, t_uchar * answer)
{
  t_uchar * tmp_name;
  int fd = 0;
  if (rel_query_path == 0)
    return;
  fd = arch_cache_put (&tmp_name, rel_query_path);
  safe_write (fd, answer, str_length (answer));
  safe_close (fd);
  arch_cache_commit (tmp_name, rel_query_path);
}


/**
 * \brief Assign a string as the answer to the cache query, appending a newline.
 *
 * No commit necessary.
 * \param rel_query_path The cache query path to store data for
 * \param answer The query answer to store
 */
extern void 
arch_cache_put_line (t_uchar * rel_query_path, t_uchar const * answer)
{
  t_uchar * tmp_name;
  int fd = 0;
  invariant (str_chr_index (answer, '\n') == NULL);
  if (rel_query_path == 0)
    return;
  fd = arch_cache_put (&tmp_name, rel_query_path);
  safe_write (fd, (t_uchar *)answer, str_length (answer));
  safe_write (fd, "\n", 1);
  safe_close (fd);
  arch_cache_commit (tmp_name, rel_query_path);
}

/**
 * Create an appropriate extended arch query
 */
t_uchar * 
arch_cache_query_new (arch_patch_id *patch_id, t_uchar const * extension)
{
  t_uchar * base_query = 0;
  t_uchar * query = 0;

  if (!arch_cache_active ())
    return NULL;
  
  base_query = arch_cache_revision_path (arch_patch_id_patch_id(patch_id));
  query = str_alloc_cat_many (0, base_query, "/", extension, str_end);

  lim_free (0, base_query);
  return query;
}


/* tag: 92436b23-1e43-46bc-ad33-8f758d440775 */


syntax highlighted by Code2HTML, v. 0.9.1