/* archives.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 "hackerlab/os/errno.h" #include "hackerlab/bugs/panic.h" #include "hackerlab/char/char-class.h" #include "hackerlab/char/str.h" #include "hackerlab/fmt/cvt.h" #include "hackerlab/mem/mem.h" #include "hackerlab/mem/talloc.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/fs/cwd.h" #include "hackerlab/rx-posix/regex.h" #include "hackerlab/sort/qsort.h" #include "hackerlab/vu/safe.h" #include "libfsutils/tmp-files.h" #include "libfsutils/file-contents.h" #include "libfsutils/read-line.h" #include "libfsutils/dir-listing.h" #include "libinifile/inifile.h" #include "po/gettext.h" #include "libawk/relational.h" #include "libawk/trim.h" #include "libarch/my.h" #include "libarch/namespace.h" #include "libarch/archive-version.h" #include "libarch/cached-archive.h" #include "libarch/debug.h" #include "libarch/archives.h" static t_uchar const * defaults_blurb(void); t_uchar * arch_archive_location_file (t_uchar const * archive_name) { t_uchar * location_dir = 0; t_uchar * answer = 0; if (! arch_valid_archive_name(archive_name)) { int ign; printfmt (&ign, 2, "Invalid archive name `%s'.\n", archive_name); exit (2); } location_dir = arch_my_archive_locations_dir (); answer = file_name_in_vicinity (0, location_dir, archive_name); lim_free (0, location_dir); return answer; } /** * \brief return the file path that a particular archives registry file will be found at */ t_uchar * arch_archive_registry_file (t_uchar const *official_name) { t_uchar *my_archives_dir; my_archives_dir = arch_my_archives_dir(); invariant (str_length (official_name)); return str_replace (my_archives_dir, file_name_in_vicinity (0, my_archives_dir, official_name)); } /** * \brief determine if official_name has any registry-style configuration details * \param official_name the archive to query * \return non zero if the arch does have registry style config details */ int arch_archives_has_registry_entry (t_uchar const *official_name) { t_uchar *entry_file = arch_archive_registry_file (official_name); int result = safe_access (entry_file, F_OK) == 0; lim_free (0, entry_file); return result; } t_uchar * archive_defaults_path (void) { return arch_archive_registry_file ("defaults"); } t_uchar const * defaults_blurb() { return _("# This file lists defaults for archive configurations.\n" "#It is useful to list defaults for GPG signing here.\n" "#anything that goes in an archive registry can be placed in this config file.\n" "#To override a single valued setting (like gpg_command) in a per-archive configuration,\n" "#just set it again in a per-archive configuration file.\n" "#To overide a multiple-valued setting (like URL), use an empty entry\n" "#which will reset that to the built-in defaults. I.e. URL= would empty the list of locations\n" "#for an archive.\n" "#To create a per-archive configuration file, if you are upgrading from baz < 1.3, or tla\n" "#Run baz upgrade. Other wise, you can just edit ~/.arch-params/archives/archive-name\n" "#(This file is created automatically by baz upgrade --all).\n"); } /** * \brief helper function for getting a match from a regex pattern * * FIXME: 20050309 put this in libarch somewhere. * \param pattern a cached regex pattern * \param pattern_string the regex string to compile if needed * \param input the string to match against * \param match the nth match to return */ static t_uchar * regex_get_match (regex_t **pattern, t_uchar const *pattern_string, t_uchar const *input, unsigned int match) { regmatch_t matches[match]; int regexres; if (!*pattern) { int re_error; *pattern = lim_malloc (0, sizeof (**pattern)); re_error = regcomp (*pattern, pattern_string, REG_EXTENDED); invariant (!re_error); } if ((regexres = regexec (*pattern, input, match, matches, 0))) { char buf[50]; if (regexres == REG_NOMATCH) return NULL; regerror (regexres, *pattern, buf, 50); safe_printfmt (2, "failed during regex match for section name: %d %s\n", regexres, buf); return NULL; } return str_save_n (0, input + matches[match - 1].rm_so, matches[match - 1].rm_eo - matches[match - 1].rm_so); } /** * \brief upgrade a signing from from old_path into inifile */ void upgrade_signing_rule (inifile_t *inifile, t_uchar const *old_default_path, int is_default) { #define SIGNINGPATTERN "^[[:space:]]*([^[:space:]]*)[[:space:]]*(((.*)--clearsign[[:space:]]*)?(.*))?$" t_uchar *old_default = file_contents (old_default_path); regex_t * pattern = NULL; t_uchar *gpg_command = regex_get_match (&pattern, SIGNINGPATTERN, old_default, 2); t_uchar *gpg_options1 = regex_get_match (&pattern, SIGNINGPATTERN, old_default, 5); t_uchar *gpg_options2 = regex_get_match (&pattern, SIGNINGPATTERN, old_default, 6); t_uchar *gpg_options = NULL; gpg_options1 = trim_surrounding_ws (gpg_options1); gpg_options2 = trim_surrounding_ws (gpg_options2); if (str_length (gpg_options1)) gpg_options = str_save (0, gpg_options1); if (str_length (gpg_options2)) gpg_options = trim_surrounding_ws(str_replace (gpg_options, str_alloc_cat_many (0, gpg_options1, " ", gpg_options2, str_end))); if (str_chr_index (gpg_options1, '\n') || str_chr_index (gpg_options2, '\n')) { safe_printfmt (2, _("Cannot upgrade ~/.arch-params/signing/=default as it is an unhandled shell script.\n" "Please extract its contents to a real executable shell script and set gpg_command=my_shell_script\n" "in ~/.arch-params/archives/defaults\n")); /* bah, can be bothered factoring these two lines out duplicated below */ inifile_add_key (inifile, "", "", "", _("# Uncomment to use gnome-gpg")); inifile_add_key (inifile, "", "", "", "# gpg_command=gnome-gpg"); /* not translated - options aren't */ } else if (is_default) { inifile_add_key (inifile, "", "", "", "# Imported default signing rules"); if (str_length (gpg_command)) inifile_add_key (inifile, "", "gpg_command", gpg_command, ""); if (str_length (gpg_options)) inifile_add_key (inifile, "", "gpg_options", gpg_options, ""); } regfree (pattern); lim_free (0, old_default); lim_free (0, gpg_command); lim_free (0, gpg_options); lim_free (0, gpg_options1); lim_free (0, gpg_options2); } /** * \brief upgrade a checking rule from from old_path into inifile */ void upgrade_checking_rule (inifile_t *inifile, t_uchar const *old_check_path) { #define CHECKPATTERN "(^[^[:space:]]*gpg-check(\\.awk)?[[:space:]]+gpg_command[[:space:]]*=[[:space:]]*\"(/usr/bin/)?gpg[[:space:]]+--verify-files[[:space:]]+-([[:space:]]+2[[:space:]]*>[[:space:]]*/dev/null)?\"[[:space:]]*$)" t_uchar *old_check = file_contents (old_check_path); regex_t * pattern = NULL; t_uchar * match= regex_get_match (&pattern, CHECKPATTERN, old_check, 1); if (!match) { safe_printfmt (2, "WARNING: Unconvertable check script %s - please set checking policy by hand\n", old_check_path); inifile_add_key (inifile, "", "", "", "# Unconvertable check script was detected."); inifile_add_key (inifile, "", "", "", "# you should place your gpg options in ~/.gnupg/options"); inifile_add_key (inifile, "", "", "", "# and use the allowed_ids and allowed_fingerprints archive "); inifile_add_key (inifile, "", "", "", "# settings to configure policy."); } regfree (pattern); lim_free (0, match); lim_free (0, old_check); } static t_uchar * arch_archives_new_archive_ini_blurb (void) { return _("# This ini file controls the configuration for one archive - \n" "# The archive is named by the same name as the name of this file\n"); } /** * \brief save the ini file */ void arch_archives_save_archive_ini_no_default (t_uchar const * official_name, inifile_t *inifile) { t_uchar *archive_file = arch_archive_registry_file (official_name); inifile_save_merge (inifile, 0, archive_file); lim_free (0, archive_file); } /** * \brief load a defaults-free version of the inifile for a given archive * * if none exists, one will be created * \param official_name the official name of the archive. * \param inifile the inifile to load into, it will be inited during the load. */ void arch_archives_get_archive_ini_no_default (t_uchar const * official_name, inifile_t *inifile) { t_uchar *defaults = archive_defaults_path (); t_uchar *archive_file = arch_archive_registry_file (official_name); inifile_init (inifile); if (safe_access (archive_file, F_OK)) { inifile_process_text (inifile, arch_archives_new_archive_ini_blurb()); inifile_save_merge (inifile, 0, archive_file); } else { inifile_load (inifile, archive_file); } lim_free (0, defaults); lim_free (0, archive_file); return; } /** * \brief load in the inifile for a given archive * * \param official_name the official name of the archive. * \param inifile the inifile to load into, it will be inited during the load. */ void arch_archives_get_archive_ini (t_uchar const *official_name, inifile_t *inifile) { t_uchar *defaults = archive_defaults_path (); t_uchar *archive_file = arch_archive_registry_file (official_name); inifile_init (inifile); inifile_load (inifile, defaults); inifile_load (inifile, archive_file); lim_free (0, archive_file); lim_free (0, defaults); return; } static void ensure_archives_defaults_exists (void) { t_uchar *my_archives_dir; t_uchar *defaults_path; int errn; arch_ensure_my_arch_params(); my_archives_dir = arch_my_archives_dir(); if (vu_mkdir (&errn, my_archives_dir , 0700) && errn != EEXIST) { safe_printfmt (2, "arch: unable to create directory %s\n", my_archives_dir); exit (2); } defaults_path = archive_defaults_path(); if (safe_access (defaults_path, F_OK)) { t_uchar *old_default_path; inifile_t inifile; inifile_init (&inifile); inifile_process_text (&inifile, defaults_blurb()); old_default_path = file_name_in_vicinity (0, arch_my_arch_params(), "signing/=default"); if (!safe_access (old_default_path, F_OK)) upgrade_signing_rule (&inifile, old_default_path, 1); else { /* bah, can be bothered factoring these two lines out duplicated above */ inifile_add_key (&inifile, "", "", "", _("# Uncomment to use gnome-gpg")); inifile_add_key (&inifile, "", "", "", "# gpg_command=gnome-gpg"); } lim_free (0, old_default_path); old_default_path = file_name_in_vicinity (0, arch_my_arch_params(), "signing/=default.check"); if (!safe_access (old_default_path, F_OK)) upgrade_checking_rule (&inifile, old_default_path); lim_free (0, old_default_path); inifile_save_merge (&inifile, 0, defaults_path); } } t_uchar * arch_archive_location (t_uchar const * archive_name, int soft) { t_uchar * file = 0; t_uchar * first_line = 0; t_uchar * start; t_uchar * end; t_uchar * answer = 0; ensure_archives_defaults_exists(); file = arch_archive_location_file (archive_name); if (safe_access (file, F_OK)) { lim_free (0, file); if (soft) return 0; safe_printfmt (2, "archive not registered: %s\n", archive_name); safe_printfmt (2, " (see register-archive)\n"); exit (2); } first_line = read_line_from_file (file); for (start = first_line; char_is_blank (*start); ++start) ; for (end = start; *end && !char_is_space (*start); ++end) ; answer = str_save_n (0, start, end - start); lim_free (0, file); lim_free (0, first_line); return answer; } /** * \brief find all the locations for an archive * \param official_name the archive name to query * \return rel_table of the locations, with parameters as fields */ ar_archive_location arch_archive_locations (t_uchar const *official_name) { return arch_archive_locations_ext (official_name, 0); } /** * \brief Compare the properties of two locations */ static int lcmp(int left, int right) { if (left == right) return 0; else if (left == 2) return -1; else if (right == 2) return 1; else return 0; } /** * \brief This function compares likely up-to-dateness of locations. * * A location is more likely up-to-date if it is * 1. a readonly master (not really, but http is nicer to use) * 2. a writable master * 3. a readable mirror * 4. a writable mirror * \param _left The first location * \param _right The second location * \param context unused * \return -1 if the left location has a higher priority, +1 if the right does */ static int arch_archive_fresh_cmp(void * _left, void *_right, void *context) { arch_archive_location_t * left = *(arch_archive_location_t **)_left; arch_archive_location_t * right = *(arch_archive_location_t **)_right; int result = lcmp (left->master, right->master); if (result == 0) result = lcmp (left->readonly, right->readonly); return result; } static int arch_archive_location_priority_cmp (void * _left, void * _right, void *context) { arch_archive_location_t * left = *(arch_archive_location_t **)_left; arch_archive_location_t * right = *(arch_archive_location_t **)_right; return right->priority - left->priority; } /** * \brief Compare two locations by priority and other properties * * \param _left The first location * \param _right The second location * \param context unused * \return <0 if the left location has a higher priority, >0 if the right does */ static int arch_archive_location_property_cmp(void * left, void *right, void *context) { int result = arch_archive_location_priority_cmp (left, right, context); if (result == 0) result = arch_archive_fresh_cmp (left, right, context); return result; } /** * \brief get a rel_table of locations and properties from an inifile value list * \param values the inifile values and comments * \return a ar_archive_location with the locations and parameters. */ ar_archive_location locations_from_ini_values (rel_table values) { ar_archive_location result = NULL; int index; /* discard any legacy locations */ rel_for_each (values, index) { int result_index = ar_size_archive_location(result); arch_archive_location_t * location = arch_archive_location_new_inivalue (values[index][0]); if (!location->disabled) ar_insert_archive_location (&result, result_index, location); else talloc_free (location); } quicksort (&result[0], ar_size_archive_location (result), sizeof (void *), arch_archive_location_property_cmp, NULL); return result; } /** * \brief find all the locations for an archive * \param official_name the archive name to query * \param new_only do not query legacy registries. * \return ar_archive_location of the locations */ ar_archive_location arch_archive_locations_ext (t_uchar const *official_name, int new_only) { ar_archive_location locations = NULL; t_uchar * found_archive = arch_archive_location (official_name, 1); if (found_archive && !new_only) { ar_insert_archive_location (&locations, 0, arch_archive_location_new (found_archive)); /* we have to present its not readonly for connect_commitable to be able to work */ locations[0]->readonly = 0; } lim_free (0, found_archive); if (arch_archives_has_registry_entry (official_name)) { inifile_t inifile; rel_table values; arch_archives_get_archive_ini (official_name, &inifile); values = inifile_get_key_values (&inifile, "", "url"); if (rel_n_records (values)) { /* discard any legacy locations */ ar_free_archive_location (&locations); locations = locations_from_ini_values(values); } rel_free_table (values); inifile_finalise (&inifile); } return locations; } /** * \brief question for a location in the archives registry * \return non zero on presence */ int arch_archives_archive_has_location (t_uchar const * official_name, t_uchar const * location) { return arch_archives_archive_has_location_ext (official_name, location, 0); } /** * \brief does the archive registry contain a specific archive location * \param new_only don't query legacy registrations * \return non zero on presence */ int arch_archives_archive_has_location_ext (t_uchar const * official_name, t_uchar const * location, int new_only) { ar_archive_location locations = arch_archive_locations_ext (official_name, new_only); int index; int found = 0; ar_for_each (locations, index) { if (!arch_archive_cmp_location (location, locations[index]->url)) found = 1; } ar_free_archive_location (&locations); return found; } void arch_delete_archive_location (t_uchar * archive_name, int force_p) { t_uchar * file = 0; int errn; file = arch_archive_location_file (archive_name); if (vu_unlink (&errn, file)) { if (!(force_p && (errn == ENOENT))) { safe_printfmt (2, "archive not registered: %s\n", archive_name); safe_printfmt (2, " (see register-archive)\n"); exit (2); } } lim_free (0, file); } void arch_archives_delete_archive_location (t_uchar const *official_name, t_uchar const *location, int force) { /* find the url in the registry */ /* remove it */ inifile_t inifile; rel_table values; int index; int offset = 0; t_uchar *uncached_location = arch_uncached_location (location); arch_archives_get_archive_ini_no_default (official_name, &inifile); values = inifile_get_key_values (&inifile, "", "url"); rel_for_each (values, index) { arch_archive_location_t *location = arch_archive_location_new_inivalue (values[index][0]); t_uchar *ini_uncached = arch_uncached_location (location->url); if (!str_cmp (ini_uncached, uncached_location)) { inifile_remove_key (&inifile, "", "url", index - offset++); } lim_free (0, ini_uncached); talloc_free (location); } if (offset) arch_archives_save_archive_ini_no_default (official_name, &inifile); else if (!force) { safe_printfmt (2, _("archive location not registered: %s at %s\n"), official_name, location); safe_printfmt (2, _(" (see register-archive)\n")); exit (2); } rel_free_table (values); lim_free (0, uncached_location); inifile_finalise (&inifile); } /** * \brief generate a list of archives registered by * =locations files * \param place the output in where */ void arch_registered_registered_name_archives (rel_table *where) { t_uchar * dir = 0; rel_table files = 0; int index; dir = arch_my_archive_locations_dir (); files = maybe_directory_files (dir); rel_for_each (files, index) { t_uchar * f; f = files[index][0]; if (str_cmp (".", f) && str_cmp ("..", f) && arch_valid_archive_name (f)) { t_uchar * location = 0; location = arch_archive_location (f, 0); rel_add_records (where, rel_make_record (f, location, 0), 0); lim_free (0, location); } } lim_free (0, dir); rel_free_table (files); } rel_table arch_registered_archives (void) { t_uchar * dir = 0; rel_table files = 0; rel_table answer = 0; rel_table temp_new = NULL; rel_table temp_old = NULL; int index; /* FIXME-REMOVENAMES */ arch_registered_registered_name_archives (&temp_old); dir = arch_my_archives_dir (); files = maybe_directory_files (dir); rel_for_each (files, index) { t_uchar * file; file = files[index][0]; if (arch_valid_archive_name (file)) { ar_archive_location locations = arch_archive_locations (file); if (ar_size_archive_location (locations)) rel_add_records (&temp_new, rel_make_record (file, locations[0]->url, NULL), NULL); ar_free_archive_location (&locations); } } lim_free (0, dir); rel_free_table (files); rel_sort_table_by_field (0, temp_new, 0); rel_sort_table_by_field (0, temp_old, 0); /* old only rows */ answer = rel_join (1, rel_join_output (1, 0, 1, 1, -1) , 0, 0, temp_old, temp_new); /* and the shared and new only ones */ rel_append_x (&answer, temp_new); rel_free_table (temp_new); rel_free_table (temp_old); return answer; } t_uchar * arch_mirrored_at_name (t_uchar const * const archive) { t_uchar * mirror_name = 0; mirror_name = str_alloc_cat (0, archive, "-MIRROR"); return mirror_name; } t_uchar * arch_mirrored_from_name (t_uchar const * const archive) { t_uchar * source_name = 0; source_name = str_alloc_cat (0, archive, "-SOURCE"); return source_name; } t_uchar * arch_mirrored_at (t_uchar const * const archive) { t_uchar * mirror_name = 0; t_uchar * mirror_loc = 0; mirror_name = arch_mirrored_at_name (archive); mirror_loc = arch_archive_location (mirror_name, 1); if (!mirror_loc) { lim_free (0, mirror_name); mirror_name = 0; } lim_free (0, mirror_loc); return mirror_name; } t_uchar * arch_mirrored_from (t_uchar const * const archive) { t_uchar * source_name = 0; t_uchar * source_loc = 0; source_name = arch_mirrored_from_name (archive); source_loc = arch_archive_location (source_name, 1); if (!source_loc) { lim_free (0, source_name); source_name = 0; } lim_free (0, source_loc); return source_name; } t_uchar * arch_fs_archive_archive_version_path (t_uchar * archive_path) { return file_name_in_vicinity (0, archive_path, ".archive-version"); } t_uchar * arch_fs_archive_meta_info_path (t_uchar * archive_path) { return file_name_in_vicinity (0, archive_path, "=meta-info"); } t_uchar * arch_fs_archive_meta_info_item_path (t_uchar * archive_path, t_uchar * meta_info_name) { t_uchar * meta_info_dir = 0; t_uchar * meta_info_path; meta_info_dir = arch_fs_archive_meta_info_path (archive_path); meta_info_path = file_name_in_vicinity (0, meta_info_dir, meta_info_name); lim_free (0, meta_info_dir); return meta_info_path; } /** * \brief legacy call to find signature checking rule */ t_uchar * arch_archives_signature_signing_rule_file (t_uchar const * registered_name) { t_uchar * result = file_name_in_vicinity (0, arch_my_arch_params(), "signing"); result = str_realloc_cat_many (0, result, "/", registered_name, str_end); return result; } t_uchar * arch_archives_signature_checking_rule_file (t_uchar const * registered_name) { t_uchar * result = file_name_in_vicinity (0, arch_my_arch_params(), "signing"); result = str_realloc_cat_many (0, result, "/", registered_name, ".check", str_end); return result; } /* tag: Tom Lord Sun May 18 19:17:40 2003 (archives.c) */