/* archive-mirror.c
*
* vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
****************************************************************
* Copyright (C) 2003 Tom Lord
*
* See the file "COPYING" for further information about
* the copyright and warranty status of this work.
*/
#include "config-options.h"
#include "po/gettext.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "libawk/trim.h"
#include "libarch/namespace.h"
#include "libarch/archive.h"
#include "libarch/archives.h"
#include "libarch/archive-mirror.h"
#include "libarch/cached-archive.h"
#include "libarch/debug.h"
#include "libarch/my.h"
#include "libarch/project-tree.h"
#include "commands/cmd.h"
#include "commands/archive-mirror.h"
#include "commands/version.h"
static t_uchar * usage = N_("[options] [from [to] [limit]]");
#define OPTS(OP) \
OP (opt_help_msg, "h", "help", 0, \
N_("Display a help message and exit.")) \
OP (opt_long_help, "H", 0, 0, \
N_("Display a verbose help message and exit.")) \
OP (opt_version, "V", "version", 0, \
N_("Display a release identifier string\n" \
"and exit.")) \
OP (opt_no_cachedrevs, 0, "no-cached", 0, \
N_("don't copy cached revisions")) \
OP (opt_summary, "s", "summary", 0, \
N_("print the summary of each patch")) \
OP (opt_id_cachedrevs, 0, "cached-tags", 0, \
N_("copy only cachedrevs for tags to other archives")) \
OP (opt_all_mirrors, "a", "all-mirrors", 0, \
N_("mirror to all known mirrors"))
t_uchar arch_cmd_archive_mirror_help[] = N_("update an archive mirror\n"
"If no arguments are given, update either the archive for the project tree\n"
"you are in, or your `my-default-archive'-MIRROR\n"
"archive with the contents of `my-default-archive'.\n"
"\n"
"If a [FROM] archive is given, update the [FROM]-MIRROR archive with\n"
"the contents of the [FROM] archive\n"
"\n"
"If both [FROM] and [TO] archives are specified, update [TO] with\n"
"the contents of [FROM]\n"
"\n"
"If LIMIT is provided, it should be a category, branch,\n"
"version, or revision name. Only the indicated part\n"
"of FROM will be copied to TO. If LIMIT is a revision,\n"
"then cached revisions will be copied and deleted to TO.\n"
"\n"
"(see \"baz make-archive -H\".).\n");
enum options
{
OPTS (OPT_ENUM)
};
static struct opt_desc opts[] =
{
OPTS (OPT_DESC)
{-1, 0, 0, 0, 0}
};
typedef struct archive_mirror_state_
{
struct arch_archive * from;
ar_arch_archive sources;
ar_arch_archive destinations;
}
archive_mirror_state_t;
static int archive_mirror_state_finalise (void *data)
{
return 0;
}
static archive_mirror_state_t *
archive_mirror_state_new (void)
{
archive_mirror_state_t * result = talloc (NULL, archive_mirror_state_t);
talloc_set_destructor (result, archive_mirror_state_finalise);
result->from = NULL;
result->sources = NULL;
result->destinations = NULL;
return result;
}
static void
connect_if_url (archive_mirror_state_t * state, struct arch_archive **archp, t_uchar **namep)
{
t_uchar *from_name = *namep;
t_uchar * temp_name = NULL;
/* if its a url, we just take it */
if (!arch_valid_archive_name (from_name) &&
!arch_valid_package_name (from_name, arch_req_archive, arch_req_category, 1))
{
t_uchar * uncached_location = arch_uncached_location (from_name);
*archp = arch_archive_connect_branch (uncached_location, &temp_name);
*archp = talloc_steal (state, arch_archive_connect_branch (uncached_location, &temp_name));
lim_free (0, uncached_location);
if (!*archp)
{
if (arch_valid_package_name (from_name, arch_no_archive, arch_req_category, 1))
return;
safe_printfmt (2, "could not connect to archive '%s'\n", from_name);
exit (2);
}
*namep = str_replace (*namep, arch_parse_name (arch_ret_archive, NULL, temp_name));
lim_free (0, temp_name);
}
}
static int
url_param (archive_mirror_state_t * state, struct arch_archive **arch, t_uchar const * const param1)
{
t_uchar * temp_name = str_save (0, param1);
*arch = NULL;
connect_if_url (state, arch, &temp_name);
lim_free (0, temp_name);
return *arch != NULL;
}
/**
* \brief find a target archive, that is different from location
* \param state the state object
* \param arch_pointer output pointer
* \param wanted_name the name we're looking for
* \param location skip this location
*/
static void
find_target_archive (archive_mirror_state_t * state, t_uchar const * const wanted_name, t_uchar const * const location, int try_mirror_aliases, int get_all)
{
struct arch_archive *temp = NULL;
int index;
ar_archive_location locations = arch_archive_locations (wanted_name);
/* FIXME: 20050328 order by priority ? */
ar_for_each (locations, index)
{
t_uchar * uncached;
if (!arch_archive_cmp_location (locations[index]->url, location))
continue;
if (locations[index]->master == 2 && !state->from)
continue;
if (locations[index]->readonly == 2 && !state->from)
continue;
uncached = arch_uncached_location (locations[index]->url);
temp = talloc_steal (state, arch_archive_connect_location (uncached, 1));
lim_free (0, uncached);
if (temp && str_cmp (wanted_name, temp->official_name))
{
/* FIXME: remove this from the registered list RBC 20050327 */
arch_archive_close (temp);
temp = NULL;
}
if (temp && state->from && (!temp->mirror_of || locations[index]->readonly == 2))
{
/* we connected to the master, switch it for the source */
/* FIXME: mark this as master in the registry RBC 20050327 */
struct arch_archive * swap = state->from;
state->from = temp;
temp = swap;
debug (dbg_ui, 2, "swapped source and target archives\n");
}
if (!temp->mirror_of)
continue; /* The master archive shouldn't be mirrored to ... */
if (temp && str_cmp(temp->mirror_of, wanted_name))
/* should also probe for readonly status */
{
/* RBC 20050327 This archive is corrupt (name and mirror_of mismatch), report or act on it somehow */
debug (dbg_ui, 1, "corrupt archive found (mname and mirror_of mismatch). %s name %s mirror_of %s\n", temp->location, temp->registered_name, temp->mirror_of);
arch_archive_close (temp);
temp = NULL;
}
if (temp)
{
ar_insert_arch_archive (&state->destinations, ar_size_arch_archive (state->destinations), temp);
if (!get_all)
return;
}
}
ar_free_archive_location (&locations);
if (get_all && ar_size_arch_archive (state->destinations))
return;
/* LEGACY FIXME-REMOVENAME support */
if (try_mirror_aliases)
{
/* from is connected, and we've been asked to look for -MIRROR and -SOURCE aliases */
/* these go FOO->FOO-MIRROR, or FOO-SOURCE->FOO.
* if from is a master, we only need too look for foo-MIRROR.
* we look for a -SOURCE first.
*/
if (state->from->mirror_of)
/* from isn't a master, there may be a better source */
{
t_uchar *source_name = arch_mirrored_from(wanted_name);
if (source_name)
{
struct arch_archive * swap;
t_uchar *source_loc = arch_archive_location (source_name, 0);
debug (dbg_ui, 2, "trying source archive %s\n", source_loc);
source_loc = str_replace (source_loc, arch_uncached_location (source_loc));
swap = arch_archive_connect_location (source_loc, 1);
if (!swap)
{
safe_printfmt (2, "Cannot connect to source archive %s\n", source_name);
talloc_free (state);
exit (2);
}
temp = state->from;
state->from = swap;
lim_free (0, source_loc);
lim_free (0, source_name);
}
}
if (!temp)
{
t_uchar * target_name = arch_mirrored_at (wanted_name);
if (target_name)
{
debug (dbg_ui, 2, "trying target archive %s\n", target_name);
/* FIXME-REMOVENAMES this goes 20050329 */
temp = arch_archive_connect_ext (target_name, wanted_name, 1);
if (!temp)
{
safe_printfmt (2, "Cannot connect to target archive %s\n", target_name);
talloc_free (state);
exit (2);
}
lim_free (0, target_name);
}
else
safe_printfmt (2, "no source or destination mirror registered for %s\n", wanted_name);
}
}
if (!temp)
{
safe_printfmt (2, "Unable to connect to target archive %s\n", wanted_name);
talloc_free (state);
exit (2);
}
ar_insert_arch_archive (&state->destinations, ar_size_arch_archive (state->destinations), temp);
}
/**
* \brief find a source archive, that is different from location.
* \param state the state object
* \param arch_pointer the output archive
* \param location to not match
*/
static void
find_source_archive (archive_mirror_state_t * state, t_uchar const * const wanted_name, t_uchar const * const location)
{
int index;
ar_archive_location locations = arch_archive_locations (wanted_name);
/* FIXME: 20050328 order by priority ? */
ar_for_each (locations, index)
{
t_uchar * uncached;
if (!arch_archive_cmp_location (locations[index]->url, location))
continue;
uncached = arch_uncached_location (locations[index]->url);
state->from = talloc_steal (state, arch_archive_connect_location (uncached, 1));
lim_free (0, uncached);
if (state->from && str_cmp (wanted_name, state->from->official_name))
/* should also probe for readonly status */
{
arch_archive_close (state->from);
state->from = NULL;
}
}
ar_free_archive_location (&locations);
}
/**
* \brief connect to the best source
* \param state the state object
* \param spec1 the source spec
*/
static void
setup_from (archive_mirror_state_t * state, t_uchar const * const spec1, t_uchar const * const location)
{
url_param (state, &state->from, spec1);
if (!state->from)
{
if (!arch_valid_archive_name (spec1))
{
/* invalid name, or inaccessible url */
safe_printfmt (2, "Cannot connect to archive %s\n", spec1);
talloc_free (state);
exit (2);
}
find_source_archive (state, spec1, location);
if (!state->from)
{
safe_printfmt (2, "Cannot connect to archive %s\n", spec1);
exit (2);
}
}
}
/**
* \brief figure out what archive connections we need
*
* \param state the mirror state to user
* \param spec1 the first user spec to use
* \param spec2 the second user spec, NULL if no used.
* \param all is an all_mirrors run requested
* \return the second spec if it wasn't used
*/
static t_uchar *
setup_connections (archive_mirror_state_t * state, t_uchar const * const spec1, t_uchar const * const spec2, int all)
{
t_uchar * result = NULL;
/* find the best source available */
if (all)
{
setup_from (state, spec1, NULL);
find_target_archive (state, state->from->official_name, state->from->location, 0, 1);
return str_save (0, spec2);
}
#if 0
1 param url - get name, determine if url is source or dest, magic find other.
1 param name - find best source, any other dest
2 param url url - use as given
2 param url name - use url as source. magic find other.
2 param name url - use url as dest, magic find other
2 param name name - must be
#endif
if (!spec2)
{
/* name or url one param form */
setup_from (state, spec1, NULL);
find_target_archive (state, state->from->official_name, state->from->location, 1, 0);
}
else
{
struct arch_archive * temp = NULL;
url_param (state, &temp, spec2);
if (temp)
ar_insert_arch_archive (&state->destinations, 0, temp);
if (ar_size_arch_archive(state->destinations))
setup_from (state, spec1, state->destinations[0]->location);
else
setup_from (state, spec1, NULL);
if (!ar_size_arch_archive(state->destinations) && !arch_valid_archive_name (spec2) &&
!arch_valid_package_name (spec2, arch_no_archive, arch_req_category, 1))
{
/* param 2 was a url, is not reachable */
safe_printfmt (2, "Cannot connect to archive %s\n", spec2);
talloc_free (state);
exit (2);
}
if (!ar_size_arch_archive(state->destinations))
{
if (!arch_valid_archive_name (spec2))
{
/* name limit */
find_target_archive (state, state->from->official_name, state->from->location, 1, 0);
result = str_save (0, spec2);
}
else
{
/* name name */
find_target_archive (state, spec2, state->from->location, 0, 0);
}
}
}
if (ar_size_arch_archive(state->destinations))
{
if (str_cmp (state->from->official_name, state->destinations[0]->official_name))
{
safe_printfmt (2, "Cannot mirror between archives with different official names. Selected archives:\n%s(%s)\n%s(%s)\n", state->from->official_name, state->from->location, state->destinations[0]->official_name, state->destinations[0]->location);
talloc_free (state);
exit (2);
}
}
return result;
}
int
arch_cmd_archive_mirror (t_uchar * program_name, int argc, char * argv[])
{
int o;
struct opt_parsed * option;
struct arch_archive_mirror_options * mirror_opts = 0;
int do_all_mirrors = 0;
mirror_opts = lim_malloc (0, sizeof (*mirror_opts));
mem_set0 ((t_uchar *)mirror_opts, sizeof (*mirror_opts));
/* TODO factor out a init method */
mirror_opts->print_summary = 0;
mirror_opts->cachedrevs = arch_mirror_all_cachedrevs;
safe_buffer_fd (1, 0, O_WRONLY, 0);
option = 0;
while (1)
{
o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, libarch_version_string, arch_cmd_archive_mirror_help, opt_help_msg, opt_long_help, opt_version);
if (o == opt_none)
break;
switch (o)
{
default:
safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
panic ("internal error parsing arguments");
usage_error:
opt_usage (2, argv[0], program_name, usage, 1);
exit (1);
/* bogus_arg: */
safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
goto usage_error;
case opt_no_cachedrevs:
{
mirror_opts->cachedrevs = arch_mirror_skip_cachedrevs;
break;
}
case opt_summary:
{
mirror_opts->print_summary = 1;
break;
}
case opt_id_cachedrevs:
{
mirror_opts->cachedrevs = arch_mirror_foreign_continuation_cachedrevs;
break;
}
case opt_all_mirrors:
{
do_all_mirrors = -1;
break;
}
}
}
if (argc > 4)
goto usage_error;
{
int a;
t_uchar * from_name = 0;
t_uchar * ambiguous_param = 0;
t_uchar * to_name = 0;
t_uchar * limit_spec = 0;
archive_mirror_state_t *state = archive_mirror_state_new ();
a = 1;
if (argc <= 1)
{
arch_project_tree_t * tree = arch_project_tree_new (talloc_context, ".");
if (tree->archive)
{
from_name = str_save (0, tree->archive);
}
else
{
from_name = arch_my_default_archive (0);
}
if (!from_name)
{
safe_printfmt (2, "%s: no default archive set, and not in a baz project tree->\n", argv[0]);
exit (1);
}
arch_project_tree_delete (tree);
}
else
{
from_name = str_save (0, argv[a]);
++a;
}
if (a < argc)
{
ambiguous_param = str_save (0, argv[2]);
++a;
}
if (!(limit_spec = setup_connections (state, from_name, ambiguous_param, do_all_mirrors)))
{
limit_spec = (argc > a ? str_save (0, argv[a]) : 0);
}
{
int index;
for (index = 0; index < ar_size_arch_archive (state->destinations); ++index)
{
/* YEEEUCK. RBC 20030514 */
state->destinations[index]->client_anticipates_mirror = str_save (0, state->destinations[index]->official_name);
}
}
if (limit_spec && !arch_valid_package_name (limit_spec, arch_no_archive, arch_req_category, 1))
{
safe_printfmt (2, "%s: invalid limit spec name (%s)\n",
argv[0], limit_spec);
exit (2);
}
if (!ar_size_arch_archive (state->destinations))
{
safe_printfmt (2, "%s: no mirror registered for %s\n",
argv[0], from_name);
exit (1);
}
if (do_all_mirrors)
{
int index = 0;
for (index = 0; index < ar_size_arch_archive (state->destinations); ++index)
arch_archive_mirror (1, state->from, state->destinations[index], limit_spec, mirror_opts);
}
else
arch_archive_mirror (1, state->from, state->destinations[0], limit_spec, mirror_opts);
lim_free (0, from_name);
lim_free (0, ambiguous_param);
lim_free (0, to_name);
lim_free (0, limit_spec);
talloc_free (state);
}
lim_free (0, mirror_opts);
return 0;
}
/* tag: Tom Lord Tue Jun 10 14:15:27 2003 (cmd-archive-mirror.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1