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