Imported Upstream version 1.12.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             if (ALLOC_N(path, end - text + 2) < 0)
116                 return NULL;
117             strncpy(path, text, end - text);
118             strcat(path, "*");
119         }
120
121         for (;current < nchildren; current++)
122             free((void *) children[current]);
123         free((void *) children);
124         nchildren = aug_match(aug, path, &children);
125         current = 0;
126
127         ctx = NULL;
128         if (path[0] != SEP)
129             aug_get(aug, AUGEAS_CONTEXT, (const char **) &ctx);
130
131         free(path);
132     }
133
134     if (end == NULL)
135         end = (char *) text;
136
137     while (current < nchildren) {
138         char *child = children[current];
139         current += 1;
140
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);
145                 if (c == NULL) {
146                     free(child);
147                     return NULL;
148                 }
149                 child = c;
150                 strcat(child, "/");
151             }
152
153             /* strip off context if the user didn't give it */
154             if (ctx != NULL) {
155                 int ctxidx = strlen(ctx);
156                 if (child[ctxidx] == SEP)
157                     ctxidx++;
158                 char *c = strdup(&child[ctxidx]);
159                 free(child);
160                 if (c == NULL)
161                     return NULL;
162                 child = c;
163             }
164
165             rl_filename_completion_desired = 1;
166             rl_completion_append_character = '\0';
167             return child;
168         } else {
169             free(child);
170         }
171     }
172     return NULL;
173 }
174
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",
183         "info", "count",
184         NULL };
185
186     static int current = 0;
187     const char *name;
188
189     if (state == 0)
190         current = 0;
191
192     rl_completion_append_character = ' ';
193     while ((name = commands[current]) != NULL) {
194         current += 1;
195         if (STREQLEN(text, name, strlen(text)))
196             return strdup(name);
197     }
198     return NULL;
199 }
200
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) {
205     return NULL;
206 }
207 #endif
208
209 #ifndef HAVE_RL_CRLF
210 static int rl_crlf(void) {
211     if (rl_outstream != NULL)
212         putc('\n', rl_outstream);
213     return 0;
214 }
215 #endif
216
217 #ifndef HAVE_RL_REPLACE_LINE
218 static void rl_replace_line(ATTRIBUTE_UNUSED const char *text,
219                               ATTRIBUTE_UNUSED int clear_undo) {
220     return;
221 }
222 #endif
223
224 static char **readline_completion(const char *text, int start,
225                                   ATTRIBUTE_UNUSED int end) {
226     if (start == 0)
227         return rl_completion_matches(text, readline_command_generator);
228     else
229         return rl_completion_matches(text, readline_path_generator);
230
231     return NULL;
232 }
233
234 static char *get_home_dir(uid_t uid) {
235     char *strbuf;
236     char *result;
237     struct passwd pwbuf;
238     struct passwd *pw = NULL;
239     long val = sysconf(_SC_GETPW_R_SIZE_MAX);
240
241     if (val < 0) {
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).
244         val = 16*1024;
245     }
246
247     size_t strbuflen = (size_t) val;
248
249     if (ALLOC_N(strbuf, strbuflen) < 0)
250         return NULL;
251
252     if (getpwuid_r(uid, &pwbuf, strbuf, strbuflen, &pw) != 0 || pw == NULL) {
253         free(strbuf);
254
255         // Try to get the user's home dir from the environment
256         char *env = getenv("HOME");
257         if (env != NULL) {
258             return strdup(env);
259         }
260         return NULL;
261     }
262
263     result = strdup(pw->pw_dir);
264
265     free(strbuf);
266
267     return result;
268 }
269
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;
274
275     /* Set up persistent history */
276     char *home_dir = get_home_dir(getuid());
277     char *history_dir = NULL;
278
279     if (home_dir == NULL)
280         goto done;
281
282     if (_xasprintf(&history_dir, "%s/.augeas", home_dir) < 0)
283         goto done;
284
285     if (mkdir(history_dir, 0755) < 0 && errno != EEXIST)
286         goto done;
287
288     if (_xasprintf(&history_file, "%s/history", history_dir) < 0)
289         goto done;
290
291     stifle_history(500);
292
293     read_history(history_file);
294
295  done:
296     free(home_dir);
297     free(history_dir);
298 }
299
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",
305             progname);
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"
323                     "                         for modules\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");
329
330     exit(EXIT_FAILURE);
331 }
332
333 static void parse_opts(int argc, char **argv) {
334     int opt;
335     size_t loadpathlen = 0;
336     enum {
337         VAL_VERSION = CHAR_MAX + 1,
338         VAL_SPAN = VAL_VERSION + 1,
339         VAL_TIMING = VAL_SPAN + 1
340     };
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 },
360         { 0, 0, 0, 0}
361     };
362     int idx;
363
364     while ((opt = getopt_long(argc, argv, "hnbcr:I:t:l:ef:siSLA", options, &idx)) != -1) {
365         switch(opt) {
366         case 'c':
367             flags |= AUG_TYPE_CHECK;
368             break;
369         case 'b':
370             flags |= AUG_SAVE_BACKUP;
371             break;
372         case 'n':
373             flags |= AUG_SAVE_NEWFILE;
374             break;
375         case 'h':
376             help();
377             break;
378         case 'r':
379             root = optarg;
380             break;
381         case 'I':
382             argz_add(&loadpath, &loadpathlen, optarg);
383             break;
384         case 't':
385             argz_add(&transforms, &transformslen, optarg);
386             break;
387         case 'l':
388             // --load-file implies --noload
389             flags |= AUG_NO_LOAD;
390             argz_add(&loadonly, &loadonlylen, optarg);
391             break;
392         case 'e':
393             echo_commands = 1;
394             break;
395         case 'f':
396             inputfile = optarg;
397             break;
398         case 's':
399             auto_save = true;
400             break;
401         case 'i':
402             interactive = true;
403             break;
404         case 'S':
405             flags |= AUG_NO_STDINC;
406             break;
407         case 'L':
408             flags |= AUG_NO_LOAD;
409             break;
410         case 'A':
411             flags |= AUG_NO_MODL_AUTOLOAD;
412             break;
413         case VAL_VERSION:
414             flags |= AUG_NO_MODL_AUTOLOAD;
415             print_version = true;
416             break;
417         case VAL_SPAN:
418             flags |= AUG_ENABLE_SPAN;
419             break;
420         case VAL_TIMING:
421             timing = true;
422             break;
423         default:
424             fprintf(stderr, "Try '%s --help' for more information.\n",
425                     progname);
426             exit(EXIT_FAILURE);
427             break;
428         }
429     }
430     argz_stringify(loadpath, loadpathlen, PATH_SEP_CHAR);
431 }
432
433 static void print_version_info(void) {
434     const char *version;
435     int r;
436
437     r = aug_get(aug, "/augeas/version", &version);
438     if (r != 1)
439         goto error;
440
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");
448     return;
449  error:
450     fprintf(stderr, "Something went terribly wrong internally - please file a bug\n");
451 }
452
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);
458 }
459
460 static int run_command(const char *line, bool with_timing) {
461     int result;
462     struct timeval stop, start;
463
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);
469     }
470
471     if (isatty(fileno(stdin)))
472         add_history(line);
473     return result;
474 }
475
476 static void print_aug_error(void) {
477     if (aug_error(aug) == AUG_ENOMEM) {
478         fprintf(stderr, "Out of memory.\n");
479         return;
480     }
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");
489         }
490     }
491 }
492
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);
496
497     // Move the cursor to the next screen line, then force a re-display.
498     rl_crlf();
499     rl_forced_update_display();
500 }
501
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);
510 }
511
512 static int main_loop(void) {
513     char *line = NULL;
514     int ret = 0;
515     char inputline [128];
516     int code;
517     bool end_reached = false;
518     bool get_line = true;
519     bool in_interactive = false;
520
521     if (inputfile) {
522         if (freopen(inputfile, "r", stdin) == NULL) {
523             char *msg = NULL;
524             if (asprintf(&msg, "Failed to open %s", inputfile) < 0)
525                 perror("Failed to open input file");
526             else
527                 perror(msg);
528             return -1;
529         }
530     }
531
532     install_signal_handlers();
533
534     // make readline silent by default
535     echo_commands = echo_commands || isatty(fileno(stdin));
536     if (echo_commands)
537         rl_outstream = NULL;
538     else
539         rl_outstream = fopen("/dev/null", "w");
540
541     while(1) {
542         if (get_line) {
543             line = readline(AUGTOOL_PROMPT);
544         } else {
545             line = NULL;
546         }
547
548         if (line == NULL) {
549             if (!isatty(fileno(stdin)) && interactive && !in_interactive) {
550                 in_interactive = true;
551                 if (echo_commands)
552                     printf("\n");
553                 echo_commands = true;
554
555                 // reopen in stream
556                 if (freopen("/dev/tty", "r", stdin) == NULL) {
557                     perror("Failed to open terminal for reading");
558                     return -1;
559                 }
560                 rl_instream = stdin;
561
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);
567                         rl_outstream = NULL;
568                     }
569                     if (freopen("/dev/tty", "w", stdout) == NULL) {
570                         perror("Failed to reopen stdout");
571                         return -1;
572                     }
573                     rl_outstream = stdout;
574                 }
575                 continue;
576             }
577
578             if (auto_save) {
579                 strncpy(inputline, "save", sizeof(inputline));
580                 line = inputline;
581                 if (echo_commands)
582                     printf("%s\n", line);
583                 auto_save = false;
584             } else {
585                 end_reached = true;
586             }
587             get_line = false;
588         }
589
590         if (end_reached) {
591             if (echo_commands)
592                 printf("\n");
593             return ret;
594         }
595
596         if (*line == '\0' || *line == '#') {
597             free(line);
598             continue;
599         }
600
601         code = run_command(line, timing);
602         if (code == -2) {
603             free(line);
604             return ret;
605         }
606
607         if (code < 0) {
608             ret = -1;
609             print_aug_error();
610         }
611
612         if (line != inputline)
613             free(line);
614     }
615 }
616
617 static int run_args(int argc, char **argv) {
618     size_t len = 0;
619     char *line = NULL;
620     int   code;
621
622     for (int i=0; i < argc; i++)
623         len += strlen(argv[i]) + 1;
624     if (ALLOC_N(line, len + 1) < 0)
625         return -1;
626     for (int i=0; i < argc; i++) {
627         strcat(line, argv[i]);
628         strcat(line, " ");
629     }
630     if (echo_commands)
631         printf("%s%s\n", AUGTOOL_PROMPT, line);
632     code = run_command(line, timing);
633     free(line);
634     if (code >= 0 && auto_save)
635         if (echo_commands)
636             printf("%ssave\n", AUGTOOL_PROMPT);
637     code = run_command("save", false);
638
639     if (code < 0) {
640         code = -1;
641         print_aug_error();
642     }
643     return (code >= 0 || code == -2) ? 0 : -1;
644 }
645
646 static void add_transforms(char *ts, size_t tslen) {
647     char *command;
648     int r;
649     char *t = NULL;
650     bool added_transform = false;
651
652     while ((t = argz_next(ts, tslen, t))) {
653         r = _xasprintf(&command, "transform %s", t);
654         if (r < 0)
655             fprintf(stderr, "error: Failed to add transform %s: could not allocate memory\n", t);
656
657         r = aug_srun(aug, stdout, command);
658         if (r < 0)
659             fprintf(stderr, "error: Failed to add transform %s: %s\n", t, aug_error_message(aug));
660
661         free(command);
662         added_transform = true;
663     }
664
665     if (added_transform) {
666         r = aug_load(aug);
667         if (r < 0)
668             fprintf(stderr, "error: Failed to load with new transforms: %s\n", aug_error_message(aug));
669     }
670 }
671
672 static void load_files(char *ts, size_t tslen) {
673     char *command;
674     int r;
675     char *t = NULL;
676
677     while ((t = argz_next(ts, tslen, t))) {
678         r = _xasprintf(&command, "load-file %s", t);
679         if (r < 0)
680             fprintf(stderr, "error: Failed to load file %s: could not allocate memory\n", t);
681
682         r = aug_srun(aug, stdout, command);
683         if (r < 0)
684             fprintf(stderr, "error: Failed to load file %s: %s\n", t, aug_error_message(aug));
685
686         free(command);
687     }
688 }
689
690 int main(int argc, char **argv) {
691     int r;
692     struct timeval start, stop;
693
694     setlocale(LC_ALL, "");
695
696     parse_opts(argc, argv);
697
698     if (timing) {
699         printf("Initializing augeas ... ");
700         fflush(stdout);
701     }
702     gettimeofday(&start, NULL);
703
704     aug = aug_init(root, loadpath, flags|AUG_NO_ERR_CLOSE);
705
706     gettimeofday(&stop, NULL);
707     if (timing) {
708         printf("done\n");
709         print_time_taken(&start, &stop);
710     }
711
712     if (aug == NULL || aug_error(aug) != AUG_NOERROR) {
713         fprintf(stderr, "Failed to initialize Augeas\n");
714         if (aug != NULL)
715             print_aug_error();
716         exit(EXIT_FAILURE);
717     }
718     load_files(loadonly, loadonlylen);
719     add_transforms(transforms, transformslen);
720     if (print_version) {
721         print_version_info();
722         return EXIT_SUCCESS;
723     }
724     readline_init();
725     if (optind < argc) {
726         // Accept one command from the command line
727         r = run_args(argc - optind, argv+optind);
728     } else {
729         r = main_loop();
730     }
731     if (history_file != NULL)
732         write_history(history_file);
733
734     aug_close(aug);
735     return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
736 }
737
738
739 /*
740  * Local variables:
741  *  indent-tabs-mode: nil
742  *  c-indent-level: 4
743  *  c-basic-offset: 4
744  *  tab-width: 4
745  * End:
746  */