/* This file is part of Eclat.
Copyright (C) 2012, 2013 Sergey Poznyakoff.
Eclat 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, or (at your option)
any later version.
Eclat 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 Eclat. If not, see . */
#include "eclat.h"
char *conffile = SYSCONFDIR "/eclat.conf" ;
int lint_mode;
int dry_run_mode;
int match_command_mode;
int preprocess_only = 0;
char *endpoint = "ec2.amazonaws.com";
char *signature_version = "2";
char *access_key;
char *secret_key;
char *region_name;
int use_ssl;
int ssl_verify_peer = 1;
char *ssl_ca_file;
char *ssl_ca_path;
char *format_expr_option;
char *format_file_option;
char *format_name_option;
int sort_option;
char *format_file;
char *test_map_name;
enum eclat_confirm_mode confirm_mode;
int translate_option = -1;
FILE *xml_dump_file;
static char *categories[] = {
"main",
"cfgram",
"cflex",
"conf",
"curl",
};
static void
debug_init()
{
int i;
for (i = 0; i < sizeof(categories)/sizeof(categories[0]); i++)
debug_register(categories[i]);
}
static int
node_ident_cmp(struct grecs_node const *a, struct grecs_node const *b)
{
return strcmp(a->ident, b->ident);
}
void listcmd(char *fmt);
#include "cmdline.h"
#define CMD_MOD 0x01
#define CMD_DESTR 0x02
#define CMD_NOQRY 0x04
struct eclat_command cmdtab[] = {
{ "start", "start-instances", "StartInstances", eclat_start_instance,
CMD_MOD },
{ "stop", "stop-instances", "StopInstances", eclat_stop_instance,
CMD_MOD },
{ "reboot", "reboot-instances", "RebootInstances", eclat_reboot_instance,
CMD_MOD },
{ "lsaddr", "describe-addresses", "DescribeAddresses",
eclat_describe_addresses },
{ "lstag", "describe-tags", "DescribeTags", eclat_describe_tags },
{ "lsiattr", "describe-instance-attribute", "DescribeInstanceAttribute",
eclat_describe_instance_attribute },
{ "lsistat", "describe-instance-status", "DescribeInstanceStatus",
eclat_describe_instance_status },
{ "lsinst", "describe-instances", "DescribeInstances",
eclat_describe_instances },
{ "lsvol", "describe-volumes", "DescribeVolumes",
eclat_describe_volumes },
{ "mkaddr", "allocate-address", "AllocateAddress",
eclat_allocate_address, CMD_MOD },
{ "rmaddr", "release-address", "ReleaseAddress",
eclat_release_address, CMD_MOD|CMD_DESTR },
{ "assocaddr", "associate-address", "AssociateAddress",
eclat_associate_address, CMD_MOD },
{ "disasaddr", "disassociate-address", "DisassociateAddress",
eclat_disassociate_address, CMD_MOD },
{ "mktag", "create-tags", "CreateTags",
eclat_create_tags, CMD_MOD },
{ "rmtag", "delete-tags", "DeleteTags",
eclat_delete_tags, CMD_MOD|CMD_DESTR },
{ "dmesg", "get-console-output", "GetConsoleOutput",
eclat_get_console_output },
{ "lssg", "describe-security-groups", "DescribeSecurityGroups",
eclat_describe_security_groups },
{ "mksnap", "create-snapshot", "CreateSnapshot",
eclat_create_snapshot },
{ "lssnap", "describe-snapshots", "DescribeSnapshots",
eclat_describe_snapshots },
{ "lssattr", "describe-snapshot-attribute", "DescribeSnapshotAttribute",
eclat_describe_snapshot_attribute },
{ "setsattr", "modify-snapshot-attribute", "ModifySnapshotAttribute",
eclat_modify_snapshot_attribute, CMD_MOD },
{ "clrsattr", "reset-snapshot-attribute", "ResetSnapshotAttribute",
eclat_reset_snapshot_attribute, CMD_MOD|CMD_DESTR },
{ "rmsnap", "delete-snapshot", "DeleteSnapshot",
eclat_delete_snapshot, CMD_MOD|CMD_DESTR },
{ "lszon", "describe-availability-zones", "DescribeAvailabilityZones",
eclat_describe_avaialbility_zones },
{ "lsreg", "describe-regions", "DescribeRegions",
eclat_describe_regions },
{ "mkvol", "create-volume", "CreateVolume",
eclat_create_volume, CMD_MOD },
{ "rmvol", "delete-volume", "DeleteVolume",
eclat_delete_volume, CMD_MOD|CMD_DESTR },
{ "atvol", "attach-volume", "AttachVolume",
eclat_attach_volume, CMD_MOD },
{ "devol", "detach-volume", "DetachVolume",
eclat_detach_volume, CMD_MOD|CMD_DESTR },
{ "setiattr", "modify-instance-attribute", "ModifyInstanceAttribute",
eclat_modify_instance_attribute, CMD_MOD },
{ "lsimg", "describe-images", "DescribeImages",
eclat_describe_images },
{ "mkinst", "run-instances", "RunInstances",
eclat_run_instances, CMD_MOD },
{ "mkimg", "create-image", "CreateImage",
eclat_create_image, CMD_MOD },
{ "deimg", "deregister-image", "DeregisterImage",
eclat_deregister_image, CMD_MOD|CMD_DESTR },
{ "cpimg", "copy-image", "CopyImage",
eclat_copy_image },
{ "cpsnap", "copy-snapshot", "CopySnapshot",
eclat_copy_snapshot },
{ "lsattr", NULL, NULL,
eclat_lsattr, CMD_NOQRY },
{ "sg", NULL, NULL, eclat_sg },
{ "mksg", "create-security-group", "CreateSecurityGroup",
eclat_create_security_group },
{ "rmsg", "delete-security-group", "DeleteSecurityGroup",
eclat_delete_security_group, CMD_MOD|CMD_DESTR }
};
size_t cmdcnt = sizeof(cmdtab) / sizeof(cmdtab[0]);
static int
cmdcmp(const void *a, const void *b)
{
struct eclat_command const *cmda = a;
struct eclat_command const *cmdb = b;
if (cmda->name && cmdb->name)
return strcmp(cmda->name, cmdb->name);
else if (cmda->ident && cmdb->ident)
return strcmp(cmda->ident, cmdb->ident);
else if (cmda->ident || cmda->name)
return 127;
else if (cmdb->ident || cmdb->name)
return -127;
return 0;
}
static void
sortcmds()
{
qsort(cmdtab, cmdcnt, sizeof(cmdtab[0]), cmdcmp);
}
/* Format:
%[pfx:][sfx][ni]
For example, to format the table of available commands for doc/eclat.1:
eclat -l '\t\\fB%n\\fR\t\\fB%i\\fR\n'
To format a "see also" list of commands (ibidem):
eclat -l '%.BR eclat-: (1)n,\n'
*/
int
parsespec(char *start, char **endp, char *fchr, char *s[], int l[])
{
char *p;
int n = 0;
++start;
s[0] = start;
s[1] = NULL;
l[0] = l[1] = 0;
for (p = start; *p; p++) {
if (*p == ':' || strchr(fchr, *p)) {
if (n > 1)
return -1;
l[n++] = p - start;
if (*p != ':') {
*endp = p + 1;
return *p;
}
if (n == 1)
s[n] = start = p + 1;
}
}
return -1;
}
void
fmtstr(const char *str, int len)
{
while (len--) {
int c = *str++;
if (c == '\\') {
if (len) {
len--;
c = wordsplit_c_unquote_char(*str++);
}
}
fputc(c, stdout);
}
}
void
listcmd(char *fmt)
{
struct eclat_command *cp;
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) {
char *p = fmt;
char *start = fmt;
char *end;
char *s[2];
int l[2];
const char *str;
while (*p) {
if (*p == '%') {
switch (parsespec(p, &end, "ni", s, l)) {
case 'n':
str = cp->name;
break;
case 'i':
str = cp->ident;
break;
default:
p++;
continue;
}
if (str) {
if (p != start)
fmtstr(start, p - start);
fmtstr(s[0], l[0]);
printf("%s", str);
fmtstr(s[1], l[1]);
}
start = p = end;
} else {
p++;
}
}
if (p != start)
fmtstr(start, p - start);
}
}
void
listcmdhook()
{
struct eclat_command *cp;
printf("Available commands\n");
printf("Eclat name EC2 Name\n");
printf("-----------+---------\n");
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) {
printf(" %-10s", cp->name ? cp->name : "");
if (cp->ident)
printf (" %s", cp->ident);
putchar ('\n');
}
printf("\nRun \"%s COMMAND --help\" to get help on a particular command.\n",
program_name);
putchar('\n');
}
#define NO_MATCH 0
#define EXACT_MATCH 1
#define PARTIAL_MATCH 2
static int
ident_matches(const char *p, const char *q)
{
int match = EXACT_MATCH;
for (;; p++, q++) {
if (*p == 0)
return *q == 0 ? match : NO_MATCH;
else if (*q == 0)
return PARTIAL_MATCH;
else if (*p == *q)
continue;
else if (*q == '-') {
for (; *p != '-'; p++)
if (!*p)
return NO_MATCH;
match = PARTIAL_MATCH;
} else
break;
}
return NO_MATCH;
}
struct eclat_command *
find_command_name(const char *name)
{
struct eclat_command *cp, *match = NULL;
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) {
if (cp->name && strcmp(cp->name, name) == 0)
return cp;
if (!cp->ident)
continue;
switch (ident_matches(cp->ident, name)) {
case NO_MATCH:
break;
case EXACT_MATCH:
return cp;
case PARTIAL_MATCH:
if (!match)
match = cp;
else {
/* Second match */
err("ambiguous command %s:", name);
err(" %s", match->ident);
err(" %s", cp->ident);
while (++cp < cmdtab + cmdcnt)
if (ident_matches(cp->ident, name) !=
NO_MATCH)
err(" %s", cp->ident);
exit(EX_USAGE);
}
}
}
return match;
}
static void
print_matching_commands(const char *pat)
{
struct eclat_command *cp;
size_t patlen = strlen (pat);
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) {
if (cp->name && strlen(cp->name) >= patlen &&
memcmp(cp->name, pat, patlen) == 0)
printf("%s\n", cp->name);
if (cp->ident && ident_matches(cp->ident, pat) != NO_MATCH)
printf("%s\n", cp->ident);
}
}
struct eclat_command *
find_command_tag(const char *tag)
{
struct eclat_command *cp;
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) {
if (eclat_actcmp(cp->tag, tag) == 0)
return cp;
}
return NULL;
}
void
set_command_confirmation(const char *name, enum eclat_confirm_mode cfmode,
grecs_locus_t *locus)
{
struct eclat_command *cp;
int flag = 0;
if (strcmp(name, "all") == 0)
flag = CMD_MOD;
else if (strcmp(name, "destructive") == 0)
flag = CMD_DESTR;
else {
cp = find_command_tag(name);
if (!cp)
grecs_error(locus, 0, "unknown command or class");
else
cp->confirm = cfmode;
return;
}
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) {
if (cp->flags & flag)
cp->confirm = cfmode;
}
}
void
set_command_format(const char *name, const char *format, grecs_locus_t *locus)
{
struct eclat_command *cp = find_command_tag(name);
if (!cp) {
grecs_error(locus, 0, "unknown command");
return;
}
if (cp->fmt) {
grecs_error(locus, 0, "format %s redefined", name);
grecs_error(&cp->locus, 0, "this is the location of "
"the previous definition");
free(cp->fmt);
}
cp->fmt = grecs_strdup(format);
cp->locus = *locus;
}
forlan_eval_env_t
compile_format_file(const char *fmtfile)
{
forlan_eval_env_t env;
FILE *fp;
struct grecs_locus_point pt;
fp = fopen(fmtfile, "r");
if (!fp)
die(EX_UNAVAILABLE, "cannot open format file \"%s\": %s",
fmtfile, strerror(errno));
pt.file = (char*) fmtfile;
pt.line = 1;
pt.col = 0;
env = forlan_parse_file(fp, &pt);
fclose(fp);
if (!env)
exit(EX_UNAVAILABLE);
return env;
}
struct filter_descr *available_filters;
static char *aws_typestr[] = {
"String",
"dateType",
"Boolean",
"Integer"
};
void
list_filters(FILE *fp)
{
struct filter_descr *flt;
size_t ncols = get_scr_cols();
size_t col, start;
fprintf(fp, "Available filters are:\n");
for (flt = available_filters; flt->name; flt++) {
col = fprintf(fp, " %-24s ", flt->name);
if (col > 26) {
col = start = 26;
fprintf(fp, "\n%*.*s", (int)start, (int)start, "");
}
if (flt->type != FILTER_ENUM)
fprintf(fp, "%s", aws_typestr[flt->type]);
else {
int i;
char *delim;
size_t len;
col += fprintf(fp, "One of:");
start = col + 1;
delim = " ";
for (i = 0; flt->avail[i]; i++) {
len = strlen(delim) + strlen(flt->avail[i]);
if (col + len >= ncols) {
fprintf(fp, ",\n%*.*s",
(int)start, (int)start, "");
col = start;
delim = "";
}
col += fprintf(fp, "%s%s", delim,
flt->avail[i]);
delim = ", ";
}
}
fputc('\n', fp);
}
fputc('\n', fp);
}
struct format_defn {
const char *name;
char *text;
grecs_locus_t locus;
};
static struct grecs_symtab *format_table;
void
define_format(const char *name, const char *fmt, grecs_locus_t *loc)
{
struct format_defn key;
struct format_defn *ent;
int install = 1;
if (!format_table) {
format_table = grecs_symtab_create_default(
sizeof(struct format_defn));
if (!format_table)
grecs_alloc_die();
}
key.name = (char*) name;
ent = grecs_symtab_lookup_or_install(format_table, &key, &install);
if (!ent)
grecs_alloc_die();
if (!install) {
grecs_error(loc, 0, "redefinition of format \"%s\"", name);
grecs_error(&ent->locus, 0,
"this is the place of the original definition");
free(ent->text);
}
ent->text = grecs_strdup(fmt);
ent->locus = *loc;
}
forlan_eval_env_t
find_format(const char *name)
{
struct format_defn key;
struct format_defn *defn;
forlan_eval_env_t env;
if (!format_table)
return NULL;
key.name = (char*) name;
defn = grecs_symtab_lookup_or_install(format_table, &key, NULL);
if (!defn)
return NULL;
env = forlan_parse_buffer(defn->text, strlen(defn->text),
&defn->locus.beg);
return env;
}
static forlan_eval_env_t
read_format(struct eclat_command *cmd)
{
forlan_eval_env_t env = NULL;
if (format_expr_option) {
struct grecs_locus_point pt;
pt.file = "";
pt.line = 1;
pt.col = 0;
env = forlan_parse_buffer(format_expr_option,
strlen(format_expr_option),
&pt);
} else if (format_file_option)
env = compile_format_file(format_file_option);
else if (format_name_option)
env = find_format(format_name_option);
else if (!cmd)
return NULL;
else if (cmd->fmt)
env = forlan_parse_buffer(cmd->fmt, strlen(cmd->fmt),
&cmd->locus.beg);
else if (format_file) {
const char *kwe[5];
struct grecs_locus_point pt;
FILE *fp;
char *filename;
kwe[0] = "command";
kwe[1] = cmd->ident ? cmd->ident : cmd->name;
kwe[2] = "action";
kwe[3] = cmd->tag;
kwe[4] = NULL;
filename = eclat_expand_kw(format_file, kwe);
fp = fopen(filename, "r");
if (!fp) {
if (errno == ENOENT) {
debug(ECLAT_DEBCAT_MAIN, 1,
("cannot open format source \"%s\": %s",
filename, strerror(errno)));
free(filename);
return NULL;
}
die(EX_UNAVAILABLE,
"cannot open format file %s: %s",
filename, strerror(errno));
}
pt.file = filename;
pt.line = 1;
pt.col = 0;
env = forlan_parse_file(fp, &pt);
fclose(fp);
free(filename);
} else
return NULL;
if (!env)
exit(EX_UNAVAILABLE);
return env;
}
int
eclat_do_command(eclat_command_env_t *env, struct eclat_command *command,
int argc, char **argv)
{
int rc;
if (!(command->flags & CMD_NOQRY)) {
env->query = eclat_query_create(use_ssl ? EC2_QF_HTTPS : 0,
endpoint, "/");
}
if (command->tag)
eclat_query_add_param(env->query, "Action", command->tag);
rc = command->handler(env, argc, argv);
if (rc == 0 && !(command->flags & CMD_NOQRY)) {
if (!eclat_confirm(confirm_mode,
"Proceed with %s", command->ident))
die(EX_CANCELLED, "command not confirmed");
rc = eclat_send_query(env->curl, env->query);
}
return rc;
}
int
main(int argc, char **argv)
{
int rc;
struct grecs_node *tree;
struct eclat_io *io;
struct grecs_node *xmltree;
forlan_eval_env_t env = NULL;
struct eclat_command *command = NULL;
eclat_command_env_t cmdenv;
set_program_name(argv[0]);
proginfo.print_help_hook = listcmdhook;
debug_init();
forlan_init();
eclat_map_init();
eclat_map_drv_register(&eclat_map_drv_null);
eclat_map_drv_register(&eclat_map_drv_file);
eclat_map_drv_register(&eclat_map_drv_seq);
eclat_map_drv_register(&eclat_map_drv_bidi);
#ifdef WITH_GDBM
eclat_map_drv_register(&eclat_map_drv_gdbm);
#endif
#ifdef WITH_LDAP
eclat_map_drv_register(&eclat_map_drv_ldap);
#endif
eclat_map_drv_register(&eclat_map_drv_ec2);
sortcmds();
config_init();
parse_options(&argc, &argv);
if (match_command_mode) {
if (argc > 1)
die(EX_USAGE, "wrong number of arguments");
print_matching_commands(argc == 1 ? argv[0] : "");
return 0;
}
if (argc && !test_map_name) {
command = find_command_name(argv[0]);
if (!command)
die(EX_USAGE, "unrecognized command");
}
grecs_gram_trace(debug_level(ECLAT_DEBCAT_CFGRAM));
grecs_lex_trace(debug_level(ECLAT_DEBCAT_CFLEX));
if (preprocess_only)
exit(grecs_preproc_run(conffile, grecs_preprocessor) ?
EX_CONFIG : 0);
if (access(conffile, R_OK) == 0) {
tree = grecs_parse(conffile);
if (!tree)
exit(EX_CONFIG);
config_finish(tree);
/* Prevent texttab from being freed by grecs_tree_free.
FIXME: A dirty kludge, needed to preserve file names
in locus structures. A proper solution would be to have
our own texttab for that purpose. */
tree->v.texttab = NULL;
grecs_tree_free(tree);
} else if (errno == ENOENT) {
warn("no configuration file");
run_config_finish_hooks();
} else
die(errno == EACCES ? EX_NOPERM : EX_OSFILE,
"cannot access \"%s\": %s",
conffile, strerror(errno));
if (translate_option != -1)
translation_enabled = translate_option;
if (test_map_name) {
int i;
if (argc < 1)
die(EX_USAGE, "wrong number of arguments");
translate_ids(argc, argv, test_map_name);
for (i = 0; i < argc; i++) {
printf("%s\n", argv[i]);
}
return 0;
}
env = read_format(command);
if (lint_mode)
exit(0);
if (!command)
die(EX_USAGE, "no command given");
if (region_name) {
endpoint = region_to_endpoint(region_name);
if (!endpoint)
die(EX_USAGE,
"cannot find endpoint for region %s", region_name);
}
if (!secret_key) {
if (get_access_creds(access_key, &access_key, &secret_key))
die(EX_UNAVAILABLE,
"cannot find authentication credentials");
}
debug(ECLAT_DEBCAT_MAIN, 1, ("using access key %s", access_key));
io = eclat_io_init(1);
if (confirm_mode == eclat_confirm_unspecified)
confirm_mode = command->confirm;
/* Prepare environment */
memset(&cmdenv, 0, sizeof(cmdenv));
cmdenv.cmd = command;
cmdenv.curl = io->curl;
rc = eclat_do_command(&cmdenv, command, argc, argv);
if (rc)
exit(rc);
xmltree = eclat_io_finish(io);
if (xml_dump_file)
fclose(xml_dump_file);
if (sort_option)
grecs_tree_sort(xmltree, node_ident_cmp);
if (dry_run_mode)
/* nothing */;
else if (env) {
rc = forlan_run(env, xmltree);
} else {
grecs_print_node(xmltree, GRECS_NODE_FLAG_DEFAULT, stdout);
fputc('\n', stdout);
}
eclat_map_free_all();
exit(rc);
}