4 * Copyright (C) 2007-2016 David Lutterkort
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Author: David Lutterkort <dlutter@redhat.com>
26 #include "safe-alloc.h"
28 #include <readline/readline.h>
29 #include <readline/history.h>
36 #include <sys/types.h>
42 /* Global variables */
44 static augeas *aug = NULL;
45 static const char *const progname = "augtool";
46 static unsigned int flags = AUG_NONE;
47 const char *root = NULL;
48 char *loadpath = NULL;
49 char *transforms = NULL;
50 char *loadonly = NULL;
51 size_t transformslen = 0;
52 size_t loadonlylen = 0;
53 const char *inputfile = NULL;
54 int echo_commands = 0; /* Gets also changed in main_loop */
55 bool print_version = false;
56 bool auto_save = false;
57 bool interactive = false;
59 /* History file is ~/.augeas/history */
60 char *history_file = NULL;
62 #define AUGTOOL_PROMPT "augtool> "
68 /* Private copy of xasprintf from internal to avoid Multiple definition in
71 static int _xasprintf(char **strp, const char *format, ...) {
75 va_start (args, format);
76 result = vasprintf (strp, format, args);
83 static int child_count(const char *path) {
87 if (path[strlen(path)-1] == SEP)
88 r = asprintf(&pat, "%s*", path);
90 r = asprintf(&pat, "%s/*", path);
93 r = aug_match(aug, pat, NULL);
99 static char *readline_path_generator(const char *text, int state) {
100 static int current = 0;
101 static char **children = NULL;
102 static int nchildren = 0;
103 static char *ctx = NULL;
105 char *end = strrchr(text, SEP);
112 if ((path = strdup("*")) == NULL)
115 if (ALLOC_N(path, end - text + 2) < 0)
117 strncpy(path, text, end - text);
121 for (;current < nchildren; current++)
122 free((void *) children[current]);
123 free((void *) children);
124 nchildren = aug_match(aug, path, &children);
129 aug_get(aug, AUGEAS_CONTEXT, (const char **) &ctx);
137 while (current < nchildren) {
138 char *child = children[current];
141 char *chend = strrchr(child, SEP) + 1;
142 if (STREQLEN(chend, end, strlen(end))) {
143 if (child_count(child) > 0) {
144 char *c = realloc(child, strlen(child)+2);
153 /* strip off context if the user didn't give it */
155 int ctxidx = strlen(ctx);
156 if (child[ctxidx] == SEP)
158 char *c = strdup(&child[ctxidx]);
165 rl_filename_completion_desired = 1;
166 rl_completion_append_character = '\0';
175 static char *readline_command_generator(const char *text, int state) {
176 // FIXME: expose somewhere under /augeas
177 static const char *const commands[] = {
178 "quit", "clear", "defnode", "defvar",
179 "get", "label", "ins", "load", "ls", "match",
180 "mv", "cp", "rename", "print", "dump-xml", "rm", "save", "set", "setm",
181 "clearm", "span", "store", "retrieve", "transform", "load-file",
182 "help", "touch", "insert", "move", "copy", "errors", "source", "context",
186 static int current = 0;
192 rl_completion_append_character = ' ';
193 while ((name = commands[current]) != NULL) {
195 if (STREQLEN(text, name, strlen(text)))
201 #ifndef HAVE_RL_COMPLETION_MATCHES
202 typedef char *rl_compentry_func_t(const char *, int);
203 static char **rl_completion_matches(ATTRIBUTE_UNUSED const char *text,
204 ATTRIBUTE_UNUSED rl_compentry_func_t *func) {
210 static int rl_crlf(void) {
211 if (rl_outstream != NULL)
212 putc('\n', rl_outstream);
217 #ifndef HAVE_RL_REPLACE_LINE
218 static void rl_replace_line(ATTRIBUTE_UNUSED const char *text,
219 ATTRIBUTE_UNUSED int clear_undo) {
224 static char **readline_completion(const char *text, int start,
225 ATTRIBUTE_UNUSED int end) {
227 return rl_completion_matches(text, readline_command_generator);
229 return rl_completion_matches(text, readline_path_generator);
234 static char *get_home_dir(uid_t uid) {
238 struct passwd *pw = NULL;
239 long val = sysconf(_SC_GETPW_R_SIZE_MAX);
242 // The libc won't tell us how big a buffer to reserve.
243 // Let's hope that 16k is enough (it really should be).
247 size_t strbuflen = (size_t) val;
249 if (ALLOC_N(strbuf, strbuflen) < 0)
252 if (getpwuid_r(uid, &pwbuf, strbuf, strbuflen, &pw) != 0 || pw == NULL) {
255 // Try to get the user's home dir from the environment
256 char *env = getenv("HOME");
263 result = strdup(pw->pw_dir);
270 static void readline_init(void) {
271 rl_readline_name = "augtool";
272 rl_attempted_completion_function = readline_completion;
273 rl_completion_entry_function = readline_path_generator;
275 /* Set up persistent history */
276 char *home_dir = get_home_dir(getuid());
277 char *history_dir = NULL;
279 if (home_dir == NULL)
282 if (_xasprintf(&history_dir, "%s/.augeas", home_dir) < 0)
285 if (mkdir(history_dir, 0755) < 0 && errno != EEXIST)
288 if (_xasprintf(&history_file, "%s/history", history_dir) < 0)
293 read_history(history_file);
300 __attribute__((noreturn))
301 static void help(void) {
302 fprintf(stderr, "Usage: %s [OPTIONS] [COMMAND]\n", progname);
303 fprintf(stderr, "Load the Augeas tree and modify it. If no COMMAND is given, run interactively\n");
304 fprintf(stderr, "Run '%s help' to get a list of possible commands.\n",
306 fprintf(stderr, "\nOptions:\n\n");
307 fprintf(stderr, " -c, --typecheck typecheck lenses\n");
308 fprintf(stderr, " -b, --backup preserve originals of modified files with\n"
309 " extension '.augsave'\n");
310 fprintf(stderr, " -n, --new save changes in files with extension '.augnew',\n"
311 " leave original unchanged\n");
312 fprintf(stderr, " -r, --root ROOT use ROOT as the root of the filesystem\n");
313 fprintf(stderr, " -I, --include DIR search DIR for modules; can be given multiple times\n");
314 fprintf(stderr, " -t, --transform XFM add a file transform; uses the 'transform' command\n"
315 " syntax, e.g. -t 'Fstab incl /etc/fstab.bak'\n");
316 fprintf(stderr, " -l, --load-file FILE load individual FILE in the tree\n");
317 fprintf(stderr, " -e, --echo echo commands when reading from a file\n");
318 fprintf(stderr, " -f, --file FILE read commands from FILE\n");
319 fprintf(stderr, " -s, --autosave automatically save at the end of instructions\n");
320 fprintf(stderr, " -i, --interactive run an interactive shell after evaluating\n"
321 " the commands in STDIN and FILE\n");
322 fprintf(stderr, " -S, --nostdinc do not search the builtin default directories\n"
324 fprintf(stderr, " -L, --noload do not load any files into the tree on startup\n");
325 fprintf(stderr, " -A, --noautoload do not autoload modules from the search path\n");
326 fprintf(stderr, " --span load span positions for nodes related to a file\n");
327 fprintf(stderr, " --timing after executing each command, show how long it took\n");
328 fprintf(stderr, " --version print version information and exit.\n");
333 static void parse_opts(int argc, char **argv) {
335 size_t loadpathlen = 0;
337 VAL_VERSION = CHAR_MAX + 1,
338 VAL_SPAN = VAL_VERSION + 1,
339 VAL_TIMING = VAL_SPAN + 1
341 struct option options[] = {
342 { "help", 0, 0, 'h' },
343 { "typecheck", 0, 0, 'c' },
344 { "backup", 0, 0, 'b' },
345 { "new", 0, 0, 'n' },
346 { "root", 1, 0, 'r' },
347 { "include", 1, 0, 'I' },
348 { "transform", 1, 0, 't' },
349 { "load-file", 1, 0, 'l' },
350 { "echo", 0, 0, 'e' },
351 { "file", 1, 0, 'f' },
352 { "autosave", 0, 0, 's' },
353 { "interactive", 0, 0, 'i' },
354 { "nostdinc", 0, 0, 'S' },
355 { "noload", 0, 0, 'L' },
356 { "noautoload", 0, 0, 'A' },
357 { "span", 0, 0, VAL_SPAN },
358 { "timing", 0, 0, VAL_TIMING },
359 { "version", 0, 0, VAL_VERSION },
364 while ((opt = getopt_long(argc, argv, "hnbcr:I:t:l:ef:siSLA", options, &idx)) != -1) {
367 flags |= AUG_TYPE_CHECK;
370 flags |= AUG_SAVE_BACKUP;
373 flags |= AUG_SAVE_NEWFILE;
382 argz_add(&loadpath, &loadpathlen, optarg);
385 argz_add(&transforms, &transformslen, optarg);
388 // --load-file implies --noload
389 flags |= AUG_NO_LOAD;
390 argz_add(&loadonly, &loadonlylen, optarg);
405 flags |= AUG_NO_STDINC;
408 flags |= AUG_NO_LOAD;
411 flags |= AUG_NO_MODL_AUTOLOAD;
414 flags |= AUG_NO_MODL_AUTOLOAD;
415 print_version = true;
418 flags |= AUG_ENABLE_SPAN;
424 fprintf(stderr, "Try '%s --help' for more information.\n",
430 argz_stringify(loadpath, loadpathlen, PATH_SEP_CHAR);
433 static void print_version_info(void) {
437 r = aug_get(aug, "/augeas/version", &version);
441 fprintf(stderr, "augtool %s <http://augeas.net/>\n", version);
442 fprintf(stderr, "Copyright (C) 2007-2016 David Lutterkort\n");
443 fprintf(stderr, "License LGPLv2+: GNU LGPL version 2.1 or later\n");
444 fprintf(stderr, " <http://www.gnu.org/licenses/lgpl-2.1.html>\n");
445 fprintf(stderr, "This is free software: you are free to change and redistribute it.\n");
446 fprintf(stderr, "There is NO WARRANTY, to the extent permitted by law.\n\n");
447 fprintf(stderr, "Written by David Lutterkort\n");
450 fprintf(stderr, "Something went terribly wrong internally - please file a bug\n");
453 static void print_time_taken(const struct timeval *start,
454 const struct timeval *stop) {
455 time_t elapsed = (stop->tv_sec - start->tv_sec)*1000
456 + (stop->tv_usec - start->tv_usec)/1000;
457 printf("Time: %ld ms\n", elapsed);
460 static int run_command(const char *line, bool with_timing) {
462 struct timeval stop, start;
464 gettimeofday(&start, NULL);
465 result = aug_srun(aug, stdout, line);
466 gettimeofday(&stop, NULL);
467 if (with_timing && result >= 0) {
468 print_time_taken(&start, &stop);
471 if (isatty(fileno(stdin)))
476 static void print_aug_error(void) {
477 if (aug_error(aug) == AUG_ENOMEM) {
478 fprintf(stderr, "Out of memory.\n");
481 if (aug_error(aug) != AUG_NOERROR) {
482 fprintf(stderr, "error: %s\n", aug_error_message(aug));
483 if (aug_error_minor_message(aug) != NULL)
484 fprintf(stderr, "error: %s\n",
485 aug_error_minor_message(aug));
486 if (aug_error_details(aug) != NULL) {
487 fputs(aug_error_details(aug), stderr);
488 fprintf(stderr, "\n");
493 static void sigint_handler(ATTRIBUTE_UNUSED int signum) {
494 // Cancel the current line of input, along with undo info for that line.
495 rl_replace_line("", 1);
497 // Move the cursor to the next screen line, then force a re-display.
499 rl_forced_update_display();
502 static void install_signal_handlers(void) {
503 // On Ctrl-C, cancel the current line (rather than exit the program).
504 struct sigaction sigint_action;
505 MEMZERO(&sigint_action, 1);
506 sigint_action.sa_handler = sigint_handler;
507 sigemptyset(&sigint_action.sa_mask);
508 sigint_action.sa_flags = 0;
509 sigaction(SIGINT, &sigint_action, NULL);
512 static int main_loop(void) {
515 char inputline [128];
517 bool end_reached = false;
518 bool get_line = true;
519 bool in_interactive = false;
522 if (freopen(inputfile, "r", stdin) == NULL) {
524 if (asprintf(&msg, "Failed to open %s", inputfile) < 0)
525 perror("Failed to open input file");
532 install_signal_handlers();
534 // make readline silent by default
535 echo_commands = echo_commands || isatty(fileno(stdin));
539 rl_outstream = fopen("/dev/null", "w");
543 line = readline(AUGTOOL_PROMPT);
549 if (!isatty(fileno(stdin)) && interactive && !in_interactive) {
550 in_interactive = true;
553 echo_commands = true;
556 if (freopen("/dev/tty", "r", stdin) == NULL) {
557 perror("Failed to open terminal for reading");
562 // reopen stdout and stream to a tty if originally silenced or
563 // not connected to a tty, for full interactive mode
564 if (rl_outstream == NULL || !isatty(fileno(rl_outstream))) {
565 if (rl_outstream != NULL) {
566 fclose(rl_outstream);
569 if (freopen("/dev/tty", "w", stdout) == NULL) {
570 perror("Failed to reopen stdout");
573 rl_outstream = stdout;
579 strncpy(inputline, "save", sizeof(inputline));
582 printf("%s\n", line);
596 if (*line == '\0' || *line == '#') {
601 code = run_command(line, timing);
612 if (line != inputline)
617 static int run_args(int argc, char **argv) {
622 for (int i=0; i < argc; i++)
623 len += strlen(argv[i]) + 1;
624 if (ALLOC_N(line, len + 1) < 0)
626 for (int i=0; i < argc; i++) {
627 strcat(line, argv[i]);
631 printf("%s%s\n", AUGTOOL_PROMPT, line);
632 code = run_command(line, timing);
634 if (code >= 0 && auto_save)
636 printf("%ssave\n", AUGTOOL_PROMPT);
637 code = run_command("save", false);
643 return (code >= 0 || code == -2) ? 0 : -1;
646 static void add_transforms(char *ts, size_t tslen) {
650 bool added_transform = false;
652 while ((t = argz_next(ts, tslen, t))) {
653 r = _xasprintf(&command, "transform %s", t);
655 fprintf(stderr, "error: Failed to add transform %s: could not allocate memory\n", t);
657 r = aug_srun(aug, stdout, command);
659 fprintf(stderr, "error: Failed to add transform %s: %s\n", t, aug_error_message(aug));
662 added_transform = true;
665 if (added_transform) {
668 fprintf(stderr, "error: Failed to load with new transforms: %s\n", aug_error_message(aug));
672 static void load_files(char *ts, size_t tslen) {
677 while ((t = argz_next(ts, tslen, t))) {
678 r = _xasprintf(&command, "load-file %s", t);
680 fprintf(stderr, "error: Failed to load file %s: could not allocate memory\n", t);
682 r = aug_srun(aug, stdout, command);
684 fprintf(stderr, "error: Failed to load file %s: %s\n", t, aug_error_message(aug));
690 int main(int argc, char **argv) {
692 struct timeval start, stop;
694 setlocale(LC_ALL, "");
696 parse_opts(argc, argv);
699 printf("Initializing augeas ... ");
702 gettimeofday(&start, NULL);
704 aug = aug_init(root, loadpath, flags|AUG_NO_ERR_CLOSE);
706 gettimeofday(&stop, NULL);
709 print_time_taken(&start, &stop);
712 if (aug == NULL || aug_error(aug) != AUG_NOERROR) {
713 fprintf(stderr, "Failed to initialize Augeas\n");
718 load_files(loadonly, loadonlylen);
719 add_transforms(transforms, transformslen);
721 print_version_info();
726 // Accept one command from the command line
727 r = run_args(argc - optind, argv+optind);
731 if (history_file != NULL)
732 write_history(history_file);
735 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
741 * indent-tabs-mode: nil