mirror of https://github.com/vmware/tdnf.git
480 lines
14 KiB
C
480 lines
14 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <glob.h>
|
|
|
|
#include "../../common/config.h"
|
|
|
|
#include "../../llconf/nodes.h"
|
|
#include "../../llconf/modules.h"
|
|
#include "../../llconf/entry.h"
|
|
#include "../../llconf/ini.h"
|
|
#include "../../llconf/strutils.h"
|
|
|
|
#include "../../jsondump/jsondump.h"
|
|
|
|
|
|
#define ERR_CMDLINE 1
|
|
#define ERR_SYSTEM 2
|
|
#define ERR_NO_REPO 3
|
|
#define ERR_NO_SETTING 4
|
|
#define ERR_REPO_EXISTS 5
|
|
#define ERR_JSON 6
|
|
|
|
#define pr_err(fmt, ...) \
|
|
fprintf(stderr, fmt, ##__VA_ARGS__)
|
|
|
|
#define fail(rc, fmt, ...) { \
|
|
pr_err(fmt, ##__VA_ARGS__); \
|
|
exit(rc); \
|
|
}
|
|
|
|
#define check_cond(COND) if(!(COND)) { \
|
|
pr_err("check_cond failed in %s line %d\n", \
|
|
__FUNCTION__, __LINE__); \
|
|
rc = -1; \
|
|
((void)(rc)); /* suppress "set but not used" warning */ \
|
|
goto error; \
|
|
}
|
|
|
|
#define check_ptr(ptr) if(!(ptr)) { \
|
|
pr_err("check_ptr failed in %s line %d\n", \
|
|
__FUNCTION__, __LINE__); \
|
|
rc = -1; \
|
|
((void)(rc)); /* suppress "set but not used" warning */ \
|
|
goto error; \
|
|
}
|
|
|
|
#define check_rc(rc) if((rc) != 0) { \
|
|
pr_err("check_rc failed in %s line %d\n", \
|
|
__FUNCTION__, __LINE__); \
|
|
goto error; \
|
|
}
|
|
|
|
#define safe_free(ptr) { if ((ptr) != NULL) { free(ptr); ptr = NULL; }}
|
|
|
|
struct cnfmodule *mod_ini = NULL;
|
|
|
|
static void
|
|
set_key_value(struct cnfnode *cn_repo, const char *keyval)
|
|
{
|
|
const char *p = keyval;
|
|
char key[256], *q = key;
|
|
struct cnfnode *cn_keyval = NULL;
|
|
|
|
/* parse key */
|
|
while(*p &&
|
|
(!isspace(*p) && (*p != '=')) &&
|
|
q < key+sizeof(key)-1)
|
|
*(q++) = *(p++);
|
|
*q = 0;
|
|
|
|
skip_spaces(&p);
|
|
|
|
if(*p == '=') {
|
|
p++;
|
|
skip_spaces(&p);
|
|
/* p is now pointing to the value */
|
|
cn_keyval = find_child(cn_repo, key);
|
|
if (cn_keyval == NULL) {
|
|
cn_keyval = create_cnfnode(key);
|
|
cnfnode_setval(cn_keyval, p);
|
|
append_node(cn_repo, cn_keyval);
|
|
} else {
|
|
cnfnode_setval(cn_keyval, p);
|
|
}
|
|
} else
|
|
fail(ERR_CMDLINE, "expected '=' after key %s\n", key);
|
|
}
|
|
|
|
static void
|
|
set_key_values(struct cnfnode *cn_root, const char *repo, char *kv[])
|
|
{
|
|
struct cnfnode *cn_repo = find_child(cn_root, repo);
|
|
if (cn_repo) {
|
|
for (int i = 0; kv[i]; i++) {
|
|
set_key_value(cn_repo, kv[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void remove_keys(struct cnfnode *cn_root, const char *repo, char *keys[])
|
|
{
|
|
struct cnfnode *cn_repo = find_child(cn_root, repo);
|
|
if (cn_repo) {
|
|
for (int i = 0; keys[i]; i++) {
|
|
struct cnfnode *cn_keyval = find_child(cn_repo, keys[i]);
|
|
if (cn_keyval) {
|
|
unlink_node(cn_keyval);
|
|
destroy_cnfnode(cn_keyval);
|
|
} else
|
|
fail(ERR_NO_SETTING, "key '%s' not found\n", keys[i]);
|
|
}
|
|
} else
|
|
fail(ERR_NO_REPO, "repo '%s' not found\n", repo);
|
|
}
|
|
|
|
static
|
|
void remove_repo(struct cnfnode *cn_root, const char *repo)
|
|
{
|
|
struct cnfnode *cn_repo = find_child(cn_root, repo);
|
|
if (cn_repo) {
|
|
unlink_node(cn_repo);
|
|
destroy_cnftree(cn_repo);
|
|
} else
|
|
fail(ERR_NO_REPO, "repo '%s' not found\n", repo);
|
|
}
|
|
static
|
|
char *get_repodir(const char *main_config)
|
|
{
|
|
char *repodir = NULL;
|
|
struct cnfnode *cn_root = cnfmodule_parse_file(mod_ini, main_config);
|
|
|
|
if (cn_root) {
|
|
struct cnfnode *cn_main = find_child(cn_root, "main");
|
|
if (cn_main) {
|
|
const struct cnfnode *cn_repodir = find_child(cn_main, TDNF_CONF_KEY_REPODIR);
|
|
if (cn_repodir) {
|
|
repodir = strdup(cnfnode_getval(cn_repodir));
|
|
}
|
|
}
|
|
}
|
|
if (repodir == NULL)
|
|
repodir = strdup(TDNF_DEFAULT_REPO_LOCATION);
|
|
|
|
if (cn_root)
|
|
destroy_cnftree(cn_root);
|
|
|
|
return repodir;
|
|
}
|
|
|
|
static
|
|
struct cnfnode *find_repo(const char *repodir, const char *repo, char **pfilename)
|
|
{
|
|
struct cnfnode *cn_root = NULL;
|
|
char pattern[256];
|
|
glob_t globbuf = {0};
|
|
int i, rc = 0;
|
|
|
|
snprintf(pattern, sizeof(pattern), "%s/*.repo", repodir);
|
|
rc = glob(pattern, 0, NULL, &globbuf);
|
|
check_cond(rc == 0 || rc == GLOB_NOMATCH);
|
|
if (rc == 0) {
|
|
for (i = 0; globbuf.gl_pathv[i]; i++) {
|
|
const struct cnfnode *cn_repo = NULL;
|
|
|
|
cn_root = cnfmodule_parse_file(mod_ini, globbuf.gl_pathv[i]);
|
|
check_ptr(cn_root);
|
|
cn_repo = find_child(cn_root, repo);
|
|
if (cn_repo) {
|
|
if (pfilename)
|
|
*pfilename = strdup(globbuf.gl_pathv[i]);
|
|
break;
|
|
}
|
|
destroy_cnftree(cn_root);
|
|
cn_root = NULL;
|
|
}
|
|
}
|
|
|
|
error:
|
|
/* leave dead code for future error conditions */
|
|
/* coverity[dead_error_condition] */
|
|
if (rc && cn_root) {
|
|
destroy_cnftree(cn_root);
|
|
cn_root = NULL;
|
|
}
|
|
globfree(&globbuf);
|
|
return cn_root;
|
|
}
|
|
|
|
static
|
|
struct cnfnode *get_repo_root(const char *main_config, const char *repo, char **pfilename)
|
|
{
|
|
struct cnfnode *cn_root = NULL;
|
|
|
|
if (strcmp(repo, "main") == 0) {
|
|
cn_root = cnfmodule_parse_file(mod_ini, main_config);
|
|
if (cn_root == NULL)
|
|
fail(ERR_SYSTEM, "could not parse config file %s\n", main_config);
|
|
if (pfilename)
|
|
*pfilename = strdup(main_config);
|
|
} else {
|
|
char *repodir = get_repodir(main_config);
|
|
|
|
cn_root = find_repo(repodir, repo, pfilename);
|
|
safe_free(repodir);
|
|
if (cn_root == NULL)
|
|
fail(ERR_NO_REPO, "repo '%s' not found\n", repo);
|
|
}
|
|
return cn_root;
|
|
}
|
|
|
|
static
|
|
int write_file(struct cnfnode *cn_root, const char *filename)
|
|
{
|
|
int rc = 0;
|
|
char buf[256];
|
|
|
|
snprintf(buf, sizeof(buf), "%s.tmp", filename);
|
|
rc = cnfmodule_unparse_file(mod_ini, buf, cn_root);
|
|
check_cond(rc == 0);
|
|
|
|
rc = rename(buf, filename);
|
|
check_cond(rc == 0);
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
struct json_dump *cnftree2json(struct cnfnode *cn_root)
|
|
{
|
|
int rc = 0;
|
|
struct json_dump *jd = jd_create(0);
|
|
|
|
jd_map_start(jd);
|
|
if (cn_root->first_child) {
|
|
struct json_dump *jd_child = cnftree2json(cn_root->first_child);
|
|
check_ptr(jd_child);
|
|
rc = jd_map_add_child(jd, cn_root->name, jd_child);
|
|
check_rc(rc);
|
|
jd_destroy(jd_child);
|
|
} else {
|
|
for (struct cnfnode *cn = cn_root; cn; cn = cn->next) {
|
|
rc = jd_map_add_string(jd, cn->name, cn->value);
|
|
check_rc(rc);
|
|
}
|
|
}
|
|
error:
|
|
if (rc && jd){
|
|
jd_destroy(jd);
|
|
jd = NULL;
|
|
}
|
|
return jd;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
const char *main_config = TDNF_CONF_FILE;
|
|
const char *repo_config = NULL;
|
|
int do_json = 0;
|
|
|
|
while(1) {
|
|
int c;
|
|
|
|
static struct option long_options[] = {
|
|
{"config", 1, 0, 'c'},
|
|
{"file", 1, 0, 'f'},
|
|
{"json", 1, 0, 'j'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
c = getopt_long(argc, argv, "c:f:j",
|
|
long_options, NULL);
|
|
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch(c){
|
|
case 'c':
|
|
main_config = optarg;
|
|
break;
|
|
case 'f':
|
|
repo_config = optarg;
|
|
break;
|
|
case 'j':
|
|
do_json = 1;
|
|
break;
|
|
case '?':
|
|
default:
|
|
/* If it's an error, getopt has already produced an error message. */
|
|
//usage();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
register_ini(NULL);
|
|
mod_ini = find_cnfmodule("ini");
|
|
|
|
/*
|
|
* Process the action(s).
|
|
*/
|
|
if (optind < argc) {
|
|
int argcount = 0;
|
|
int rc = 0;
|
|
const char *action = NULL;
|
|
const char *repo = NULL;
|
|
char *filename = NULL;
|
|
struct cnfnode *cn_root = NULL;
|
|
|
|
while (optind + argcount < argc)
|
|
argcount++;
|
|
|
|
/*
|
|
* Find the action.
|
|
*/
|
|
action = argv[optind];
|
|
if(strcmp(action, "edit") == 0 || strcmp(action, "create") == 0) {
|
|
if (argcount < 2)
|
|
fail(ERR_CMDLINE, "expected main or repo name\n");
|
|
|
|
repo = argv[optind+1];
|
|
|
|
if (argcount < 3)
|
|
fail(ERR_CMDLINE, "Expected at least one setting.");
|
|
|
|
if (strcmp(action, "edit") == 0)
|
|
cn_root = get_repo_root(main_config, repo, &filename);
|
|
else { /* create repo config */
|
|
if(strcmp(repo, "main") != 0) {
|
|
char buf[256];
|
|
char *repodir = get_repodir(main_config);
|
|
if (!find_repo(repodir, repo, NULL)) {
|
|
struct cnfnode *cn_repo = create_cnfnode(repo);
|
|
|
|
if (repo_config == NULL) {
|
|
cn_root = create_cnfnode("(root)");
|
|
snprintf(buf, sizeof(buf), "%s/%s.repo", repodir, repo);
|
|
filename = strdup(buf);
|
|
} else {
|
|
cn_root = cnfmodule_parse_file(mod_ini, repo_config);
|
|
if (cn_root == NULL) {
|
|
if (errno == ENOENT)
|
|
cn_root = create_cnfnode("(root)");
|
|
else
|
|
fail(ERR_SYSTEM, "could not parse config file %s\n", repo_config);
|
|
}
|
|
filename = strdup(repo_config);
|
|
}
|
|
append_node(cn_root, cn_repo);
|
|
|
|
} else
|
|
fail(ERR_REPO_EXISTS, "repo '%s' already exists\n", repo);
|
|
safe_free(repodir);
|
|
} else
|
|
fail(ERR_CMDLINE, "invalid repo name 'main'\n");
|
|
}
|
|
|
|
if (cn_root) {
|
|
set_key_values(cn_root, repo, &argv[optind+2]);
|
|
if (filename) {
|
|
rc = write_file(cn_root, filename);
|
|
if (rc != 0)
|
|
fail(ERR_SYSTEM, "failed to write file '%s': %s (%d)", filename, strerror(errno), errno);
|
|
|
|
safe_free(filename);
|
|
} else
|
|
cnfmodule_unparse(mod_ini, stdout, cn_root);
|
|
|
|
destroy_cnftree(cn_root);
|
|
}
|
|
} else if (strcmp(action, "get") == 0) {
|
|
if (argcount < 2)
|
|
fail(ERR_CMDLINE, "expected main or repo name\n");
|
|
|
|
repo = argv[optind+1];
|
|
|
|
if (argcount < 3)
|
|
fail(ERR_CMDLINE, "expected one setting\n");
|
|
|
|
cn_root = get_repo_root(main_config, repo, NULL);
|
|
|
|
if (cn_root) {
|
|
struct cnfnode *cn_repo = find_child(cn_root, repo);
|
|
if (cn_repo) {
|
|
const struct cnfnode *cn_keyval = find_child(cn_repo, argv[optind+2]);
|
|
if (cn_keyval)
|
|
printf("%s\n", cn_keyval->value);
|
|
else
|
|
fail(ERR_NO_SETTING, "'%s' not found in '%s'\n", argv[optind+2], repo);
|
|
} else
|
|
fail(ERR_NO_REPO, "repo '%s' not found\n", repo);
|
|
|
|
destroy_cnftree(cn_root);
|
|
}
|
|
} else if (strcmp(action, "remove") == 0) {
|
|
if (argcount < 2)
|
|
fail(ERR_CMDLINE, "expected main or repo name\n");
|
|
|
|
repo = argv[optind+1];
|
|
|
|
if (argcount < 3)
|
|
fail(ERR_CMDLINE, "expected one setting\n");
|
|
|
|
cn_root = get_repo_root(main_config, repo, &filename);
|
|
|
|
if (cn_root) {
|
|
remove_keys(cn_root, repo, &argv[optind+2]);
|
|
if (filename) {
|
|
rc = write_file(cn_root, filename);
|
|
if (rc != 0)
|
|
fail(ERR_SYSTEM, "failed to write file '%s': %s (%d)", filename, strerror(errno), errno);
|
|
|
|
safe_free(filename);
|
|
} else
|
|
cnfmodule_unparse(mod_ini, stdout, cn_root);
|
|
|
|
destroy_cnftree(cn_root);
|
|
}
|
|
} else if (strcmp(action, "removerepo") == 0) {
|
|
if (argcount < 2)
|
|
fail(ERR_CMDLINE, "expected main or repo name\n");
|
|
|
|
repo = argv[optind+1];
|
|
|
|
cn_root = get_repo_root(main_config, repo, &filename);
|
|
|
|
if (cn_root) {
|
|
remove_repo(cn_root, repo);
|
|
if (filename) {
|
|
if (cn_root->first_child) {
|
|
rc = write_file(cn_root, filename);
|
|
if (rc != 0)
|
|
fail(ERR_SYSTEM, "failed to write file '%s': %s (%d)", filename, strerror(errno), errno);
|
|
} else {
|
|
rc = unlink(filename);
|
|
if (rc != 0)
|
|
fail(ERR_SYSTEM, "failed to remove file '%s': %s (%d)", filename, strerror(errno), errno);
|
|
}
|
|
safe_free(filename);
|
|
} else
|
|
cnfmodule_unparse(mod_ini, stdout, cn_root);
|
|
destroy_cnftree(cn_root);
|
|
}
|
|
} else if (strcmp(action, "dump") == 0) {
|
|
if (argcount < 2)
|
|
fail(ERR_CMDLINE, "expected main or repo name\n");
|
|
|
|
repo = argv[optind+1];
|
|
|
|
cn_root = get_repo_root(main_config, repo, &filename);
|
|
|
|
if (cn_root) {
|
|
struct cnfnode *cn_repo = find_child(cn_root, repo);
|
|
|
|
if (!do_json)
|
|
cnfmodule_unparse(mod_ini, stdout, cn_root);
|
|
else {
|
|
struct json_dump *jd = cnftree2json(cn_repo);
|
|
unlink_node(cn_repo); /* do not dump siblings */
|
|
if (jd) {
|
|
printf("%s", jd->buf);
|
|
jd_destroy(jd);
|
|
} else
|
|
fail(ERR_JSON, "failed to generate json\n");
|
|
/* we unlinked this so need to free explicitely */
|
|
destroy_cnftree(cn_repo);
|
|
}
|
|
destroy_cnftree(cn_root);
|
|
}
|
|
} else
|
|
fail(ERR_CMDLINE, "Unknown command '%s'\n", action);
|
|
|
|
safe_free(filename);
|
|
}
|
|
}
|