Imported Upstream version 1.5.0
[platform/upstream/augeas.git] / src / augtool.c
1 /*
2  * augtool.c:
3  *
4  * Copyright (C) 2007-2015 David Lutterkort
5  *
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.
10  *
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.
15  *
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
19  *
20  * Author: David Lutterkort <dlutter@redhat.com>
21  */
22
23 #include <config.h>
24 #include "augeas.h"
25 #include "internal.h"
26 #include "safe-alloc.h"
27
28 #include <readline/readline.h>
29 #include <readline/history.h>
30 #include <argz.h>
31 #include <getopt.h>
32 #include <limits.h>
33 #include <ctype.h>
34 #include <locale.h>
35 #include <signal.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <pwd.h>
39 #include <stdarg.h>
40 #include <sys/time.h>
41
42 /* Global variables */
43
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 size_t transformslen = 0;
51 const char *inputfile = NULL;
52 int echo_commands = 0;         /* Gets also changed in main_loop */
53 bool print_version = false;
54 bool auto_save = false;
55 bool interactive = false;
56 bool timing = false;
57 /* History file is ~/.augeas/history */
58 char *history_file = NULL;
59
60 #define AUGTOOL_PROMPT "augtool> "
61
62 /*
63  * General utilities
64  */
65
66 /* Not static, since prototype is in internal.h */
67 int xasprintf(char **strp, const char *format, ...) {
68   va_list args;
69   int result;
70
71   va_start (args, format);
72   result = vasprintf (strp, format, args);
73   va_end (args);
74   if (result < 0)
75       *strp = NULL;
76   return result;
77 }
78
79 static int child_count(const char *path) {
80     char *pat = NULL;
81     int r;
82
83     if (path[strlen(path)-1] == SEP)
84         r = asprintf(&pat, "%s*", path);
85     else
86         r = asprintf(&pat, "%s/*", path);
87     if (r < 0)
88         return -1;
89     r = aug_match(aug, pat, NULL);
90     free(pat);
91
92     return r;
93 }
94
95 static char *readline_path_generator(const char *text, int state) {
96     static int current = 0;
97     static char **children = NULL;
98     static int nchildren = 0;
99     static char *ctx = NULL;
100
101     char *end = strrchr(text, SEP);
102     if (end != NULL)
103         end += 1;
104
105     if (state == 0) {
106         char *path;
107         if (end == NULL) {
108             if ((path = strdup("*")) == NULL)
109                 return NULL;
110         } else {
111             CALLOC(path, end - text + 2);
112             if (path == NULL)
113                 return NULL;
114             strncpy(path, text, end - text);
115             strcat(path, "*");
116         }
117
118         for (;current < nchildren; current++)
119             free((void *) children[current]);
120         free((void *) children);
121         nchildren = aug_match(aug, path, &children);
122         current = 0;
123
124         ctx = NULL;
125         if (path[0] != SEP)
126             aug_get(aug, AUGEAS_CONTEXT, (const char **) &ctx);
127
128         free(path);
129     }
130
131     if (end == NULL)
132         end = (char *) text;
133
134     while (current < nchildren) {
135         char *child = children[current];
136         current += 1;
137
138         char *chend = strrchr(child, SEP) + 1;
139         if (STREQLEN(chend, end, strlen(end))) {
140             if (child_count(child) > 0) {
141                 char *c = realloc(child, strlen(child)+2);
142                 if (c == NULL) {
143                     free(child);
144                     return NULL;
145                 }
146                 child = c;
147                 strcat(child, "/");
148             }
149
150             /* strip off context if the user didn't give it */
151             if (ctx != NULL) {
152                 char *c = realloc(child, strlen(child)-strlen(ctx)+1);
153                 if (c == NULL) {
154                     free(child);
155                     return NULL;
156                 }
157                 int ctxidx = strlen(ctx);
158                 if (child[ctxidx] == SEP)
159                     ctxidx++;
160                 strcpy(c, &child[ctxidx]);
161                 child = c;
162             }
163
164             rl_filename_completion_desired = 1;
165             rl_completion_append_character = '\0';
166             return child;
167         } else {
168             free(child);
169         }
170     }
171     return NULL;
172 }
173
174 static char *readline_command_generator(const char *text, int state) {
175     // FIXME: expose somewhere under /augeas
176     static const char *const commands[] = {
177         "quit", "clear", "defnode", "defvar",
178         "get", "label", "ins", "load", "ls", "match",
179         "mv", "cp", "rename", "print", "dump-xml", "rm", "save", "set", "setm",
180         "clearm", "span", "store", "retrieve", "transform",
181         "help", "touch", "insert", "move", "copy", "errors", NULL };
182
183     static int current = 0;
184     const char *name;
185
186     if (state == 0)
187         current = 0;
188
189     rl_completion_append_character = ' ';
190     while ((name = commands[current]) != NULL) {
191         current += 1;
192         if (STREQLEN(text, name, strlen(text)))
193             return strdup(name);
194     }
195     return NULL;
196 }
197
198 #ifndef HAVE_RL_COMPLETION_MATCHES
199 typedef char *rl_compentry_func_t(const char *, int);
200 static char **rl_completion_matches(ATTRIBUTE_UNUSED const char *text,
201                            ATTRIBUTE_UNUSED rl_compentry_func_t *func) {
202     return NULL;
203 }
204 #endif
205
206 #ifndef HAVE_RL_CRLF
207 static int rl_crlf(void) {
208     if (rl_outstream != NULL)
209         putc('\n', rl_outstream);
210     return 0;
211 }
212 #endif
213
214 #ifndef HAVE_RL_REPLACE_LINE
215 static void rl_replace_line(ATTRIBUTE_UNUSED const char *text,
216                               ATTRIBUTE_UNUSED int clear_undo) {
217     return;
218 }
219 #endif
220
221 static char **readline_completion(const char *text, int start,
222                                   ATTRIBUTE_UNUSED int end) {
223     if (start == 0)
224         return rl_completion_matches(text, readline_command_generator);
225     else
226         return rl_completion_matches(text, readline_path_generator);
227
228     return NULL;
229 }
230
231 static char *get_home_dir(uid_t uid) {
232     char *strbuf;
233     char *result;
234     struct passwd pwbuf;
235     struct passwd *pw = NULL;
236     long val = sysconf(_SC_GETPW_R_SIZE_MAX);
237     size_t strbuflen = val;
238
239     if (val < 0)
240         return NULL;
241
242     if (ALLOC_N(strbuf, strbuflen) < 0)
243         return NULL;
244
245     if (getpwuid_r(uid, &pwbuf, strbuf, strbuflen, &pw) != 0 || pw == NULL) {
246         free(strbuf);
247         return NULL;
248     }
249
250     result = strdup(pw->pw_dir);
251
252     free(strbuf);
253
254     return result;
255 }
256
257 static void readline_init(void) {
258     rl_readline_name = "augtool";
259     rl_attempted_completion_function = readline_completion;
260     rl_completion_entry_function = readline_path_generator;
261
262     /* Set up persistent history */
263     char *home_dir = get_home_dir(getuid());
264     char *history_dir = NULL;
265
266     if (home_dir == NULL)
267         goto done;
268
269     if (xasprintf(&history_dir, "%s/.augeas", home_dir) < 0)
270         goto done;
271
272     if (mkdir(history_dir, 0755) < 0 && errno != EEXIST)
273         goto done;
274
275     if (xasprintf(&history_file, "%s/history", history_dir) < 0)
276         goto done;
277
278     stifle_history(500);
279
280     read_history(history_file);
281
282  done:
283     free(home_dir);
284     free(history_dir);
285 }
286
287 __attribute__((noreturn))
288 static void help(void) {
289     fprintf(stderr, "Usage: %s [OPTIONS] [COMMAND]\n", progname);
290     fprintf(stderr, "Load the Augeas tree and modify it. If no COMMAND is given, run interactively\n");
291     fprintf(stderr, "Run '%s help' to get a list of possible commands.\n",
292             progname);
293     fprintf(stderr, "\nOptions:\n\n");
294     fprintf(stderr, "  -c, --typecheck      typecheck lenses\n");
295     fprintf(stderr, "  -b, --backup         preserve originals of modified files with\n"
296                     "                       extension '.augsave'\n");
297     fprintf(stderr, "  -n, --new            save changes in files with extension '.augnew',\n"
298                     "                       leave original unchanged\n");
299     fprintf(stderr, "  -r, --root ROOT      use ROOT as the root of the filesystem\n");
300     fprintf(stderr, "  -I, --include DIR    search DIR for modules; can be given multiple times\n");
301     fprintf(stderr, "  -t, --transform XFM  add a file transform; uses the 'transform' command\n"
302                     "                       syntax, e.g. -t 'Fstab incl /etc/fstab.bak'\n");
303     fprintf(stderr, "  -e, --echo           echo commands when reading from a file\n");
304     fprintf(stderr, "  -f, --file FILE      read commands from FILE\n");
305     fprintf(stderr, "  -s, --autosave       automatically save at the end of instructions\n");
306     fprintf(stderr, "  -i, --interactive    run an interactive shell after evaluating\n"
307                     "                       the commands in STDIN and FILE\n");
308     fprintf(stderr, "  -S, --nostdinc       do not search the builtin default directories\n"
309                     "                       for modules\n");
310     fprintf(stderr, "  -L, --noload         do not load any files into the tree on startup\n");
311     fprintf(stderr, "  -A, --noautoload     do not autoload modules from the search path\n");
312     fprintf(stderr, "  --span               load span positions for nodes related to a file\n");
313     fprintf(stderr, "  --timing             after executing each command, show how long it took\n");
314     fprintf(stderr, "  --version            print version information and exit.\n");
315
316     exit(EXIT_FAILURE);
317 }
318
319 static void parse_opts(int argc, char **argv) {
320     int opt;
321     size_t loadpathlen = 0;
322     enum {
323         VAL_VERSION = CHAR_MAX + 1,
324         VAL_SPAN = VAL_VERSION + 1,
325         VAL_TIMING = VAL_SPAN + 1
326     };
327     struct option options[] = {
328         { "help",        0, 0, 'h' },
329         { "typecheck",   0, 0, 'c' },
330         { "backup",      0, 0, 'b' },
331         { "new",         0, 0, 'n' },
332         { "root",        1, 0, 'r' },
333         { "include",     1, 0, 'I' },
334         { "transform",   1, 0, 't' },
335         { "echo",        0, 0, 'e' },
336         { "file",        1, 0, 'f' },
337         { "autosave",    0, 0, 's' },
338         { "interactive", 0, 0, 'i' },
339         { "nostdinc",    0, 0, 'S' },
340         { "noload",      0, 0, 'L' },
341         { "noautoload",  0, 0, 'A' },
342         { "span",        0, 0, VAL_SPAN },
343         { "timing",      0, 0, VAL_TIMING },
344         { "version",     0, 0, VAL_VERSION },
345         { 0, 0, 0, 0}
346     };
347     int idx;
348
349     while ((opt = getopt_long(argc, argv, "hnbcr:I:t:ef:siSLA", options, &idx)) != -1) {
350         switch(opt) {
351         case 'c':
352             flags |= AUG_TYPE_CHECK;
353             break;
354         case 'b':
355             flags |= AUG_SAVE_BACKUP;
356             break;
357         case 'n':
358             flags |= AUG_SAVE_NEWFILE;
359             break;
360         case 'h':
361             help();
362             break;
363         case 'r':
364             root = optarg;
365             break;
366         case 'I':
367             argz_add(&loadpath, &loadpathlen, optarg);
368             break;
369         case 't':
370             argz_add(&transforms, &transformslen, optarg);
371             break;
372         case 'e':
373             echo_commands = 1;
374             break;
375         case 'f':
376             inputfile = optarg;
377             break;
378         case 's':
379             auto_save = true;
380             break;
381         case 'i':
382             interactive = true;
383             break;
384         case 'S':
385             flags |= AUG_NO_STDINC;
386             break;
387         case 'L':
388             flags |= AUG_NO_LOAD;
389             break;
390         case 'A':
391             flags |= AUG_NO_MODL_AUTOLOAD;
392             break;
393         case VAL_VERSION:
394             flags |= AUG_NO_MODL_AUTOLOAD;
395             print_version = true;
396             break;
397         case VAL_SPAN:
398             flags |= AUG_ENABLE_SPAN;
399             break;
400         case VAL_TIMING:
401             timing = true;
402             break;
403         default:
404             fprintf(stderr, "Try '%s --help' for more information.\n",
405                     progname);
406             exit(EXIT_FAILURE);
407             break;
408         }
409     }
410     argz_stringify(loadpath, loadpathlen, PATH_SEP_CHAR);
411 }
412
413 static void print_version_info(void) {
414     const char *version;
415     int r;
416
417     r = aug_get(aug, "/augeas/version", &version);
418     if (r != 1)
419         goto error;
420
421     fprintf(stderr, "augtool %s <http://augeas.net/>\n", version);
422     fprintf(stderr, "Copyright (C) 2007-2015 David Lutterkort\n");
423     fprintf(stderr, "License LGPLv2+: GNU LGPL version 2.1 or later\n");
424     fprintf(stderr, "                 <http://www.gnu.org/licenses/lgpl-2.1.html>\n");
425     fprintf(stderr, "This is free software: you are free to change and redistribute it.\n");
426     fprintf(stderr, "There is NO WARRANTY, to the extent permitted by law.\n\n");
427     fprintf(stderr, "Written by David Lutterkort\n");
428     return;
429  error:
430     fprintf(stderr, "Something went terribly wrong internally - please file a bug\n");
431 }
432
433 static void print_time_taken(const struct timeval *start,
434                              const struct timeval *stop) {
435     time_t elapsed = (stop->tv_sec - start->tv_sec)*1000
436                    + (stop->tv_usec - start->tv_usec)/1000;
437     printf("Time: %ld ms\n", elapsed);
438 }
439
440 static int run_command(const char *line, bool with_timing) {
441     int result;
442     struct timeval stop, start;
443
444     gettimeofday(&start, NULL);
445     result = aug_srun(aug, stdout, line);
446     gettimeofday(&stop, NULL);
447     if (with_timing && result >= 0) {
448         print_time_taken(&start, &stop);
449     }
450
451     if (isatty(fileno(stdin)))
452         add_history(line);
453     return result;
454 }
455
456 static void print_aug_error(void) {
457     if (aug_error(aug) == AUG_ENOMEM) {
458         fprintf(stderr, "Out of memory.\n");
459         return;
460     }
461     if (aug_error(aug) != AUG_NOERROR) {
462         fprintf(stderr, "error: %s\n", aug_error_message(aug));
463         if (aug_error_minor_message(aug) != NULL)
464             fprintf(stderr, "error: %s\n",
465                     aug_error_minor_message(aug));
466         if (aug_error_details(aug) != NULL) {
467             fputs(aug_error_details(aug), stderr);
468             fprintf(stderr, "\n");
469         }
470     }
471 }
472
473 static void sigint_handler(ATTRIBUTE_UNUSED int signum) {
474     // Cancel the current line of input, along with undo info for that line.
475     rl_replace_line("", 1);
476
477     // Move the cursor to the next screen line, then force a re-display.
478     rl_crlf();
479     rl_forced_update_display();
480 }
481
482 static void install_signal_handlers(void) {
483     // On Ctrl-C, cancel the current line (rather than exit the program).
484     struct sigaction sigint_action;
485     MEMZERO(&sigint_action, 1);
486     sigint_action.sa_handler = sigint_handler;
487     sigemptyset(&sigint_action.sa_mask);
488     sigint_action.sa_flags = 0;
489     sigaction(SIGINT, &sigint_action, NULL);
490 }
491
492 static int main_loop(void) {
493     char *line = NULL;
494     int ret = 0;
495     char inputline [128];
496     int code;
497     bool end_reached = false;
498     bool get_line = true;
499     bool in_interactive = false;
500
501     if (inputfile) {
502         if (freopen(inputfile, "r", stdin) == NULL) {
503             char *msg = NULL;
504             if (asprintf(&msg, "Failed to open %s", inputfile) < 0)
505                 perror("Failed to open input file");
506             else
507                 perror(msg);
508             return -1;
509         }
510     }
511
512     install_signal_handlers();
513
514     // make readline silent by default
515     echo_commands = echo_commands || isatty(fileno(stdin));
516     if (echo_commands)
517         rl_outstream = NULL;
518     else
519         rl_outstream = fopen("/dev/null", "w");
520
521     while(1) {
522         if (get_line) {
523             line = readline(AUGTOOL_PROMPT);
524         } else {
525             line = NULL;
526         }
527
528         if (line == NULL) {
529             if (!isatty(fileno(stdin)) && interactive && !in_interactive) {
530                 in_interactive = true;
531                 if (echo_commands)
532                     printf("\n");
533                 echo_commands = true;
534
535                 // reopen in stream
536                 if (freopen("/dev/tty", "r", stdin) == NULL) {
537                     perror("Failed to open terminal for reading");
538                     return -1;
539                 }
540                 rl_instream = stdin;
541
542                 // reopen stdout and stream to a tty if originally silenced or
543                 // not connected to a tty, for full interactive mode
544                 if (rl_outstream == NULL || !isatty(fileno(rl_outstream))) {
545                     if (rl_outstream != NULL) {
546                         fclose(rl_outstream);
547                         rl_outstream = NULL;
548                     }
549                     if (freopen("/dev/tty", "w", stdout) == NULL) {
550                         perror("Failed to reopen stdout");
551                         return -1;
552                     }
553                     rl_outstream = stdout;
554                 }
555                 continue;
556             }
557
558             if (auto_save) {
559                 strncpy(inputline, "save", sizeof(inputline));
560                 line = inputline;
561                 if (echo_commands)
562                     printf("%s\n", line);
563                 auto_save = false;
564             } else {
565                 end_reached = true;
566             }
567             get_line = false;
568         }
569
570         if (end_reached) {
571             if (echo_commands)
572                 printf("\n");
573             return ret;
574         }
575
576         if (*line == '\0' || *line == '#') {
577             free(line);
578             continue;
579         }
580
581         code = run_command(line, timing);
582         if (code == -2) {
583             free(line);
584             return ret;
585         }
586
587         if (code < 0) {
588             ret = -1;
589             print_aug_error();
590         }
591
592         if (line != inputline)
593             free(line);
594     }
595 }
596
597 static int run_args(int argc, char **argv) {
598     size_t len = 0;
599     char *line = NULL;
600     int   code;
601
602     for (int i=0; i < argc; i++)
603         len += strlen(argv[i]) + 1;
604     if (ALLOC_N(line, len + 1) < 0)
605         return -1;
606     for (int i=0; i < argc; i++) {
607         strcat(line, argv[i]);
608         strcat(line, " ");
609     }
610     if (echo_commands)
611         printf("%s%s\n", AUGTOOL_PROMPT, line);
612     code = run_command(line, timing);
613     free(line);
614     if (code >= 0 && auto_save)
615         if (echo_commands)
616             printf("%ssave\n", AUGTOOL_PROMPT);
617     code = run_command("save", false);
618
619     if (code < 0) {
620         code = -1;
621         print_aug_error();
622     }
623     return (code >= 0 || code == -2) ? 0 : -1;
624 }
625
626 static void add_transforms(char *ts, size_t tslen) {
627     char *command;
628     int r;
629     char *t = NULL;
630     bool added_transform = false;
631
632     while ((t = argz_next(ts, tslen, t))) {
633         r = xasprintf(&command, "transform %s", t);
634         if (r < 0)
635             fprintf(stderr, "error: Failed to add transform %s: could not allocate memory\n", t);
636
637         r = aug_srun(aug, stdout, command);
638         if (r < 0)
639             fprintf(stderr, "error: Failed to add transform %s: %s\n", t, aug_error_message(aug));
640
641         free(command);
642         added_transform = true;
643     }
644
645     if (added_transform) {
646         r = aug_load(aug);
647         if (r < 0)
648             fprintf(stderr, "error: Failed to load with new transforms: %s\n", aug_error_message(aug));
649     }
650 }
651
652 int main(int argc, char **argv) {
653     int r;
654     struct timeval start, stop;
655
656     setlocale(LC_ALL, "");
657
658     parse_opts(argc, argv);
659
660     if (timing) {
661         printf("Initializing augeas ... ");
662         fflush(stdout);
663     }
664     gettimeofday(&start, NULL);
665
666     aug = aug_init(root, loadpath, flags|AUG_NO_ERR_CLOSE);
667
668     gettimeofday(&stop, NULL);
669     if (timing) {
670         printf("done\n");
671         print_time_taken(&start, &stop);
672     }
673
674     if (aug == NULL || aug_error(aug) != AUG_NOERROR) {
675         fprintf(stderr, "Failed to initialize Augeas\n");
676         if (aug != NULL)
677             print_aug_error();
678         exit(EXIT_FAILURE);
679     }
680     add_transforms(transforms, transformslen);
681     if (print_version) {
682         print_version_info();
683         return EXIT_SUCCESS;
684     }
685     readline_init();
686     if (optind < argc) {
687         // Accept one command from the command line
688         r = run_args(argc - optind, argv+optind);
689     } else {
690         r = main_loop();
691     }
692     if (history_file != NULL)
693         write_history(history_file);
694
695     return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
696 }
697
698
699 /*
700  * Local variables:
701  *  indent-tabs-mode: nil
702  *  c-indent-level: 4
703  *  c-basic-offset: 4
704  *  tab-width: 4
705  * End:
706  */