/* 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)
*/
syntax highlighted by Code2HTML, v. 0.9.1