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