/* wydawca - automatic release submission daemon
Copyright (C) 2007-2013, 2017 Sergey Poznyakoff
Wydawca is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
Wydawca is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with wydawca. If not, see . */
#include "wydawca.h"
#include
/* Triplets are stored in a symtab: */
static struct grecs_symtab *triplet_table;
/* ... and are organized into a doubly-linked list, using the prev and
next members of struct wy_triplet. The list is ordered so that
prev points to a triplet older than this one, and next points to a
newer triplet. The triplet_list variable points to the root of the
list, i.e. the oldest triplet available. */
static struct wy_triplet *triplet_list;
/* Functions for building the ordered doubly-linked list of triplets */
static time_t
triplet_timestamp(struct wy_triplet *tp)
{
int i;
time_t t = 0;
if (!tp)
return 0;
for (i = 0; i < FILE_TYPE_COUNT; i++) {
if (tp->file[i].name &&
(t == 0 || t > tp->file[i].sb.st_mtime))
t = tp->file[i].sb.st_mtime;
}
return t;
}
static time_t
triplet_ttl(struct wy_triplet *tp)
{
time_t t;
if (!tp)
return 0;
t = time(NULL) - triplet_timestamp(tp);
if (t < tp->spool->file_sweep_time)
return tp->spool->file_sweep_time - t;
return 0;
}
void
triplet_list_unlink(struct wy_triplet *tp)
{
if (tp->prev)
tp->prev->next = tp->next;
else
triplet_list = tp->next;
if (tp->next)
tp->next->prev = tp->prev;
tp->next = tp->prev = NULL;
}
static void
triplet_list_insert_before(struct wy_triplet *newp,
struct wy_triplet *anchor)
{
if (!anchor) {
triplet_list = newp;
return;
}
if (anchor->prev)
anchor->prev->next = newp;
else
triplet_list = newp;
newp->prev = anchor->prev;
anchor->prev = newp;
newp->next = anchor;
}
void
triplet_list_ordered_insert(struct wy_triplet *tp)
{
time_t t = triplet_timestamp(tp);
struct wy_triplet *p, *prev = NULL;
for (p = triplet_list; p && triplet_timestamp(p) < t;
prev = p, p = p->next) ;
if (p)
triplet_list_insert_before(tp, p);
else if (prev) {
prev->next = tp;
tp->prev = prev;
tp->next = NULL;
} else {
tp->next = tp->prev = NULL;
triplet_list = tp;
}
}
static struct wy_user *
wy_user_create(struct wy_user *src)
{
struct wy_user *p = grecs_malloc(sizeof(*p));
p->next = NULL;
p->name = src->name;
p->realname = src->realname;
p->gpg_key = src->gpg_key;
p->email = src->email;
p->fpr = NULL;
return p;
}
static void
wy_userlist_free(struct wy_user *wp)
{
while (wp) {
struct wy_user *next = wp->next;
free(wp->fpr);
free(wp);
wp = next;
}
}
/* Functions for operation on a symtab of triplets. */
static unsigned
hash_triplet_hasher(void *data, unsigned long n_buckets)
{
struct wy_triplet const *t = data;
return grecs_hash_string(t->name, n_buckets);
}
/* Compare two strings for equality. */
static int
hash_triplet_compare(void const *data1, void const *data2)
{
struct wy_triplet const *t1 = data1;
struct wy_triplet const *t2 = data2;
return t1->spool == t2->spool && strcmp(t1->name, t2->name);
}
/* Reclaim memory storage associated with a table entry */
void
hash_triplet_free(void *data)
{
int i;
struct wy_triplet *tp = data;
free(tp->name);
for (i = 0; i < FILE_TYPE_COUNT; i++) {
if (tp->file[i].name)
free(tp->file[i].name);
}
free(tp->directive);
free(tp->blurb);
free(tp->tmp);
txtacc_free(tp->acc);
/* Free uploader and admin lists */
wy_userlist_free(tp->uploader_list);
wy_userlist_free(tp->admin_list);
free(tp);
}
char *
triplet_strdup(struct wy_triplet *tp, const char *str)
{
size_t len = strlen(str);
txtacc_grow(tp->acc, str, len + 1);
return txtacc_finish(tp->acc, 0);
}
/* Register a file in the triplet table */
void
register_file(struct file_info *finfo, const struct spool *spool)
{
struct wy_triplet key, *ret;
int install = 1;
if (!triplet_table) {
triplet_table =
grecs_symtab_create(sizeof(struct wy_triplet),
hash_triplet_hasher,
hash_triplet_compare,
NULL,
NULL,
hash_triplet_free);
if (!triplet_table)
grecs_alloc_die();
}
key.name = grecs_malloc(finfo->root_len + 1);
memcpy(key.name, finfo->name, finfo->root_len);
key.name[finfo->root_len] = 0;
key.spool = spool;
ret = grecs_symtab_lookup_or_install(triplet_table, &key, &install);
if (!ret)
grecs_alloc_die();
free(key.name);
if (install) {
ret->spool = spool;
ret->acc = txtacc_create();
}
ret->file[finfo->type] = *finfo;
triplet_list_unlink(ret);
triplet_list_ordered_insert(ret);
}
struct wy_triplet *
triplet_lookup(struct spool *spool, const char *name)
{
struct wy_triplet key, *ret;
struct file_info finfo;
if (!triplet_table)
return NULL;
parse_file_name(name, &finfo);
key.name = grecs_malloc(finfo.root_len + 1);
memcpy(key.name, finfo.name, finfo.root_len);
key.name[finfo.root_len] = 0;
key.spool = spool;
file_info_cleanup(&finfo);
ret = grecs_symtab_lookup_or_install(triplet_table, &key, NULL);
free(key.name);
return ret;
}
/* Return true if any part of the triplet TRP was modified more than
TTL seconds ago */
static int
triplet_expired_p(struct wy_triplet *trp)
{
int i;
time_t now = time(NULL);
time_t ttl = trp->spool->file_sweep_time;
if (ttl == 0)
return 0;
for (i = 0; i < FILE_TYPE_COUNT; i++) {
if (trp->file[i].name
&& (now - trp->file[i].sb.st_mtime) >= ttl) {
wy_debug(1, (_("file %s expired"), trp->file[i].name));
return 1;
}
}
return 0;
}
enum triplet_state {
triplet_directive, /* Short triplet: only a directive is present,
but nothing more is required */
triplet_complete, /* A complete triplet: all three files are
present and have the same owner */
triplet_incomplete, /* Incomplete triplet: some files are
missing */
triplet_bad, /* Bad triplet. Should be removed
immediately. */
};
static enum triplet_state
check_triplet_state(struct wy_triplet *trp, int noauth)
{
if (trp->file[file_directive].name) {
if (verify_directive_file(trp, noauth))
return triplet_bad;
if (trp->file[file_dist].name == 0 &&
trp->file[file_signature].name == 0) {
if (directive_get_value(trp, "filename", NULL))
return triplet_directive;
} else if (trp->file[file_dist].name &&
trp->file[file_signature].name) {
if (trp->file[file_dist].sb.st_uid ==
trp->file[file_signature].sb.st_uid &&
trp->file[file_dist].sb.st_uid ==
trp->file[file_directive].sb.st_uid)
return triplet_complete;
else {
wy_debug(1, (_("%s: invalid triplet: "
"UIDs differ"),
trp->name));
return triplet_bad;
}
}
}
return triplet_incomplete;
}
/* Unlink all parts of the triplet TRP */
int
remove_triplet(struct wy_triplet *trp, int check)
{
int i;
for (i = 0; i < FILE_TYPE_COUNT; i++) {
if (trp->file[i].name) {
if (check) {
if (access(trp->file[i].name, F_OK) == 0) {
wy_log(LOG_NOTICE,
_("%s still exists"),
trp->file[i].name);
return 1;
}
} else {
wy_log(LOG_NOTICE, _("removing %s"),
trp->file[i].name);
if (!wy_dry_run && unlink(trp->file[i].name))
wy_log(LOG_ERR,
_("cannot remove %s: %s"),
trp->file[i].name,
strerror(errno));
}
}
}
triplet_list_unlink(trp);
triplet_jq_unlink(trp);
grecs_symtab_remove(triplet_table, trp);
return 0;
}
time_t
triplet_sweep(void)
{
while (triplet_list && triplet_expired_p(triplet_list))
remove_triplet(triplet_list, 0);
return triplet_ttl(triplet_list);
}
/* Process a single triplet from the table */
static int
triplet_processor(void *data, void *proc_data)
{
struct wy_triplet *trp = data;
wy_debug(1, ("FILE %s, DIST=%s, SIG=%s, DIRECTIVE=%s",
trp->name,
SP(trp->file[file_dist].name),
SP(trp->file[file_signature].name),
SP(trp->file[file_directive].name)));
switch (check_triplet_state(trp, 0)) {
case triplet_directive:
case triplet_complete:
wy_debug(1, (_("processing triplet `%s'"), trp->name));
if (process_directives(trp))
remove_triplet(trp, 0);
return 0;
case triplet_incomplete:
wy_debug(1, (_("%s: incomplete triplet"), trp->name));
/* ignore unless expired (see below); */
UPDATE_STATS(STAT_INCOMPLETE_TRIPLETS);
break;
case triplet_bad:
UPDATE_STATS(STAT_BAD_TRIPLETS);
remove_triplet(trp, 0);
return 0;
}
if (triplet_expired_p(trp)) {
UPDATE_STATS(STAT_EXPIRED_TRIPLETS);
remove_triplet(trp, 0);
}
return 0;
}
/* Process all triplets from the table according to the SPOOL */
void
spool_commit_triplets(struct spool *spool, struct wy_triplet *tplist)
{
wy_debug(1, (_("processing spool %s (%s)"),
spool->tag, wy_url_printable(spool->dest_url)));
if (spool_open_dictionaries(spool))
return;
if (tplist) {
while (tplist) {
struct wy_triplet *next = tplist->jq_next;
if (tplist->spool == spool)
triplet_processor(tplist, NULL);
tplist = next;
}
} else if (triplet_table) {
grecs_symtab_enumerate(triplet_table, triplet_processor, NULL);
grecs_symtab_clear(triplet_table);
}
}
size_t
count_collected_triplets()
{
return triplet_table ? grecs_symtab_count_entries(triplet_table) : 0;
}
struct count_closure {
struct wy_triplet *tp;
};
static int
triplet_linker(void *data, void *proc_data)
{
struct wy_triplet *trp = data;
struct count_closure *cp = proc_data;
if (trp->job)
return 0;
wy_debug(1, ("FILE %s, DIST=%s, SIG=%s, DIRECTIVE=%s",
trp->name,
SP(trp->file[file_dist].name),
SP(trp->file[file_signature].name),
SP(trp->file[file_directive].name)));
switch (check_triplet_state(trp, 1)) {
case triplet_directive:
case triplet_complete:
if (triplet_expired_p(trp))
remove_triplet(trp, 0);
else {
trp->jq_prev = NULL;
if (cp->tp)
cp->tp->jq_prev = trp;
trp->jq_next = cp->tp;
cp->tp = trp;
}
break;
case triplet_incomplete:
break;
case triplet_bad:
remove_triplet(trp, 0);
}
return 0;
}
struct wy_triplet *
link_processable_triplets()
{
struct count_closure clos;
clos.tp = NULL;
if (triplet_table)
grecs_symtab_enumerate(triplet_table, triplet_linker, &clos);
return clos.tp;
}
void
triplet_remove_file(struct spool *spool, const char *name)
{
struct wy_triplet *tp = triplet_lookup(spool, name);
int i, n = 0;
if (!tp)
return;
for (i = 0; i < FILE_TYPE_COUNT; i++) {
if (!tp->file[i].name)
/* nothing */ ;
else if (strcmp(tp->file[i].name, name) == 0)
file_info_cleanup(&tp->file[i]);
else
n++;
}
if (!n) {
wy_debug(1, ("deleting empty triplet (%s/%s)",
spool->source_dir, name));
remove_triplet(tp, 0);
}
}
static const char *
expand_project_base(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->project;
}
static const char *
expand_tag(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->spool->tag;
}
static const char *
expand_url(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->spool->url;
}
static const char *
expand_relative_dir(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
directive_get_value(trp, "directory", (const char **)&def->value);
return def->value;
}
static const char *
expand_dest_dir(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->spool->dest_dir;
}
static const char *
expand_source_dir(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->spool->source_dir;
}
static void
decode_file_mode(mode_t mode, char *string)
{
*string++ = mode & S_IRUSR ? 'r' : '-';
*string++ = mode & S_IWUSR ? 'w' : '-';
*string++ = (mode & S_ISUID ? (mode & S_IXUSR ? 's' : 'S')
: (mode & S_IXUSR ? 'x' : '-'));
*string++ = mode & S_IRGRP ? 'r' : '-';
*string++ = mode & S_IWGRP ? 'w' : '-';
*string++ = (mode & S_ISGID ? (mode & S_IXGRP ? 's' : 'S')
: (mode & S_IXGRP ? 'x' : '-'));
*string++ = mode & S_IROTH ? 'r' : '-';
*string++ = mode & S_IWOTH ? 'w' : '-';
*string++ = (mode & S_ISVTX ? (mode & S_IXOTH ? 't' : 'T')
: (mode & S_IXOTH ? 'x' : '-'));
*string = '\0';
}
/* Width of "user/group size", with initial value chosen
heuristically. This grows as needed, though this may cause some
stairstepping in the output. Make it too small and the output will
almost always look ragged. Make it too large and the output will
be spaced out too far. */
static int ugswidth = 19;
static int
format_file_data(struct wy_triplet *trp, enum file_type type, char **pret)
{
char modes[11];
struct file_info *info = trp->file + type;
char timebuf[sizeof "YYYY-MM-DD HH:MM:SS +0000"];
struct passwd *pw;
struct group *grp;
char *sptr = NULL;
size_t slen = 0;
int pad;
char *user_name;
char *group_name;
struct tm *tm;
char *buf = NULL;
size_t size = 0;
if (!info->name)
return 1;
/* MODE OWNER GROUP SIZE MTIME FILE_NAME MD5SUM? */
modes[0] = '-'; /* Only regular files are allowed */
decode_file_mode(info->sb.st_mode, modes + 1);
/* File time */
tm = localtime(&info->sb.st_mtime);
strftime(timebuf, sizeof timebuf, "%Y-%m-%d %H:%M:%S %z", tm);
pw = getpwuid(TRIPLET_UID(trp));
if (!pw)
user_name = "unknown";
else
user_name = pw->pw_name;
grp = getgrgid(TRIPLET_GID(trp));
if (!grp)
group_name = "unknown"; /* should not happen */
else
group_name = grp->gr_name;
/* Size */
if (grecs_asprintf
(&sptr, &slen, "%lu", (unsigned long)info->sb.st_size))
grecs_alloc_die();
/* Figure out padding and format the buffer */
slen = strlen(sptr);
pad = strlen(user_name) + 1 + strlen(group_name) + 1 + slen;
if (pad > ugswidth)
ugswidth = pad;
if (grecs_asprintf(&buf, &size,
"%s %s %s %*s %s %s",
modes, user_name, group_name, ugswidth - pad + slen,
sptr, timebuf, info->name))
grecs_alloc_die();
free(sptr);
*pret = buf;
return 0;
}
static const char *
expand_triplet_ls_full(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
char *buf[FILE_TYPE_COUNT] = { NULL, NULL, NULL };
size_t size = 0;
if (format_file_data(trp, file_dist, &buf[file_dist]) == 0)
size += strlen(buf[file_dist]) + 1;
if (format_file_data(trp, file_signature, &buf[file_signature]) == 0)
size += strlen(buf[file_signature]) + 1;
if (format_file_data(trp, file_directive, &buf[file_directive]) == 0)
size += strlen(buf[file_directive]) + 1;
def->value = def->storage = grecs_malloc(size + 1);
def->value[0] = 0;
if (buf[file_dist]) {
strcat(def->value, buf[file_dist]);
strcat(def->value, "\n");
}
if (buf[file_signature]) {
strcat(def->value, buf[file_signature]);
strcat(def->value, "\n");
}
if (buf[file_directive]) {
strcat(def->value, buf[file_directive]);
strcat(def->value, "\n");
}
free(buf[file_dist]);
free(buf[file_signature]);
free(buf[file_directive]);
return def->value;
}
static const char *
expand_triplet_ls_upload(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
char *buf[2] = { NULL, NULL };
size_t size = 0;
if (format_file_data(trp, file_dist, &buf[file_dist]) == 0)
size += strlen(buf[file_dist]) + 1;
if (format_file_data(trp, file_signature, &buf[file_signature]) == 0)
size += strlen(buf[file_signature]) + 1;
def->value = def->storage = grecs_malloc(size + 1);
def->value[0] = 0;
if (buf[file_dist]) {
strcat(def->value, buf[file_dist]);
strcat(def->value, "\n");
}
if (buf[file_signature]) {
strcat(def->value, buf[file_signature]);
strcat(def->value, "\n");
}
free(buf[file_dist]);
free(buf[file_signature]);
return def->value;
}
static const char *
expand_triplet_dist(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->file[file_dist].name;
}
static const char *
expand_triplet_sig(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->file[file_signature].name;
}
static const char *
expand_triplet_dir(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->file[file_directive].name;
}
static const char *
expand_triplet_ls_dist(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
format_file_data(trp, file_dist, &def->storage);
def->value = def->storage;
return def->value;
}
static const char *
expand_triplet_ls_sig(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
format_file_data(trp, file_signature, &def->storage);
def->value = def->storage;
return def->value;
}
static const char *
expand_triplet_ls_directive(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
format_file_data(trp, file_directive, &def->storage);
def->value = def->storage;
return def->value;
}
static const char *
expand_user_name(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
if (trp->uploader)
return trp->uploader->name;
def->value = "UNKNOWN";
return def->value;
}
static const char *
expand_user_real_name(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
if (trp->uploader)
return trp->uploader->realname;
def->value = "UNKNOWN";
return def->value;
}
static const char *
expand_user_email(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
if (trp->uploader)
return trp->uploader->email;
def->value = "UNKNOWN";
return def->value;
}
static const char *
expand_email_user(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
if (trp->uploader) {
size_t size = 0;
if (grecs_asprintf(&def->storage, &size, "\"%s\" <%s>",
trp->uploader->realname,
trp->uploader->email))
grecs_alloc_die();
def->value = def->storage;
}
return def->value;
}
static const char *
expand_report(struct wy_metadef *def, void *data)
{
return report_string;
}
static const char *
expand_comment(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
if (directive_get_value(trp, "comment", (const char **)&def->value))
def->value = "";
return def->value;
}
static const char *
expand_check_diagn(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
return trp->check_diag;
}
static const char *
expand_check_result(struct wy_metadef *def, void *data)
{
struct wy_triplet *trp = data;
int status = trp->check_result;
if (status == 0)
return def->value = "0";
else if (WIFEXITED(status)) {
size_t size = 0;
def->storage = NULL;
if (grecs_asprintf(&def->storage, &size,
"%d", WEXITSTATUS(status)))
grecs_alloc_die();
} else if (WIFSIGNALED(status)) {
size_t size = 0;
def->storage = NULL;
if (grecs_asprintf(&def->storage, &size, "SIG+%d",
WTERMSIG(status)))
grecs_alloc_die();
} else
return def->value = "[unrecognized return code]";
return def->value = def->storage;
}
#define DECL_EXPAND_TIMER(what) \
static const char * \
wy_s_cat2(expand_timer_,what) (struct wy_metadef *def, void *data) \
{ \
wydawca_timer_t t = timer_stop ((char*)def->data); \
def->storage = timer_format_time (wy_s_cat2(timer_get_,what) (t)); \
return def->value = def->storage; \
}
DECL_EXPAND_TIMER(real)
DECL_EXPAND_TIMER(user)
DECL_EXPAND_TIMER(system)
#define DECL_TIMER(name,t) \
{ "timer:" #name ":" #t, NULL, wy_s_cat2(expand_timer_,t), NULL, #name }
#define DECL_FULL_TIMER(name) \
DECL_TIMER(name, real), \
DECL_TIMER(name, user), \
DECL_TIMER(name, system)
struct wy_metadef triplet_default_meta[] = {
{ "u", NULL, expand_user_name, NULL },
{ "user", NULL, expand_user_name, NULL },
{ "user:name", NULL, expand_user_name, NULL },
{ "p", NULL, expand_project_base, NULL },
{ "project", NULL, expand_project_base, NULL },
{ "url", NULL, expand_url, NULL },
{ "spool", NULL, expand_tag, NULL },
{ "dir", NULL, expand_relative_dir, NULL },
{ "dest-dir", NULL, expand_dest_dir, NULL },
{ "source-dir", NULL, expand_source_dir, NULL },
{ "comment", NULL, expand_comment, NULL },
{ NULL }
};
char *
triplet_expand_dictionary_query(struct dictionary *dict, void *handle,
struct wy_triplet const *trp)
{
char *p = meta_expand_string(dict->query, triplet_default_meta,
(void*) trp,
dict, handle);
meta_free(triplet_default_meta);
return p;
}
struct wy_metadef triplet_meta[] = {
{ "project", NULL, expand_project_base, NULL },
{ "url", NULL, expand_url, NULL },
{ "spool", NULL, expand_tag, NULL },
{ "dir", NULL, expand_relative_dir, NULL },
{ "dest-dir", NULL, expand_dest_dir, NULL },
{ "source-dir", NULL, expand_source_dir, NULL },
{ "triplet:dist", NULL, expand_triplet_dist, NULL },
{ "triplet:sig", NULL, expand_triplet_sig, NULL },
{ "triplet:dir", NULL, expand_triplet_dir, NULL },
{ "triplet:ls:full", NULL, expand_triplet_ls_full, NULL },
{ "triplet:ls:upload", NULL, expand_triplet_ls_upload, NULL },
{ "triplet:ls:dist", NULL, expand_triplet_ls_dist, NULL },
{ "triplet:ls:sig", NULL, expand_triplet_ls_sig, NULL },
{ "triplet:ls:dir", NULL, expand_triplet_ls_directive, NULL },
{ "check:result", NULL, expand_check_result, NULL },
{ "check:diagn", NULL, expand_check_diagn, NULL },
{ "user", NULL, expand_user_name, NULL },
{ "user:name", NULL, expand_user_name, NULL },
{ "user:real-name", NULL, expand_user_real_name, NULL },
{ "user:email", NULL, expand_user_email, NULL },
{ "email:user", NULL, expand_email_user, NULL },
{ "report", NULL, expand_report, NULL },
{ "comment", NULL, expand_comment, NULL },
DECL_FULL_TIMER(wydawca),
DECL_FULL_TIMER(triplet),
DECL_FULL_TIMER(spool),
{ NULL}
};
char *
wy_triplet_expand_param(const char *tmpl, struct wy_triplet const *trp,
struct wy_metadef *xmeta)
{
char *p;
struct wy_metadef *mp = metadef_join(triplet_meta, xmeta);
p = meta_expand_string(tmpl, mp, (void *)trp, NULL, NULL);
meta_free(mp);
free(mp);
return p;
}
struct wy_user *
wy_triplet_get_uploaders(struct wy_triplet *trp)
{
const struct spool *spool;
struct dictionary *dict;
void *md;
char *command;
int rc;
size_t nrows, ncols, i;
struct wy_user *head, *tail;
if (trp->uploader_list)
return trp->uploader_list;
ASGN_SPOOL(spool, trp, return NULL);
dict = spool->dictionary[project_uploader_dict];
md = dictionary_open(dict);
if (!md)
return NULL;
command = triplet_expand_dictionary_query(dict, md, trp);
rc = dictionary_lookup(dict, md, command);
free(command);
if (rc) {
wy_log(LOG_ERR, _("cannot get uploaders for %s"), trp->name);
dictionary_close(dict, md);
return NULL;
}
nrows = dictionary_num_rows(dict);
if (nrows == 0) {
wy_log(LOG_ERR, _("found no uploaders for %s"), trp->name);
dictionary_close(dict, md);
return NULL;
}
ncols = dictionary_num_cols(dict);
if (ncols < 4) {
wy_log(LOG_ERR,
_("project-uploader dictionary error: "
"too few columns (%lu)"),
(unsigned long)ncols);
dictionary_close(dict, md);
return NULL;
}
head = tail = NULL;
for (i = 0; i < nrows; i++) {
const char *p;
struct wy_user info, *ptr;
memset(&info, 0, sizeof(info));
p = dictionary_result(dict, md, i, 0);
if (p)
info.name = triplet_strdup(trp, p);
p = dictionary_result(dict, md, i, 1);
if (p)
info.realname = triplet_strdup(trp, p);
p = dictionary_result(dict, md, i, 2);
if (p)
info.email = triplet_strdup(trp, p);
p = dictionary_result(dict, md, i, 3);
if (p)
info.gpg_key = triplet_strdup(trp, p);
if (wy_debug_level > 3) {
wy_log(LOG_DEBUG, _("name: %s"),
SP(info.name));
wy_log(LOG_DEBUG, _("realname: %s"),
SP(info.realname));
wy_log(LOG_DEBUG, _("gpg-key: %s"),
SP(info.gpg_key));
wy_log(LOG_DEBUG, _("email: %s"),
SP(info.email));
}
if (!info.name || !info.realname || !info.gpg_key ||
!info.email) {
wy_log(LOG_ERR,
_("project-uploader dictionary error: "
"malformed row %lu"),
(unsigned long)i);
/* FIXME: Memory allocated for `info' will
be reclaimed only when the triplet is
freed. */
continue;
}
ptr = wy_user_create(&info);
if (tail)
tail->next = ptr;
else
head = ptr;
tail = ptr;
}
dictionary_close(dict, md);
if (!head) {
wy_log(LOG_ERR, _("no valid uploaders found for %s"),
trp->name);
return NULL;
}
trp->uploader_list = head;
return head;
}
struct wy_user *
wy_triplet_get_uploader(struct wy_triplet *trp)
{
return trp->uploader;
}
struct wy_user *
wy_triplet_get_admins(struct wy_triplet *trp)
{
const struct spool *spool;
struct dictionary *dict;
void *md;
char *command;
int rc;
size_t nrows, ncols, i;
struct wy_user *head, *tail;
if (trp->admin_list)
return trp->admin_list;
ASGN_SPOOL(spool, trp, return NULL);
dict = spool->dictionary[project_owner_dict];
if (dict->type == dictionary_none) {
wy_log(LOG_ERR,
_("%s: dictionary %s not configured (spool %s)"),
trp->name, "project_owner_dict", spool->tag);
return NULL;
}
md = dictionary_open(dict);
if (!md) {
wy_log(LOG_ERR,
_("%s: failed to open dictionary %s (spool %s)"),
trp->name, "project_owner_dict", spool->tag);
return NULL;
}
command = triplet_expand_dictionary_query(dict, md, trp);
rc = dictionary_lookup(dict, md, command);
free(command);
if (rc) {
wy_log(LOG_ERR,
_("%s: cannot obtain recipient emails"),
trp->name);
dictionary_close(dict, md);
return NULL;
}
nrows = dictionary_num_rows(dict);
ncols = dictionary_num_cols(dict);
if (nrows == 0) {
wy_log(LOG_ERR,
_("%s: cannot obtain recipient emails"),
trp->name);
return NULL;
}
head = tail = NULL;
for (i = 0; i < nrows; i++) {
const char *p;
struct wy_user info, *ptr;
memset(&info, 0, sizeof(info));
p = dictionary_result(dict, md, i, 0);
if (p)
info.email = triplet_strdup(trp, p);
if (ncols > 0 && (p = dictionary_result(dict, md, i, 1)))
info.realname = triplet_strdup(trp, p);
if (wy_debug_level > 3) {
wy_log(LOG_DEBUG, _("realname: %s"),
SP(info.realname));
wy_log(LOG_DEBUG, _("email: %s"),
SP(info.email));
}
ptr = wy_user_create(&info);
if (tail)
tail->next = ptr;
else
head = ptr;
tail = ptr;
}
dictionary_close(dict, md);
trp->admin_list = head;
return trp->admin_list;
}
const char *
wy_triplet_project(struct wy_triplet *trp)
{
return trp->project;
}