/* 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) */