/* This file is part of Eclat.
Copyright (C) 2012 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 preprocess_only = 0;
char *endpoint = "ec2.amazonaws.com";
int use_ssl;
char *access_key;
char *secret_key;
char *region_name;
char *format_expr_option;
char *format_file_option;
char *format_name_option;
int sort_option;
char *format_file;
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 void
dump(const char *text, FILE *stream, unsigned char *ptr, size_t size)
{
size_t i;
size_t c;
unsigned int width = 0x10;
int hex = debug_level(ECLAT_DEBCAT_CURL) > 2;
if (!hex)
/* without the hex output, we can fit more on screen */
width = 0x40;
fprintf(stream, "%s, %zd bytes (0x%zx)\n", text, size, size);
for (i = 0; i < size; i += width) {
fprintf(stream, "%04zx: ", i);
if (hex) {
for (c = 0; c < width; c++)
if (i+c < size)
fprintf(stream, "%02x ", ptr[i+c]);
else
fputs(" ", stream);
}
for(c = 0; (c < width) && (i+c < size); c++) {
/* check for CRLf; if found, skip past and start a
new line of output */
if (!hex && (i + c + 1 < size) &&
ptr[i+c] == '\r' && ptr[i+c+1] == '\n') {
i += (c + 2 -width);
break;
}
fprintf(stream, "%c",
isprint(ptr[i+c]) ? ptr[i+c] : '.');
/* check again for CRLF, to avoid an extra \n if
it's at width */
if (!hex && (i + c + 2 < size) &&
ptr[i+c+1] == '\r' && ptr[i+c+2] == '\n') {
i += (c + 3 - width);
break;
}
}
fputc('\n', stream); /* newline */
}
fflush(stream);
}
static int
eclat_trace_fun(CURL *handle, curl_infotype type,
char *data, size_t size,
void *userp)
{
struct data *config = (struct data *)userp;
const char *text;
switch (type) {
case CURLINFO_TEXT:
fprintf(stderr, "== Info: %s", data);
default: /* in case a new one is introduced to shock us */
return 0;
case CURLINFO_HEADER_OUT:
text = "=> Send header";
break;
case CURLINFO_DATA_OUT:
text = "=> Send data";
break;
case CURLINFO_SSL_DATA_OUT:
text = "=> Send SSL data";
break;
case CURLINFO_HEADER_IN:
text = "<= Recv header";
break;
case CURLINFO_DATA_IN:
text = "<= Recv data";
break;
case CURLINFO_SSL_DATA_IN:
text = "<= Recv SSL data";
break;
}
dump(text, stderr, (unsigned char *)data, size);
return 0;
}
static void
dump_text(FILE *stream, int line, int column, const char *text, size_t len)
{
fprintf(stream, "%02d:%02d: ", line, column);
while (len--) {
char c = *text++;
if (c == '\r')
continue;
fputc(c, stream);
++column;
if (c == '\n') {
++line;
column = 0;
fprintf(stream, "%02d:%02d: ", line, column);
}
}
fputc('\n', stream);
}
static void
dumpxml(void *ptr, size_t realsize)
{
static int open_failed = 0;
if (open_failed)
return;
if (!xml_dump_file) {
xml_dump_file = fopen(XML_DUMP_FILE_NAME, "w");
if (!xml_dump_file) {
err("cannot open dump file %s: %s",
XML_DUMP_FILE_NAME, strerror(errno));
open_failed = 1;
return;
}
}
fwrite(ptr, realsize, 1, xml_dump_file);
}
static size_t
write_callback(void *ptr, size_t size, size_t nmemb, void *data)
{
size_t realsize = size * nmemb;
XML_Parser parser = data;
enum XML_Status status;
int line = XML_GetCurrentLineNumber(parser);
int column = XML_GetCurrentColumnNumber(parser);
if (debug_level(ECLAT_DEBCAT_MAIN) >= 10)
dumpxml(ptr, realsize);
status = XML_Parse(parser, ptr, realsize, 0);
if (status == XML_STATUS_ERROR) {
enum XML_Error error = XML_GetErrorCode(parser);
line = XML_GetCurrentLineNumber(parser);
column = XML_GetCurrentColumnNumber(parser);
/* FIXME: better diagnostics. */
die(EX_SOFTWARE, "XML parse error at %d:%d: %s",
line, column, XML_ErrorString(error));
}
return realsize;
}
static int
node_ident_cmp(struct grecs_node const *a, struct grecs_node const *b)
{
return strcmp(a->ident, b->ident);
}
#include "cmdline.h"
struct command {
const char *ident;
const char *tag;
eclat_command_handler_t handler;
char *fmt;
struct grecs_locus locus;
};
struct command cmdtab[] = {
{ "start-instances", "StartInstances", eclat_start_instance },
{ "stop-instances", "StopInstances", eclat_stop_instance },
{ "reboot-instances", "RebootInstances", eclat_reboot_instance },
{ "describe-addresses", "DescribeAddresses",
eclat_describe_addresses },
{ "describe-tags", "DescribeTags", eclat_describe_tags },
{ "describe-instance-status", "DescribeInstanceStatus",
eclat_describe_instance_status },
{ "describe-instances", "DescribeInstances",
eclat_describe_instances },
{ "describe-volumes", "DescribeVolumes",
eclat_describe_volumes },
{ "associate-address", "AssociateAddress",
eclat_associate_address },
{ "disassociate-address", "DisassociateAddress",
eclat_disassociate_address },
{ "get-console-output", "GetConsoleOutput",
eclat_get_console_output }
};
size_t cmdcnt = sizeof(cmdtab) / sizeof(cmdtab[0]);
static int
cmdcmp(const void *a, const void *b)
{
struct command const *cmda = a, *cmdb = b;
return strcmp(cmda->ident, cmdb->ident);
}
void
listcmd()
{
struct command *cp;
printf("Available commands:\n");
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++)
printf(" %s\n", cp->ident);
printf("\nRun \"%s COMMAND --help\" to get help on a particular command.\n",
program_name);
putchar('\n');
}
struct command *
find_command_name(const char *name)
{
struct command *cp, *match = NULL;
size_t namelen = strlen(name);
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) {
if (!strncmp(cp->ident, name, namelen)) {
if (strlen(cp->ident) == namelen)
return cp;
else if (!match)
match = cp;
else {
/* Second match */
err("ambiguous command %s:");
err(" %s", match->ident);
err(" %s", cp->ident);
while (++cp < cmdtab + cmdcnt)
if (!strncmp(cp->ident, name, namelen))
err(" %s", cp->ident);
exit(EX_USAGE);
}
}
}
return match;
}
struct command *
find_command_tag(const char *tag)
{
struct command *cp;
for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) {
if (strcmp(cp->tag, tag) == 0)
return cp;
}
return NULL;
}
void
set_command_format(const char *name, const char *format, grecs_locus_t *locus)
{
struct 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", start, 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",
start, 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 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) {
struct wordsplit ws;
const char *kve[5];
struct grecs_locus_point pt;
FILE *fp;
kve[0] = "command";
kve[1] = cmd->ident;
kve[2] = "action";
kve[3] = cmd->tag;
kve[4] = NULL;
ws.ws_env = kve;
if (wordsplit(format_file, &ws,
WRDSF_NOSPLIT | WRDSF_NOCMD |
WRDSF_ENV | WRDSF_ENV_KV))
die(EX_SOFTWARE,
"error expanding format-file: %s",
wordsplit_strerror(&ws));
fp = fopen(ws.ws_wordv[0], "r");
if (!fp) {
if (errno == ENOENT) {
debug(ECLAT_DEBCAT_MAIN, 1,
("cannot open format source \"%s\": %s",
ws.ws_wordv[0], strerror(errno)));
wordsplit_free(&ws);
return NULL;
}
die(EX_UNAVAILABLE,
"cannot open format file %s: %s",
ws.ws_wordv[0], strerror(errno));
}
pt.file = ws.ws_wordv[0];
pt.line = 1;
pt.col = 0;
env = forlan_parse_file(fp, &pt);
fclose(fp);
wordsplit_free(&ws);
} else
return NULL;
if (!env)
exit(EX_UNAVAILABLE);
return env;
}
int
main(int argc, char **argv)
{
int index, rc;
struct grecs_node *tree;
CURL *curl;
XML_Parser parser;
eclat_partial_tree_t part;
struct grecs_node *xmltree;
forlan_eval_env_t env = NULL;
struct command *command = NULL;
set_program_name(argv[0]);
proginfo.print_help_hook = listcmd;
debug_init();
forlan_init();
config_init();
parse_options(argc, argv, &index);
argc -= index;
argv += index;
if (argc) {
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(EX_OSFILE, "cannot access \"%s\": %s",
conffile, strerror(errno));
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));
curl = curl_easy_init();
if (!curl)
die(EX_UNAVAILABLE, "curl_easy_init failed");
if (debug_level(ECLAT_DEBCAT_CURL)) {
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
if (debug_level(ECLAT_DEBCAT_CURL) > 1)
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
eclat_trace_fun);
}
/* Create XML parser */
parser = XML_ParserCreate("UTF-8");
if (!parser)
die(EX_SOFTWARE, "cannot create XML parser");
XML_SetElementHandler(parser,
eclat_partial_tree_start_handler,
eclat_partial_tree_end_handler);
XML_SetCharacterDataHandler(parser,
eclat_partial_tree_data_handler);
part = eclat_partial_tree_create();
XML_SetUserData(parser, part);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, parser);
rc = command->handler(curl, argc, argv);
curl_easy_cleanup(curl);
XML_Parse(parser, "", 0, 1);
if (xml_dump_file)
fclose(xml_dump_file);
xmltree = eclat_partial_tree_finish(part);
if (sort_option)
grecs_tree_sort(xmltree, node_ident_cmp);
if (env) {
forlan_run(env, xmltree);
} else {
grecs_print_node(xmltree, GRECS_NODE_FLAG_DEFAULT, stdout);
fputc('\n', stdout);
}
exit(rc);
}