/*
* augtool.c:
*
* Copyright (C) 2007-2016 David Lutterkort
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: David Lutterkort <dlutter@redhat.com>
*/
#include <config.h>
#include "augeas.h"
#include "internal.h"
#include "safe-alloc.h"
#include <readline/readline.h>
#include <readline/history.h>
#include <argz.h>
#include <getopt.h>
#include <limits.h>
#include <ctype.h>
#include <locale.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <stdarg.h>
#include <sys/time.h>
/* Global variables */
static augeas *aug = NULL;
static const char *const progname = "augtool";
static unsigned int flags = AUG_NONE;
const char *root = NULL;
char *loadpath = NULL;
char *transforms = NULL;
char *loadonly = NULL;
size_t transformslen = 0;
size_t loadonlylen = 0;
const char *inputfile = NULL;
int echo_commands = 0; /* Gets also changed in main_loop */
bool print_version = false;
bool auto_save = false;
bool interactive = false;
bool timing = false;
/* History file is ~/.augeas/history */
char *history_file = NULL;
#define AUGTOOL_PROMPT "augtool> "
/*
* General utilities
*/
/* Private copy of xasprintf from internal to avoid Multiple definition in
* static builds.
*/
static int _xasprintf(char **strp, const char *format, ...) {
va_list args;
int result;
va_start (args, format);
result = vasprintf (strp, format, args);
va_end (args);
if (result < 0)
*strp = NULL;
return result;
}
static int child_count(const char *path) {
char *pat = NULL;
int r;
if (path[strlen(path)-1] == SEP)
r = asprintf(&pat, "%s*", path);
else
r = asprintf(&pat, "%s/*", path);
if (r < 0)
return -1;
r = aug_match(aug, pat, NULL);
free(pat);
return r;
}
static char *readline_path_generator(const char *text, int state) {
static int current = 0;
static char **children = NULL;
static int nchildren = 0;
static char *ctx = NULL;
char *end = strrchr(text, SEP);
if (end != NULL)
end += 1;
if (state == 0) {
char *path;
if (end == NULL) {
if ((path = strdup("*")) == NULL)
return NULL;
} else {
if (ALLOC_N(path, end - text + 2) < 0)
return NULL;
strncpy(path, text, end - text);
strcat(path, "*");
}
for (;current < nchildren; current++)
free((void *) children[current]);
free((void *) children);
nchildren = aug_match(aug, path, &children);
current = 0;
ctx = NULL;
if (path[0] != SEP)
aug_get(aug, AUGEAS_CONTEXT, (const char **) &ctx);
free(path);
}
if (end == NULL)
end = (char *) text;
while (current < nchildren) {
char *child = children[current];
current += 1;
char *chend = strrchr(child, SEP) + 1;
if (STREQLEN(chend, end, strlen(end))) {
if (child_count(child) > 0) {
char *c = realloc(child, strlen(child)+2);
if (c == NULL) {
free(child);
return NULL;
}
child = c;
strcat(child, "/");
}
/* strip off context if the user didn't give it */
if (ctx != NULL) {
int ctxidx = strlen(ctx);
if (child[ctxidx] == SEP)
ctxidx++;
char *c = strdup(&child[ctxidx]);
free(child);
if (c == NULL)
return NULL;
child = c;
}
rl_filename_completion_desired = 1;
rl_completion_append_character = '\0';
return child;
} else {
free(child);
}
}
return NULL;
}
static char *readline_command_generator(const char *text, int state) {
// FIXME: expose somewhere under /augeas
static const char *const commands[] = {
"quit", "clear", "defnode", "defvar",
"get", "label", "ins", "load", "ls", "match",
"mv", "cp", "rename", "print", "dump-xml", "rm", "save", "set", "setm",
"clearm", "span", "store", "retrieve", "transform", "load-file",
"help", "touch", "insert", "move", "copy", "errors", "source", "context",
"info", "count",
NULL };
static int current = 0;
const char *name;
if (state == 0)
current = 0;
rl_completion_append_character = ' ';
while ((name = commands[current]) != NULL) {
current += 1;
if (STREQLEN(text, name, strlen(text)))
return strdup(name);
}
return NULL;
}
#ifndef HAVE_RL_COMPLETION_MATCHES
typedef char *rl_compentry_func_t(const char *, int);
static char **rl_completion_matches(ATTRIBUTE_UNUSED const char *text,
ATTRIBUTE_UNUSED rl_compentry_func_t *func) {
return NULL;
}
#endif
#ifndef HAVE_RL_CRLF
static int rl_crlf(void) {
if (rl_outstream != NULL)
putc('\n', rl_outstream);
return 0;
}
#endif
#ifndef HAVE_RL_REPLACE_LINE
static void rl_replace_line(ATTRIBUTE_UNUSED const char *text,
ATTRIBUTE_UNUSED int clear_undo) {
return;
}
#endif
static char **readline_completion(const char *text, int start,
ATTRIBUTE_UNUSED int end) {
if (start == 0)
return rl_completion_matches(text, readline_command_generator);
else
return rl_completion_matches(text, readline_path_generator);
return NULL;
}
static char *get_home_dir(uid_t uid) {
char *strbuf;
char *result;
struct passwd pwbuf;
struct passwd *pw = NULL;
long val = sysconf(_SC_GETPW_R_SIZE_MAX);
if (val < 0) {
// The libc won't tell us how big a buffer to reserve.
// Let's hope that 16k is enough (it really should be).
val = 16*1024;
}
size_t strbuflen = (size_t) val;
if (ALLOC_N(strbuf, strbuflen) < 0)
return NULL;
if (getpwuid_r(uid, &pwbuf, strbuf, strbuflen, &pw) != 0 || pw == NULL) {
free(strbuf);
// Try to get the user's home dir from the environment
char *env = getenv("HOME");
if (env != NULL) {
return strdup(env);
}
return NULL;
}
result = strdup(pw->pw_dir);
free(strbuf);
return result;
}
/* Inspired from:
* https://thoughtbot.com/blog/tab-completion-in-gnu-readline
*/
static int quote_detector(char *str, int index) {
return index > 0
&& str[index - 1] == '\\'
&& quote_detector(str, index - 1) == 0;
}
static void readline_init(void) {
rl_readline_name = "augtool";
rl_attempted_completion_function = readline_completion;
rl_completion_entry_function = readline_path_generator;
rl_completer_quote_characters = "\"'";
rl_completer_word_break_characters = (char *) " ";
rl_char_is_quoted_p = "e_detector;
/* Set up persistent history */
char *home_dir = get_home_dir(getuid());
char *history_dir = NULL;
if (home_dir == NULL)
goto done;
if (_xasprintf(&history_dir, "%s/.augeas", home_dir) < 0)
goto done;
if (mkdir(history_dir, 0755) < 0 && errno != EEXIST)
goto done;
if (_xasprintf(&history_file, "%s/history", history_dir) < 0)
goto done;
stifle_history(500);
read_history(history_file);
done:
free(home_dir);
free(history_dir);
}
__attribute__((noreturn))
static void help(void) {
fprintf(stderr, "Usage: %s [OPTIONS] [COMMAND]\n", progname);
fprintf(stderr, "Load the Augeas tree and modify it. If no COMMAND is given, run interactively\n");
fprintf(stderr, "Run '%s help' to get a list of possible commands.\n",
progname);
fprintf(stderr, "\nOptions:\n\n");
fprintf(stderr, " -c, --typecheck typecheck lenses\n");
fprintf(stderr, " -b, --backup preserve originals of modified files with\n"
" extension '.augsave'\n");
fprintf(stderr, " -n, --new save changes in files with extension '.augnew',\n"
" leave original unchanged\n");
fprintf(stderr, " -r, --root ROOT use ROOT as the root of the filesystem\n");
fprintf(stderr, " -I, --include DIR search DIR for modules; can be given multiple times\n");
fprintf(stderr, " -t, --transform XFM add a file transform; uses the 'transform' command\n"
" syntax, e.g. -t 'Fstab incl /etc/fstab.bak'\n");
fprintf(stderr, " -l, --load-file FILE load individual FILE in the tree\n");
fprintf(stderr, " -e, --echo echo commands when reading from a file\n");
fprintf(stderr, " -f, --file FILE read commands from FILE\n");
fprintf(stderr, " -s, --autosave automatically save at the end of instructions\n");
fprintf(stderr, " -i, --interactive run an interactive shell after evaluating\n"
" the commands in STDIN and FILE\n");
fprintf(stderr, " -S, --nostdinc do not search the builtin default directories\n"
" for modules\n");
fprintf(stderr, " -L, --noload do not load any files into the tree on startup\n");
fprintf(stderr, " -A, --noautoload do not autoload modules from the search path\n");
fprintf(stderr, " --span load span positions for nodes related to a file\n");
fprintf(stderr, " --timing after executing each command, show how long it took\n");
fprintf(stderr, " --version print version information and exit.\n");
exit(EXIT_FAILURE);
}
static void parse_opts(int argc, char **argv) {
int opt;
size_t loadpathlen = 0;
enum {
VAL_VERSION = CHAR_MAX + 1,
VAL_SPAN = VAL_VERSION + 1,
VAL_TIMING = VAL_SPAN + 1
};
struct option options[] = {
{ "help", 0, 0, 'h' },
{ "typecheck", 0, 0, 'c' },
{ "backup", 0, 0, 'b' },
{ "new", 0, 0, 'n' },
{ "root", 1, 0, 'r' },
{ "include", 1, 0, 'I' },
{ "transform", 1, 0, 't' },
{ "load-file", 1, 0, 'l' },
{ "echo", 0, 0, 'e' },
{ "file", 1, 0, 'f' },
{ "autosave", 0, 0, 's' },
{ "interactive", 0, 0, 'i' },
{ "nostdinc", 0, 0, 'S' },
{ "noload", 0, 0, 'L' },
{ "noautoload", 0, 0, 'A' },
{ "span", 0, 0, VAL_SPAN },
{ "timing", 0, 0, VAL_TIMING },
{ "version", 0, 0, VAL_VERSION },
{ 0, 0, 0, 0}
};
int idx;
while ((opt = getopt_long(argc, argv, "hnbcr:I:t:l:ef:siSLA", options, &idx)) != -1) {
switch(opt) {
case 'c':
flags |= AUG_TYPE_CHECK;
break;
case 'b':
flags |= AUG_SAVE_BACKUP;
break;
case 'n':
flags |= AUG_SAVE_NEWFILE;
break;
case 'h':
help();
break;
case 'r':
root = optarg;
break;
case 'I':
argz_add(&loadpath, &loadpathlen, optarg);
break;
case 't':
argz_add(&transforms, &transformslen, optarg);
break;
case 'l':
// --load-file implies --noload
flags |= AUG_NO_LOAD;
argz_add(&loadonly, &loadonlylen, optarg);
break;
case 'e':
echo_commands = 1;
break;
case 'f':
inputfile = optarg;
break;
case 's':
auto_save = true;
break;
case 'i':
interactive = true;
break;
case 'S':
flags |= AUG_NO_STDINC;
break;
case 'L':
flags |= AUG_NO_LOAD;
break;
case 'A':
flags |= AUG_NO_MODL_AUTOLOAD;
break;
case VAL_VERSION:
flags |= AUG_NO_MODL_AUTOLOAD;
print_version = true;
break;
case VAL_SPAN:
flags |= AUG_ENABLE_SPAN;
break;
case VAL_TIMING:
timing = true;
break;
default:
fprintf(stderr, "Try '%s --help' for more information.\n",
progname);
exit(EXIT_FAILURE);
break;
}
}
argz_stringify(loadpath, loadpathlen, PATH_SEP_CHAR);
}
static void print_version_info(void) {
const char *version;
int r;
r = aug_get(aug, "/augeas/version", &version);
if (r != 1)
goto error;
fprintf(stderr, "augtool %s <http://augeas.net/>\n", version);
fprintf(stderr, "Copyright (C) 2007-2016 David Lutterkort\n");
fprintf(stderr, "License LGPLv2+: GNU LGPL version 2.1 or later\n");
fprintf(stderr, " <http://www.gnu.org/licenses/lgpl-2.1.html>\n");
fprintf(stderr, "This is free software: you are free to change and redistribute it.\n");
fprintf(stderr, "There is NO WARRANTY, to the extent permitted by law.\n\n");
fprintf(stderr, "Written by David Lutterkort\n");
return;
error:
fprintf(stderr, "Something went terribly wrong internally - please file a bug\n");
}
static void print_time_taken(const struct timeval *start,
const struct timeval *stop) {
time_t elapsed = (stop->tv_sec - start->tv_sec)*1000
+ (stop->tv_usec - start->tv_usec)/1000;
printf("Time: %ld ms\n", elapsed);
}
static int run_command(const char *line, bool with_timing) {
int result;
struct timeval stop, start;
gettimeofday(&start, NULL);
result = aug_srun(aug, stdout, line);
gettimeofday(&stop, NULL);
if (with_timing && result >= 0) {
print_time_taken(&start, &stop);
}
if (isatty(fileno(stdin)))
add_history(line);
return result;
}
static void print_aug_error(void) {
if (aug_error(aug) == AUG_ENOMEM) {
fprintf(stderr, "Out of memory.\n");
return;
}
if (aug_error(aug) != AUG_NOERROR) {
fprintf(stderr, "error: %s\n", aug_error_message(aug));
if (aug_error_minor_message(aug) != NULL)
fprintf(stderr, "error: %s\n",
aug_error_minor_message(aug));
if (aug_error_details(aug) != NULL) {
fputs(aug_error_details(aug), stderr);
fprintf(stderr, "\n");
}
}
}
static void sigint_handler(ATTRIBUTE_UNUSED int signum) {
// Cancel the current line of input, along with undo info for that line.
rl_replace_line("", 1);
// Move the cursor to the next screen line, then force a re-display.
rl_crlf();
rl_forced_update_display();
}
static void install_signal_handlers(void) {
// On Ctrl-C, cancel the current line (rather than exit the program).
struct sigaction sigint_action;
MEMZERO(&sigint_action, 1);
sigint_action.sa_handler = sigint_handler;
sigemptyset(&sigint_action.sa_mask);
sigint_action.sa_flags = 0;
sigaction(SIGINT, &sigint_action, NULL);
}
static int main_loop(void) {
char *line = NULL;
int ret = 0;
char inputline [128];
int code;
bool end_reached = false;
bool get_line = true;
bool in_interactive = false;
if (inputfile) {
if (freopen(inputfile, "r", stdin) == NULL) {
char *msg = NULL;
if (asprintf(&msg, "Failed to open %s", inputfile) < 0)
perror("Failed to open input file");
else
perror(msg);
return -1;
}
}
install_signal_handlers();
// make readline silent by default
echo_commands = echo_commands || isatty(fileno(stdin));
if (echo_commands)
rl_outstream = NULL;
else
rl_outstream = fopen("/dev/null", "w");
while(1) {
if (get_line) {
line = readline(AUGTOOL_PROMPT);
} else {
line = NULL;
}
if (line == NULL) {
if (!isatty(fileno(stdin)) && interactive && !in_interactive) {
in_interactive = true;
if (echo_commands)
printf("\n");
echo_commands = true;
// reopen in stream
if (freopen("/dev/tty", "r", stdin) == NULL) {
perror("Failed to open terminal for reading");
return -1;
}
rl_instream = stdin;
// reopen stdout and stream to a tty if originally silenced or
// not connected to a tty, for full interactive mode
if (rl_outstream == NULL || !isatty(fileno(rl_outstream))) {
if (rl_outstream != NULL) {
fclose(rl_outstream);
rl_outstream = NULL;
}
if (freopen("/dev/tty", "w", stdout) == NULL) {
perror("Failed to reopen stdout");
return -1;
}
rl_outstream = stdout;
}
continue;
}
if (auto_save) {
strncpy(inputline, "save", sizeof(inputline));
line = inputline;
if (echo_commands)
printf("%s\n", line);
auto_save = false;
} else {
end_reached = true;
}
get_line = false;
}
if (end_reached) {
if (echo_commands)
printf("\n");
return ret;
}
if (*line == '\0' || *line == '#') {
free(line);
continue;
}
code = run_command(line, timing);
if (code == -2) {
free(line);
return ret;
}
if (code < 0) {
ret = -1;
print_aug_error();
}
if (line != inputline)
free(line);
}
}
static int run_args(int argc, char **argv) {
size_t len = 0;
char *line = NULL;
int code;
for (int i=0; i < argc; i++)
len += strlen(argv[i]) + 1;
if (ALLOC_N(line, len + 1) < 0)
return -1;
for (int i=0; i < argc; i++) {
strcat(line, argv[i]);
strcat(line, " ");
}
if (echo_commands)
printf("%s%s\n", AUGTOOL_PROMPT, line);
code = run_command(line, timing);
free(line);
if (code >= 0 && auto_save)
if (echo_commands)
printf("%ssave\n", AUGTOOL_PROMPT);
code = run_command("save", false);
if (code < 0) {
code = -1;
print_aug_error();
}
return (code >= 0 || code == -2) ? 0 : -1;
}
static void add_transforms(char *ts, size_t tslen) {
char *command;
int r;
char *t = NULL;
bool added_transform = false;
while ((t = argz_next(ts, tslen, t))) {
r = _xasprintf(&command, "transform %s", t);
if (r < 0)
fprintf(stderr, "error: Failed to add transform %s: could not allocate memory\n", t);
r = aug_srun(aug, stdout, command);
if (r < 0)
fprintf(stderr, "error: Failed to add transform %s: %s\n", t, aug_error_message(aug));
free(command);
added_transform = true;
}
if (added_transform) {
r = aug_load(aug);
if (r < 0)
fprintf(stderr, "error: Failed to load with new transforms: %s\n", aug_error_message(aug));
}
}
static void load_files(char *ts, size_t tslen) {
char *command;
int r;
char *t = NULL;
while ((t = argz_next(ts, tslen, t))) {
r = _xasprintf(&command, "load-file %s", t);
if (r < 0)
fprintf(stderr, "error: Failed to load file %s: could not allocate memory\n", t);
r = aug_srun(aug, stdout, command);
if (r < 0)
fprintf(stderr, "error: Failed to load file %s: %s\n", t, aug_error_message(aug));
free(command);
}
}
int main(int argc, char **argv) {
int r;
struct timeval start, stop;
setlocale(LC_ALL, "");
parse_opts(argc, argv);
if (timing) {
printf("Initializing augeas ... ");
fflush(stdout);
}
gettimeofday(&start, NULL);
aug = aug_init(root, loadpath, flags|AUG_NO_ERR_CLOSE);
gettimeofday(&stop, NULL);
if (timing) {
printf("done\n");
print_time_taken(&start, &stop);
}
if (aug == NULL || aug_error(aug) != AUG_NOERROR) {
fprintf(stderr, "Failed to initialize Augeas\n");
if (aug != NULL)
print_aug_error();
exit(EXIT_FAILURE);
}
load_files(loadonly, loadonlylen);
add_transforms(transforms, transformslen);
if (print_version) {
print_version_info();
return EXIT_SUCCESS;
}
readline_init();
if (optind < argc) {
// Accept one command from the command line
r = run_args(argc - optind, argv+optind);
} else {
r = main_loop();
}
if (history_file != NULL)
write_history(history_file);
aug_close(aug);
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
/*
* Local variables:
* indent-tabs-mode: nil
* c-indent-level: 4
* c-basic-offset: 4
* tab-width: 4
* End:
*/