Bump to 1.14.1
[platform/upstream/augeas.git] / src / augprint.c
1 /* vim: expandtab:softtabstop=2:tabstop=2:shiftwidth=2
2  *
3  * Copyright (C) 2022 George Hansper <george@hansper.id.au>
4  * -----------------------------------------------------------------------
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along with this program.
16  * If not, see <https://www.gnu.org/licenses/>.
17  *
18  * Author: George Hansper <george@hansper.id.au>
19  * -----------------------------------------------------------------------
20  * This tool produces output similar to the augeas 'print' statement
21  * or aug_print() API function
22  *
23  * Where augeas print may produce a set of paths and values like this:
24  *
25  * /files/some/path/label[1]/tail_a value_1a
26  * /files/some/path/label[1]/tail_b value_1b
27  * /files/some/path/label[2]/tail_a value_2a
28  * /files/some/path/label[2]/tail_b value_2b
29  *
30  * or
31  *
32  * /files/some/path/1/tail_a value_1a
33  * /files/some/path/1/tail_b value_1b
34  * /files/some/path/2/tail_a value_2a
35  * /files/some/path/2/tail_b value_2b
36  *
37  *
38  * This tool replaces the abosolute 'position' (1, 2, 3,..) with a path-expression that matches the position
39  * where the values are used to identify the position
40  *
41  * /files/some/path/label[tail_a = value_1a]/tail_a value_1a
42  * /files/some/path/label[tail_a = value_1a]/tail_b value_1b
43  * /files/some/path/label[tail_a = value_2a]/tail_a value_2a
44  * /files/some/path/label[tail_a = value_2a]/tail_b value_2b
45  *
46  * Terms used within:
47  *
48  * /files/some/path/label[1]/tail_a value_1a
49  * `--------------------' \ `-----' `------'
50  *         `--- head       \    \       `--- value
51  *                          \    `--- tail
52  *                           `-- position
53  *
54  * For more complex paths
55  * /files/some/path/1/segement/label[1]/tail_a value_1a
56  *                    `----------------------'
57  *                             |
58  *                             v
59  *                   /segement/label/tail_a
60  *                   `--------------------'
61  *                             `--- simple_tail
62  */
63
64 #define _GNU_SOURCE
65 #include <config.h>
66 #include <stdio.h>
67 #include <stdlib.h>        /* for exit, strtoul */
68 #include <getopt.h>
69 #include <string.h>
70 #include <augeas.h>
71 #include <errno.h>
72 #include <libgen.h>        /* for basename() on FreeBSD and MacOS */
73 #include <sys/param.h>     /* for MIN() MAX() */
74 #include <unistd.h>
75 #include "augprint.h"
76
77 #define CHECK_OOM(condition, action, arg)         \
78     do {                                          \
79         if (condition) {                          \
80             out_of_memory|=1;                     \
81             action(arg);                          \
82         }                                         \
83     } while (0)
84
85 #define MAX_PRETTY_WIDTH 30
86
87 static augeas *aug = NULL;
88 static unsigned int flags = AUG_NONE;
89 static unsigned int num_groups = 0;
90 static struct group **all_groups=NULL;
91 static char **all_matches;
92 static int num_matched;
93 static struct augeas_path_value **all_augeas_paths; /* array of pointers */
94
95 static int out_of_memory=0;
96 static int verbose=0;
97 static int debug=0;
98 static int pretty=0;
99 static int noseq=0;
100 static int help=0;
101 static int print_version=0;
102 static int use_regexp=0;
103 static char *lens = NULL;
104 static char *loadpath = NULL;
105
106 static char *str_next_pos(char *start, char **head_end, unsigned int *pos);
107 static char *str_simplified_tail(char *tail_orig);
108 static void add_segment_to_group(struct path_segment *segment, struct augeas_path_value *);
109 static char *quote_value(char *);
110 static char *regexp_value(char *, int);
111
112
113 static void exit_oom(const char *msg) {
114   fprintf(stderr, "Out of memory");
115   if( msg ) {
116     fprintf(stderr, " %s\n", msg);
117   } else {
118     fprintf(stderr, "\n");
119   }
120   exit(1);
121 }
122
123 /* Remove /./ and /../ components from path
124  * because they just don't work with augeas
125  */
126 static void cleanup_filepath(char *path) {
127   char *to=path, *from=path;
128   while(*from) {
129     if(*from == '/' ) {
130       if( *(from+1) == '/' ) {
131           /* // skip over 2nd / */
132           from++;
133           continue;
134       } else if( *(from+1) == '.' ) {
135         if( *(from+2) == '/' ) {
136           /* /./ skip 2 spaces */
137           from+=2;
138           continue;
139         } else if ( *(from+2) == '.' && *(from+3) == '/' ) {
140           /* /../ rewind to previous / */
141           from+=3;
142           while( to > path && *(--to) != '/' )
143             ;
144           continue;
145         }
146       }
147     }
148     *to++ = *from++;
149   }
150   *to='\0';
151 }
152
153 static char *find_lens_for_path(char *filename) {
154   char *aug_load_path = NULL;
155   char **matching_lenses;
156   int  num_lenses, result, ndx;
157   char *filename_tail;
158   filename_tail = filename;
159   for (char *s1 = filename; *s1; s1++ ) {
160      if ( *s1 == '/' )
161        filename_tail = s1+1;
162   }
163   result = asprintf(&aug_load_path, "/augeas/load/*['%s' =~ glob(incl)]['%s' !~ glob(excl)]['%s' !~ glob(excl)]", filename, filename, filename_tail);
164   CHECK_OOM( result < 0, exit_oom, NULL);
165
166   if(debug) {
167     fprintf(stderr,"path expr: %s\n",aug_load_path);
168     aug_print(aug, stderr, aug_load_path);
169   }
170   num_lenses = aug_match( aug, aug_load_path, &matching_lenses);
171   if ( num_lenses == 0 ) {
172     fprintf(stderr, "Aborting - no lens applies for target: %s\n", filename);
173     exit(1);
174   }
175   lens = matching_lenses[0] + 13; /* skip over /augeas/load */
176
177   if ( num_lenses > 1 ) {
178     /* Should never happen */
179     for( ndx=0; ndx<num_lenses;ndx++) {
180       fprintf(stderr,"Found lens: %s\n", matching_lenses[ndx]);
181     }
182     fprintf(stderr, "Warning: multiple lenses apply to target %s - using %s\n", filename, lens);
183   }
184
185   free(aug_load_path);
186   return(lens);
187 }
188
189 static void move_tree(char *inputfile, char *target_file) {
190   char *files_inputfile;
191   char *files_targetfile;
192   char *dangling_path;
193   char *aug_rm_path;
194   int  result;
195   int  removed;
196   char *last, *s;
197   result = asprintf(&files_inputfile, "/files%s", inputfile );
198   CHECK_OOM( result < 0, exit_oom, NULL);
199
200   result = asprintf(&files_targetfile, "/files%s", target_file );
201   CHECK_OOM( result < 0, exit_oom, NULL);
202
203   aug_mv(aug, files_inputfile, files_targetfile);
204   /* After the aug_mv, we're left with the empty parent nodes */
205   dangling_path = files_inputfile;
206   do {
207     /* dirname(prune_path), without copying anything */
208     for( s=last=dangling_path; *s; s++ ) {
209       if( *s == '/')
210         last=s;
211     }
212     *last = '\0';
213
214     result = asprintf(&aug_rm_path, "%s[count(*)=0 and .='']", dangling_path );
215     CHECK_OOM( result < 0, exit_oom, NULL);
216
217     removed = aug_rm(aug, aug_rm_path);
218     free(aug_rm_path);
219
220   } while ( removed == 1 );
221
222   free(files_inputfile);
223   free(files_targetfile);
224 }
225
226 /* ----- split_path() str_next_pos() str_simplified_tail() add_segment_to_group() ----- */
227 /* split_path()
228  * Break up a path like this
229  *   /head/label_a[123]/middle/label_b[456]/tail
230  *
231  * into (struct path_segment) segments like this
232  *
233  * head     = "/head/label_a"
234  * segement = "/head/label_a"
235  * position = 123
236  * simplified_tail = "/middle/label_b/tail"
237  *
238  * head     = "/head/label_a[123]/middle/label_b"
239  * segement =                   "/middle/label_b"
240  * position = 456
241  * simplified_tail = "/tail"
242  *
243  * head     = "/head/label_a[123]/middle/label_b[456]/tail"
244  * segement =                   "/tail"
245  * position = UINT_MAX (-1)
246  * simplified_tail = ""
247  *
248  * If label_b is absent, use seq::* or * instead in the simplified tail, head is unaffected
249  */
250 static struct path_segment *split_path(struct augeas_path_value *path_value) {
251   char *path = path_value->path;
252   struct path_segment *first_segment = NULL;
253   struct path_segment *this_segment = NULL;
254   struct path_segment **next_segment = &first_segment;
255   unsigned int position;
256   char *head_end;
257   char *path_seg_start=path;
258   char *path_seg_end;
259
260   while(*path_seg_start) {
261     this_segment = malloc(sizeof(struct path_segment));
262     CHECK_OOM(! this_segment, exit_oom, "split_path() allocating struct path_segment");
263
264     *next_segment  = this_segment;
265     path_seg_end   = str_next_pos(path_seg_start, &head_end, &position);
266     this_segment->head     = strndup(path, (head_end-path));
267     this_segment->segment  = (this_segment->head) + (path_seg_start-path);
268     this_segment->position = position;
269     this_segment->simplified_tail = str_simplified_tail(path_seg_end);
270     path_seg_start = path_seg_end;
271     this_segment->next     = NULL;
272     next_segment = &(this_segment->next);
273     if ( position != UINT_MAX ) {
274       add_segment_to_group(this_segment, path_value);
275     } else {
276       this_segment->group = NULL;
277     }
278   }
279   return(first_segment);
280 }
281
282 /*
283  * str_next_pos() scans a string from (char *)start, and finds the next occurance
284  * of the substring '[123]' or '/123/' where 123 is a decimal number
285  * (int *)pos is set to the value of 123
286  * if [123] is found,
287  *   - head_end points to the character '['
288  *   - returns a  pointer to the character after the ']'
289  * if /123/  or /123\0 is found
290  *   - head_end points to character after the first '/'
291  *   - returns a pointer to the second '/' or '\0'
292  * if none of the above are found
293  *   - head_end points to the terminating '\0'
294  *   - returns a pointer to the terminating '\0' (same as head_end)
295  * ie. look for [123] or /123/ or /123\0, set int pos to 123 or UINT_MAX, set head_end to char before '[' or before 1st digit; return pointer to trailing / or \0
296 */
297 static char *str_next_pos(char *start, char **head_end, unsigned int *pos) {
298   char *endptr=NULL;
299   char *s=start;
300   unsigned long lpos;
301   *pos=UINT_MAX;
302   while(*s) {
303     if( *s=='[' && *(s+1) >= '0' && *(s+1) <= '9' ) {
304         lpos = strtoul(s+1, &endptr, 10);
305         *pos = MIN(lpos, UINT_MAX);
306         if ( *endptr == ']' ) {
307           /* success */
308           *head_end = s;
309           return(endptr+1);
310         }
311     } else if ( *s == '/' && *(s+1) >= '0' && *(s+1) <= '9' ) {
312         lpos = strtoul(s+1, &endptr, 10);
313         *pos = MIN(lpos, UINT_MAX);
314         if ( *endptr == '\0' || *endptr == '/' ) {
315           /* success */
316           *head_end = s+1;
317           return(endptr);
318         }
319     }
320     s++;
321   }
322   *head_end=s;
323   return(s);
324 }
325
326 static char *str_simplified_tail(char *tail_orig) {
327   int tail_len=0;
328   char *tail;
329   char *from, *to, *scan;
330   char *simple;
331   /* first work out how much space we will need to allocate */
332   tail=tail_orig;
333   while(*tail) {
334     if( *tail == '[' && *(tail+1) >= '0' && *(tail+1) <= '9' ) {
335       /* Look for matching ']' */
336       scan = tail;
337       scan++;
338       while (*scan >= '0' && *scan <= '9')
339         scan++;
340       if(*scan == ']') {
341         tail=scan+1;
342         continue;
343       }
344     } else if ( *tail == '/' && *(tail+1) >= '0' && *(tail+1) <= '9' ) {
345       /* Look for next '/' or '\0' */
346       scan = tail;
347       scan++;
348       while (*scan >= '0' && *scan <= '9')
349         scan++;
350       if(*scan == '/' || *scan == '\0' ) {
351         tail=scan;
352         tail_len += 7; /* allow for /seq::* */
353         continue;
354       }
355     }
356     tail_len++;
357     tail++;
358   }
359   simple = (char *) malloc( sizeof(char) * (tail_len+1));
360   CHECK_OOM( ! simple, exit_oom, "allocating simple_tail in str_simplified_tail()");
361
362   from=tail_orig;
363   to=simple;
364   while(*from) {
365     if( *from == '[' && *(from+1) >= '0' && *(from+1) <= '9' ) {
366       /* skip over  [123] */
367       scan = from;
368       scan++;
369       while (*scan >= '0' && *scan <= '9')
370         scan++;
371       if(*scan == ']') {
372         from=scan+1;
373         continue;
374       }
375     } else if ( *from == '/' && *(from+1) >= '0' && *(from+1) <= '9' ) {
376       /* replace /123 with /seq::*  */
377       scan = from;
378       scan++;
379       while (*scan >= '0' && *scan <= '9')
380         scan++;
381       if(*scan == '/' || *scan == '\0' ) {
382         from=scan;
383         if ( noseq ) {
384           strcpy(to,"/*");
385           to += 2;
386         } else {
387           strcpy(to,"/seq::*");
388           to += 7; /* len("/seq::*") */
389         }
390         continue;
391       }
392     }
393     *to++ = *from++; /* copy */
394   }
395   *to='\0';
396   return(simple);
397 }
398
399 /* Compare two values (char *), subject to use_regexp
400  * If both pointers are NULL, return 1 (true)
401  * If only one pointer is NULL, return 0 (false)
402  * set *matched to the number of characters in common
403  * return 1 (true) if the strings match, otherwise 0 (false)
404  */
405 static int value_cmp(char *v1, char *v2, unsigned int *matched) {
406   char *s1, *s2;
407   if( v1 == NULL && v2 == NULL ) {
408     *matched = 0;
409     return(1);
410   }
411   if( v1 == NULL || v2 == NULL ) {
412     *matched = 0;
413     return(0);
414   }
415   s1 = v1;
416   s2 = v2;
417   *matched = 0;
418   if( use_regexp ) {
419     /* Compare values, allowing for the fact that ']' is replaced with '.' */
420     while( *s1 || *s2 ) {
421       if( *s1 != *s2 ) {
422         if( *s1 =='\0' || *s2 == '\0')
423           return(0);
424         if( *s1 != ']' && *s2 != ']' )
425           return(0);
426       }
427       s1++; s2++; (*matched)++;
428     }
429     return(1);
430   } else {
431     while( *s1 == *s2 ) {
432       if( *s1 == '\0' ) {
433         return(1);
434       }
435       s1++; s2++; (*matched)++;
436     }
437     return(0);
438   }
439   return(1); /* unreachable */
440 }
441
442 /* Find an existing group with the same 'head'
443  * If no such group exists, create a new one
444  * Update the size of all_groups array if required
445  */
446 static struct group *find_or_create_group(char *head) {
447   unsigned long ndx;
448   struct group **all_groups_realloc;
449   unsigned int num_groups_newsize;
450   struct group *group = NULL;
451   /* Look for an existing group with group->head matching path_seg->head */
452   for(ndx=0; ndx < num_groups; ndx++) {
453     if( strcmp(head, all_groups[ndx]->head) == 0 ) {
454       group = all_groups[ndx];
455       return(group);
456     }
457   }
458   /* Group not found - create a new one */
459   /* First, grow all_groups[] array if required */
460   if ( num_groups % 32 == 0 ) {
461       num_groups_newsize = (num_groups)/32*32+32;
462       all_groups_realloc = reallocarray(all_groups, sizeof(struct group *), num_groups_newsize);
463       CHECK_OOM( ! all_groups_realloc, exit_oom, "in find_or_create_group()");
464
465       all_groups=all_groups_realloc;
466   }
467   /* Create new group */
468   group = malloc(sizeof(struct group));
469   CHECK_OOM( ! group, exit_oom, "allocating struct group in find_or_create_group()");
470
471   all_groups[num_groups++] = group;
472   group->head = head;
473   group->all_tails = NULL;
474   group->position_array_size = 0;
475   group->tails_at_position = NULL;
476   group->chosen_tail = NULL;
477   group->chosen_tail_state = NULL;
478   group->first_tail = NULL;
479   group->position_array_size = 0;
480   group->max_position = 0;
481   group->subgroups = NULL;  /* subgroups are only created if we need to use our 3rd preference */
482   group->subgroup_position = NULL;
483   /* for --pretty */
484   group->pretty_width_ct = NULL;
485   /* for --regexp */
486   group->re_width_ct = NULL;
487   group->re_width_ft = NULL;
488
489   return(group);
490 }
491
492 /* Find a matching tail+value within group->all_tails linked list
493  * If no such tail exists, append a new (struct tail) list item
494  * Return the tail found, or the new tail
495  */
496 static struct tail *find_or_create_tail(struct group *group, struct path_segment *path_seg, struct augeas_path_value *path_value) {
497   /* Scan for a matching simplified tail+value in group->all_tails */
498   struct tail *tail;
499   struct tail *found_tail_value=NULL;
500   struct tail *found_tail=NULL;
501   struct tail **all_tails_end;
502   unsigned int tail_found_this_pos=1;
503   unsigned int match_length;
504   all_tails_end =&(group->all_tails);
505   found_tail_value=NULL;
506   for( tail = group->all_tails; tail != NULL; tail=tail->next ) {
507     if( strcmp(path_seg->simplified_tail, tail->simple_tail) == 0 ) {
508       /* found matching simple_tail - increment counters */
509       tail->tail_found_map[path_seg->position]++;
510       tail_found_this_pos = tail->tail_found_map[path_seg->position];
511       if ( value_cmp(tail->value, path_value->value, &match_length ) ) {
512         /* matching tail+value found, increment tail_value_found */
513         tail->tail_value_found_map[path_seg->position]++;
514         tail->tail_value_found++;
515         found_tail_value=tail;
516       }
517       found_tail=tail;
518     }
519     all_tails_end=&tail->next;
520   }
521   if ( found_tail_value == NULL ) {
522     /* matching tail+value not found, create a new one */
523     tail = malloc(sizeof(struct tail));
524     CHECK_OOM( ! tail, exit_oom, "in find_or_create_tail()");
525
526     tail->tail_found_map       = reallocarray(NULL, sizeof(unsigned int), group->position_array_size);
527     CHECK_OOM( ! tail->tail_found_map, exit_oom, "in find_or_create_tail()");
528
529     tail->tail_value_found_map = reallocarray(NULL, sizeof(unsigned int), group->position_array_size);
530     CHECK_OOM( ! tail->tail_value_found_map, exit_oom, "in find_or_create_tail()");
531
532
533     for(unsigned int i=0; i<group->position_array_size; i++) {
534       tail->tail_found_map[i]=0;
535       tail->tail_value_found_map[i]=0;
536     }
537
538     if ( found_tail ) {
539       for( unsigned int ndx=0; ndx<=group->max_position; ndx++ ) {
540         tail->tail_found_map[ndx] = found_tail->tail_found_map[ndx];
541       }
542     }
543     tail->tail_found_map[path_seg->position]=tail_found_this_pos;
544     tail->tail_value_found_map[path_seg->position]=1;
545     tail->tail_value_found = 1;
546     tail->simple_tail = path_seg->simplified_tail;
547     tail->value       = path_value->value;
548     tail->value_qq    = path_value->value_qq;
549     tail->next        = NULL;
550     *all_tails_end = tail;
551     return(tail);
552   } else {
553     return(found_tail_value);
554   }
555 }
556
557 /* Append a (struct tail_stub) to the linked list group->tails_at_position[position] */
558 static void append_tail_stub(struct group *group, struct tail *tail, unsigned int position) {
559   struct tail_stub **tail_stub_pp;
560
561   for( tail_stub_pp=&(group->tails_at_position[position]); *tail_stub_pp != NULL; tail_stub_pp=&(*tail_stub_pp)->next ) {
562   }
563   *tail_stub_pp = malloc(sizeof(struct tail_stub));
564   CHECK_OOM( ! *tail_stub_pp, exit_oom, "in append_tail_stub()");
565
566   (*tail_stub_pp)->tail     = tail;
567   (*tail_stub_pp)->next     = NULL;
568 }
569
570 /* Grow memory structures within the group record and associated tail records
571  * to accommodate additional positions
572  */
573 static void grow_position_arrays(struct group *group, unsigned int new_max_position) {
574   struct tail_stub **tails_at_position_realloc;
575   struct tail      **chosen_tail_realloc;
576   struct tail_stub **first_tail_realloc;
577   unsigned int      *chosen_tail_state_realloc;
578   unsigned int      *pretty_width_ct_realloc;
579   unsigned int      *re_width_ct_realloc;
580   unsigned int      *re_width_ft_realloc;
581   unsigned int       ndx;
582   if( new_max_position != UINT_MAX && new_max_position >= group->position_array_size ) {
583     unsigned int old_size = group->position_array_size;
584     unsigned int new_size = (new_max_position+1) / 8 * 8 + 8;
585
586     /* Grow arrays within struct group */
587     tails_at_position_realloc = reallocarray(group->tails_at_position,  sizeof(struct tail_stub *),  new_size);
588     chosen_tail_realloc       = reallocarray(group->chosen_tail,        sizeof(struct tail *),       new_size);
589     first_tail_realloc        = reallocarray(group->first_tail,         sizeof(struct tail_stub *),  new_size);
590     chosen_tail_state_realloc = reallocarray(group->chosen_tail_state,  sizeof(chosen_tail_state_t), new_size);
591     pretty_width_ct_realloc   = reallocarray(group->pretty_width_ct,    sizeof(unsigned int),        new_size);
592     re_width_ct_realloc       = reallocarray(group->re_width_ct,        sizeof(unsigned int),        new_size);
593     re_width_ft_realloc       = reallocarray(group->re_width_ft,        sizeof(unsigned int),        new_size);
594     CHECK_OOM( ! tails_at_position_realloc || ! chosen_tail_realloc || ! chosen_tail_state_realloc ||
595                ! pretty_width_ct_realloc   || ! re_width_ct_realloc || ! re_width_ft_realloc       ||
596                ! first_tail_realloc, exit_oom, "in grow_position_arrays()");
597
598     /* initialize array entries between old size to new_size */
599     for( ndx=old_size; ndx < new_size; ndx++) {
600       tails_at_position_realloc[ndx]=NULL;
601       chosen_tail_realloc[ndx]=NULL;
602       first_tail_realloc[ndx]=NULL;
603       chosen_tail_state_realloc[ndx] = NOT_DONE;
604       pretty_width_ct_realloc[ndx] = 0;
605       re_width_ct_realloc[ndx] = 0;
606       re_width_ft_realloc[ndx] = 0;
607     }
608     group->tails_at_position = tails_at_position_realloc;
609     group->chosen_tail = chosen_tail_realloc;
610     group->first_tail  = first_tail_realloc;
611     group->chosen_tail_state = chosen_tail_state_realloc;
612     group->pretty_width_ct = pretty_width_ct_realloc;
613     group->re_width_ct = re_width_ct_realloc;
614     group->re_width_ft = re_width_ft_realloc;
615     /* Grow arrays in all_tails */
616     struct tail *tail;
617     for( tail = group->all_tails; tail != NULL; tail=tail->next ) {
618       unsigned int *tail_found_map_realloc;
619       unsigned int *tail_value_found_map_realloc;
620       tail_found_map_realloc       = reallocarray(tail->tail_found_map,       sizeof(unsigned int), new_size);
621       tail_value_found_map_realloc = reallocarray(tail->tail_value_found_map, sizeof(unsigned int), new_size);
622       CHECK_OOM( ! tail_found_map_realloc || ! tail_value_found_map_realloc, exit_oom, "in grow_position_arrays()");
623
624       /* initialize array entries between old size to new_size */
625       for( ndx=old_size; ndx < new_size; ndx++) {
626         tail_found_map_realloc[ndx]=0;
627         tail_value_found_map_realloc[ndx]=0;
628       }
629       tail->tail_found_map = tail_found_map_realloc;
630       tail->tail_value_found_map = tail_value_found_map_realloc;
631     }
632     group->position_array_size = new_size;
633   }
634 }
635
636 static void add_segment_to_group(struct path_segment *path_seg, struct augeas_path_value *path_value) {
637   struct group *group = NULL;
638   struct tail  *tail;
639   group = find_or_create_group(path_seg->head);
640
641   /* group is our new or matching group for this segment->head */
642   path_seg->group = group;
643   if( path_seg->position != UINT_MAX && path_seg->position > group->max_position ) {
644     group->max_position = path_seg->position;
645     if( group->max_position >= group->position_array_size ) {
646       /* grow arrays in group */
647       grow_position_arrays(group, group->max_position);
648     }
649   }
650   tail = find_or_create_tail(group, path_seg, path_value);
651
652   /* Append a tail_stub record to the linked list @ group->tails_at_position[position] */
653   append_tail_stub(group, tail, path_seg->position);
654 }
655
656 /* find_or_create_subgroup()
657  * This is called from choose_tail(), and is only used if we need to go to our 3rd Preference
658  */
659 static struct subgroup *find_or_create_subgroup(struct group *group, struct tail *first_tail) {
660   struct subgroup *subgroup_ptr;
661   struct subgroup **sg_pp;
662   for( sg_pp=&(group->subgroups); *sg_pp != NULL; sg_pp=&(*sg_pp)->next) {
663     if( (*sg_pp)->first_tail == first_tail ) {
664       return(*sg_pp);
665     }
666   }
667   /* Create and populate subgroup */
668   subgroup_ptr = (struct subgroup *) malloc( sizeof(struct subgroup));
669   CHECK_OOM( ! subgroup_ptr, exit_oom, "in find_or_create_subgroup()");
670
671   subgroup_ptr->next=NULL;
672   subgroup_ptr->first_tail=first_tail;
673   /* positions are 1..max_position, +1 for the terminating 0=end-of-list */
674   subgroup_ptr->matching_positions = malloc( (group->max_position+1) * sizeof( unsigned int ));
675   CHECK_OOM( ! subgroup_ptr->matching_positions, exit_oom, "in find_or_create_subgroup()");
676
677   /* malloc group->subgroup_position if not already done */
678   if ( ! group->subgroup_position ) {
679     group->subgroup_position = malloc( (group->max_position+1) * sizeof( unsigned int ));
680     CHECK_OOM( ! group->subgroup_position, exit_oom, "in find_or_create_subgroup()");
681
682   }
683   *sg_pp = subgroup_ptr; /* Append new subgroup record to list */
684   /* populate matching_positions */
685   unsigned int pos_ndx;
686   unsigned int ndx = 0;
687   for(pos_ndx=1; pos_ndx <= group->max_position; pos_ndx++ ){
688     /* save the position if this tail+value exists for this position - not necessarily the first tail, we need to check all tails at this position */
689     struct tail_stub *tail_stub_ptr;
690     for( tail_stub_ptr = group->tails_at_position[pos_ndx]; tail_stub_ptr != NULL; tail_stub_ptr=tail_stub_ptr->next ) {
691       if( tail_stub_ptr->tail == first_tail ) {
692         subgroup_ptr->matching_positions[ndx++] = pos_ndx;
693         if( first_tail == group->first_tail[pos_ndx]->tail ) {
694           /* If first_fail is also the first_tail for this position, update subgroup_position[] */
695           group->subgroup_position[pos_ndx]=ndx; /* yes, we want ndx+1, because matching_positions index starts at 0, where as the fallback position starts at 1 */
696         }
697         break;
698       }
699     }
700   }
701   subgroup_ptr->matching_positions[ndx] = 0;  /* 0 = end of list */
702   return(subgroup_ptr);
703 }
704
705 /* str_ischild()
706  * compare 2 strings which are  of the form simple_tail
707  * return true(1)  if parent == /path and child == /path/tail
708  * return false(0) if child == /pathother or child == /pat or anything else
709  */
710 static int str_ischild(char *parent, char *child) {
711   while( *parent ) {
712     if( *parent != *child ) {
713       return(0);
714     }
715     parent++;
716     child++;
717   }
718   if( *child == '/' ) {
719     return(1);
720   } else {
721     return(0);
722   }
723 }
724
725 /* Find the first tail in the linked-list that is not NULL, or has no child nodes
726    * eg for paths starting with /head/123/... ignore the entry:
727    *    /head/123 (null)
728    * and any further paths like this
729    *   head/123/tail (null)
730    *   head/123/tail/child (null)
731    * stop when we encounter a value, or find a tail that has no child nodes,
732    * if the next tail is eg
733    *   head/123/tail2
734    * then head/123/tail/child is significant, and that becomes the first_tail
735    */
736 static struct tail_stub *find_first_tail(struct tail_stub *tail_stub_ptr) {
737   if( tail_stub_ptr == NULL )
738     return(NULL);
739   for( ; tail_stub_ptr->next != NULL; tail_stub_ptr=tail_stub_ptr->next ) {
740     if ( tail_stub_ptr->tail->value != NULL && tail_stub_ptr->tail->value[0] != '\0' ) {
741       break;
742     }
743     if( ! str_ischild( tail_stub_ptr->tail->simple_tail, tail_stub_ptr->next->tail->simple_tail) ) {
744       /* the next tail is not a child-node of this tail */
745       break;
746     }
747   }
748   return(tail_stub_ptr);
749 }
750
751 static struct tail *choose_tail(struct group *group, unsigned int position ) {
752   struct tail_stub *first_tail_stub;
753   struct tail_stub *tail_stub_ptr;
754   unsigned int ndx;
755
756   if( group->tails_at_position[position] == NULL ) {
757     /* first_tail_stub == NULL
758      * this does not happen, because every position gets at least one tail of ""
759      * eg, even if the value is NULL.
760      *   /head/1   (null)
761      *   ...simple_tail ""
762      *   ...value NULL
763      * paths without a position ( /head/tail ) are not added to any group
764      * We can't do anything with this, use seq::* or [*] only (no value) */
765     fprintf(stderr,"# choose_tail() %s[%u] first_tail_stub is NULL (internal error)\n", group->head, position);
766     group->chosen_tail_state[position] = NO_CHILD_NODES;
767     return(NULL);
768   }
769
770   first_tail_stub = group->first_tail[position];
771
772   /* First preference - if the first-tail+value is unique, use that */
773   if( first_tail_stub->tail->tail_value_found == 1 ) {
774     group->chosen_tail_state[position] = FIRST_TAIL;
775     return(first_tail_stub->tail);
776   }
777
778   /* Second preference - find a unique tail+value that has only one value for this position and has the tail existing for all other positions */
779   for( tail_stub_ptr=first_tail_stub; tail_stub_ptr!=NULL; tail_stub_ptr=tail_stub_ptr->next) {
780     if( tail_stub_ptr->tail->tail_value_found == 1 ) { /* tail_stub_ptr->tail->value can be NULL, just needs to be unique */
781       int found=1;
782       for( ndx=1; ndx <= group->max_position; ndx++ ) {
783         if( tail_stub_ptr->tail->tail_found_map[ndx] == 0 ) {
784           /* tail does not exist for every position within this group */
785           found=0;
786           break;
787         }
788       }
789       if ( found ) {
790         /* This works only if chosen_tail->simple_tail is the first appearance of simple_tail at this position */
791         struct tail_stub *tail_check_ptr;
792         for( tail_check_ptr=first_tail_stub; tail_check_ptr != tail_stub_ptr; tail_check_ptr=tail_check_ptr->next) {
793           if( strcmp(tail_check_ptr->tail->simple_tail, tail_stub_ptr->tail->simple_tail ) == 0 ) {
794             found=0;
795           }
796         }
797       }
798       if ( found ) {
799         group->chosen_tail_state[position] = CHOSEN_TAIL_START;
800         return(tail_stub_ptr->tail);
801       }
802     } /* if ... tail_value_found == 1 */
803   }
804
805   /* Third preference - first tail is not unique but could make a unique combination with another tail */
806   struct subgroup *subgroup_ptr = find_or_create_subgroup(group, first_tail_stub->tail);
807   for( tail_stub_ptr=first_tail_stub->next; tail_stub_ptr!=NULL; tail_stub_ptr=tail_stub_ptr->next) {
808     /* for each tail at this position (other than the first) */
809     /* Find a tail at this position where:
810      * a) tail+value is unique within this subgroup
811      * b) tail exists at all positions within this subgroup
812      */
813     int found=1;
814     for(ndx=0; subgroup_ptr->matching_positions[ndx] != 0; ndx++ ) {
815       int pos=subgroup_ptr->matching_positions[ndx];
816       if ( pos == position ) continue;
817       if( tail_stub_ptr->tail->tail_value_found_map[pos] != 0 ) {
818         /* tail+value is not unique within this subgroup */
819         found=0;
820         break;
821       }
822       if( tail_stub_ptr->tail->tail_found_map[pos] == 0 ) {
823         /* tail does not exist for every position within this subgroup */
824         found=0;
825         break;
826       }
827     }
828     if ( found ) {
829       /* This works only if chosen_tail->simple_tail is the first appearance of simple_tail at this position */
830       struct tail_stub *tail_check_ptr;
831       for( tail_check_ptr=first_tail_stub; tail_check_ptr != tail_stub_ptr; tail_check_ptr=tail_check_ptr->next) {
832         if( strcmp(tail_check_ptr->tail->simple_tail, tail_stub_ptr->tail->simple_tail ) == 0 ) {
833           found=0;
834         }
835       }
836     }
837     if ( found ) {
838       group->chosen_tail_state[position] = CHOSEN_TAIL_PLUS_FIRST_TAIL_START;
839       return(tail_stub_ptr->tail);
840     }
841   }
842   /* Fourth preference (fallback) - use first_tail PLUS the position with the subgroup */
843   group->chosen_tail_state[position] = FIRST_TAIL_PLUS_POSITION;
844   return(first_tail_stub->tail);
845 }
846
847 /* simple_tail_expr()
848  * given a simple_tail of the form "/path" or ""
849  * return "path" or "."
850  */
851 static const char *simple_tail_expr(char *simple_tail) {
852   if( *simple_tail == '/' ) {
853     /* usual case - .../123/... or /label[123]/... */
854     return(simple_tail+1);
855   } else if ( *simple_tail == '\0' ) {
856     /* path ending in /123 or /label[123] */
857     return(".");
858   } else {
859     /* unreachabe ? */
860     return(simple_tail);
861   }
862 }
863
864 /* Write out the path-segment, up to and including the [ expr ] (if required) */
865 static void output_segment(struct path_segment *ps_ptr, struct augeas_path_value *path_value_seg) {
866   char *last_c, *str;
867   struct group *group;
868   struct tail *chosen_tail;
869   unsigned int position;
870   chosen_tail_state_t     chosen_tail_state;
871   struct tail_stub *first_tail;
872
873   char *value_qq = path_value_seg->value_qq;
874
875   /* print segment possibly followed by * or seq::* */
876   last_c=ps_ptr->segment;
877   for(str=ps_ptr->segment; *str; last_c=str++)  /* find end of string */
878     ;
879   if(*last_c=='/') {
880     /* sequential position .../123 */
881     if ( noseq )
882       printf("%s*", ps_ptr->segment);
883     else
884       printf("%sseq::*", ps_ptr->segment);
885   } else {
886     /* label with a position .../label[123], or no position ... /last */
887     printf("%s", ps_ptr->segment);
888   }
889   group = ps_ptr->group;
890   if( group == NULL ) {
891     /* last segment .../last_tail No position, nothing else to print */
892     return;
893   }
894
895   /* apply "chosen_tail" criteria here */
896   position = ps_ptr->position;
897   chosen_tail = group->chosen_tail[position];
898   if( chosen_tail == NULL ) {
899     /* This should not happen */
900     fprintf(stderr,"chosen_tail==NULL ???\n");
901   }
902
903   first_tail = find_first_tail(group->tails_at_position[position]);
904   chosen_tail_state = group->chosen_tail_state[position];
905
906
907   switch( chosen_tail_state ) {
908     case CHOSEN_TAIL_START:
909       group->chosen_tail_state[position] = CHOSEN_TAIL_WIP;
910       __attribute__ ((fallthrough));   /* drop through */
911     case FIRST_TAIL:
912     case CHOSEN_TAIL_DONE:
913     case FIRST_TAIL_PLUS_POSITION:
914       if ( chosen_tail->value == NULL ) {
915         printf("[%s]", simple_tail_expr(chosen_tail->simple_tail));
916       } else if ( use_regexp ) {
917         printf("[%s=~regexp(%*s)]",
918           simple_tail_expr(chosen_tail->simple_tail),
919           -(group->pretty_width_ct[position]),        /* minimum field width */
920           chosen_tail->value_re
921           );
922       } else {
923         printf("[%s=%*s]",
924           simple_tail_expr(chosen_tail->simple_tail),
925           -(group->pretty_width_ct[position]),        /* minimum field width */
926           chosen_tail->value_qq
927           );
928       }
929       if ( chosen_tail_state == FIRST_TAIL_PLUS_POSITION ) {
930         /* no unique tail+value - duplicate or overlapping positions */
931         printf("[%u]", group->subgroup_position[position] );
932       }
933       break;
934     case CHOSEN_TAIL_WIP:
935       if ( chosen_tail->value == NULL ) {
936         /* theoretically possible - how to test? */
937         printf("[%s or count(%s)=0]",
938           simple_tail_expr(chosen_tail->simple_tail),
939           simple_tail_expr(chosen_tail->simple_tail));
940       } else if ( use_regexp ) {
941         printf("[%s=~regexp(%*s) or count(%s)=0]",
942           simple_tail_expr(chosen_tail->simple_tail),
943           -(group->pretty_width_ct[position]),        /* minimum field width */
944           chosen_tail->value_re,
945           simple_tail_expr(chosen_tail->simple_tail));
946       } else {
947         printf("[%s=%*s or count(%s)=0]",
948           simple_tail_expr(chosen_tail->simple_tail),
949           -(group->pretty_width_ct[position]),        /* minimum field width */
950           chosen_tail->value_qq,
951           simple_tail_expr(chosen_tail->simple_tail));
952       }
953       if ( strcmp(chosen_tail->simple_tail, ps_ptr->simplified_tail) == 0 && strcmp(chosen_tail->value_qq, value_qq) == 0 ) {
954         group->chosen_tail_state[position] = CHOSEN_TAIL_DONE;
955       }
956       break;
957     case CHOSEN_TAIL_PLUS_FIRST_TAIL_START:
958       if ( first_tail->tail->value == NULL && use_regexp ) {
959         /* test with /etc/sudoers */
960         printf("[%s and %s=~regexp(%s)]",
961           simple_tail_expr(first_tail->tail->simple_tail),
962           simple_tail_expr(chosen_tail->simple_tail),
963           chosen_tail->value_re
964           );
965
966       } else if ( first_tail->tail->value == NULL && ! use_regexp ) {
967         /* test with /etc/sudoers */
968         printf("[%s and %s=%s]",
969           simple_tail_expr(first_tail->tail->simple_tail),
970           simple_tail_expr(chosen_tail->simple_tail),
971           chosen_tail->value_qq
972           );
973       } else if ( use_regexp ) {
974         printf("[%s=~regexp(%*s) and %s=~regexp(%s)]",
975           simple_tail_expr(first_tail->tail->simple_tail),
976           -(group->pretty_width_ct[position]),        /* minimum field width */
977           first_tail->tail->value_re,
978           simple_tail_expr(chosen_tail->simple_tail),
979           chosen_tail->value_re );
980       } else {
981         printf( "[%s=%*s and %s=%s]",
982           simple_tail_expr(first_tail->tail->simple_tail),
983           -(group->pretty_width_ct[position]),        /* minimum field width */
984           first_tail->tail->value_qq,
985           simple_tail_expr(chosen_tail->simple_tail),
986           chosen_tail->value_qq );
987       }
988       group->chosen_tail_state[position] = CHOSEN_TAIL_PLUS_FIRST_TAIL_WIP;
989       break;
990     case CHOSEN_TAIL_PLUS_FIRST_TAIL_WIP:
991       if ( first_tail->tail->value == NULL && use_regexp ) {
992         printf("[%s and ( %s=~regexp(%s) or count(%s)=0 )]",
993           simple_tail_expr(first_tail->tail->simple_tail),
994           simple_tail_expr(chosen_tail->simple_tail),
995           chosen_tail->value_re,
996           simple_tail_expr(chosen_tail->simple_tail)
997           );
998       } else if ( first_tail->tail->value == NULL && ! use_regexp ) {
999         printf("[%s and ( %s=%s or count(%s)=0 )]",
1000           simple_tail_expr(first_tail->tail->simple_tail),
1001           simple_tail_expr(chosen_tail->simple_tail),
1002           chosen_tail->value_qq,
1003           simple_tail_expr(chosen_tail->simple_tail)
1004           );
1005       } else if ( use_regexp ) {
1006         printf("[%s=~regexp(%*s) and ( %s=~regexp(%s) or count(%s)=0 ) ]",
1007           simple_tail_expr(first_tail->tail->simple_tail),
1008           -(group->pretty_width_ct[position]),        /* minimum field width */
1009           first_tail->tail->value_re,
1010           simple_tail_expr(chosen_tail->simple_tail),
1011           chosen_tail->value_re,
1012           simple_tail_expr(chosen_tail->simple_tail)
1013           );
1014       } else {
1015         printf("[%s=%*s and ( %s=%s or count(%s)=0 ) ]",
1016           simple_tail_expr(first_tail->tail->simple_tail),
1017           -(group->pretty_width_ct[position]),        /* minimum field width */
1018           first_tail->tail->value_qq,
1019           simple_tail_expr(chosen_tail->simple_tail),
1020           chosen_tail->value_qq,
1021           simple_tail_expr(chosen_tail->simple_tail)
1022           );
1023       }
1024       if ( strcmp(chosen_tail->simple_tail, ps_ptr->simplified_tail) == 0 && strcmp(chosen_tail->value_qq, value_qq) == 0 ) {
1025         group->chosen_tail_state[position] = CHOSEN_TAIL_PLUS_FIRST_TAIL_DONE;
1026       }
1027       break;
1028     case CHOSEN_TAIL_PLUS_FIRST_TAIL_DONE:
1029       if ( first_tail->tail->value == NULL && use_regexp ) {
1030         printf("[%s and %s=~regexp(%s)]",
1031           simple_tail_expr(first_tail->tail->simple_tail),
1032           simple_tail_expr(chosen_tail->simple_tail),
1033           chosen_tail->value_re
1034           );
1035       } else if ( first_tail->tail->value == NULL && ! use_regexp ) {
1036         printf("[%s and %s=%s]",
1037           simple_tail_expr(first_tail->tail->simple_tail),
1038           simple_tail_expr(chosen_tail->simple_tail),
1039           chosen_tail->value_qq
1040           );
1041       } else if ( use_regexp ) {
1042         printf("[%s=~regexp(%*s) and %s=~regexp(%s)]",
1043           simple_tail_expr(first_tail->tail->simple_tail),
1044           -(group->pretty_width_ct[position]),        /* minimum field width */
1045           first_tail->tail->value_re,
1046           simple_tail_expr(chosen_tail->simple_tail),
1047           chosen_tail->value_re
1048           );
1049       } else {
1050         printf("[%s=%*s and %s=%s]",
1051           simple_tail_expr(first_tail->tail->simple_tail),
1052           -(group->pretty_width_ct[position]),        /* minimum field width */
1053           first_tail->tail->value_qq,
1054           simple_tail_expr(chosen_tail->simple_tail),
1055           chosen_tail->value_qq
1056           );
1057       }
1058       break;
1059     case NO_CHILD_NODES:
1060       if(*last_c!='/') {
1061         printf("[*]"); /* /head/label with no child nodes */
1062       }
1063       break;
1064     default:
1065       /* unreachable */
1066       printf("[ %s=%s ]", simple_tail_expr(chosen_tail->simple_tail),chosen_tail->value_qq);
1067   }
1068 }
1069
1070 static void output_path(struct augeas_path_value *path_value_seg) {
1071   struct path_segment *ps_ptr;
1072   printf("set ");
1073   for( ps_ptr=path_value_seg->segments; ps_ptr != NULL; ps_ptr=ps_ptr->next) {
1074     output_segment(ps_ptr, path_value_seg);
1075   }
1076   if( path_value_seg->value_qq != NULL ) {
1077     printf(" %s\n", path_value_seg->value_qq);
1078   } else {
1079     printf("\n");
1080   }
1081 }
1082
1083 static void output(void) {
1084   int ndx;   /* index to matches() */
1085   struct augeas_path_value  *path_value_seg;
1086   char *value;
1087   for( ndx=0; ndx<num_matched; ndx++) {
1088     path_value_seg = all_augeas_paths[ndx];
1089     value = path_value_seg->value;
1090     if( value != NULL && *value == '\0' )
1091       value = NULL;
1092     if(verbose) {
1093       if ( value == NULL )
1094         fprintf(stdout,"#   %s\n", path_value_seg->path);
1095       else
1096         fprintf(stdout,"#   %s  %s\n", path_value_seg->path, path_value_seg->value_qq);
1097     }
1098     /* weed out null paths here, eg
1099      *   /head/123 (null)
1100      *   /head/123/tail (null)
1101      *   /head/path (null)
1102      * ie. if value==NULL AND this node has child nodes
1103      * does not apply if there is no
1104      *   /head/path/tail
1105      */
1106     if ( value == NULL && ndx < num_matched-1 ) {
1107       if(str_ischild(all_augeas_paths[ndx]->path, all_augeas_paths[ndx+1]->path)) {
1108         continue;
1109       }
1110     }
1111     output_path(path_value_seg);
1112     if( pretty ) {
1113       if( ndx < num_matched-1 ) {
1114         /* fixme - do we just need to compare the position? */
1115         struct group *this_group, *next_group;
1116         this_group = all_augeas_paths[ndx]->segments->group;
1117         next_group = all_augeas_paths[ndx+1]->segments->group;
1118         if ( this_group != next_group
1119           || ( this_group != NULL && all_augeas_paths[ndx]->segments->position != all_augeas_paths[ndx+1]->segments->position )
1120           ) {
1121           /* New group, put in a newline for visual seperation */
1122           printf("\n");
1123         }
1124       }
1125     }
1126   }
1127 }
1128
1129 static void choose_re_width(struct group *group) {
1130   unsigned int position;
1131   /* For each position, compare the value of chosen_tail with
1132    * all other matching simple_tails in the group, to find the minimum
1133    * required length of the RE
1134    */
1135   for(position=1; position<=group->max_position; position++) {
1136     unsigned int max_re_width_ct=0;
1137     unsigned int max_re_width_ft=0;
1138     unsigned int re_width;
1139     struct tail *chosen_tail = group->chosen_tail[position];
1140     struct tail *first_tail  = group->first_tail[position]->tail;
1141     struct tail *tail_ptr;
1142     for(tail_ptr = group->all_tails; tail_ptr != NULL; tail_ptr = tail_ptr->next) {
1143       if ( tail_ptr != chosen_tail ) {
1144         if( strcmp(tail_ptr->simple_tail, chosen_tail->simple_tail) == 0 ) {
1145           value_cmp(tail_ptr->value, chosen_tail->value, &re_width);
1146           if( re_width + 1 > max_re_width_ct ) {
1147             max_re_width_ct = re_width+1;
1148           }
1149         }
1150       }
1151       if( group->chosen_tail_state[position] == CHOSEN_TAIL_PLUS_FIRST_TAIL_START && chosen_tail != first_tail ) {
1152         /* 3rd preference, we need an re_width for both the chosen_tail and the first_tail */
1153         /* In theory, the first_tail of this position may be present in other positions, but may not be first */
1154         if ( tail_ptr != first_tail ) {
1155           if( strcmp(tail_ptr->simple_tail, first_tail->simple_tail) == 0 ) {
1156             value_cmp(tail_ptr->value, first_tail->value, &re_width);
1157             if( re_width + 1 > max_re_width_ft ) {
1158               max_re_width_ft = re_width+1;
1159             }
1160           }
1161         }
1162       } /* If 3rd preference */
1163     } /* for each tail in group->all_tails */
1164     max_re_width_ct = MAX(max_re_width_ct,use_regexp);
1165     max_re_width_ft = MAX(max_re_width_ft,use_regexp);
1166     group->re_width_ct[position] = max_re_width_ct;
1167     group->re_width_ft[position] = max_re_width_ft;
1168     chosen_tail->value_re = regexp_value( chosen_tail->value, max_re_width_ct );
1169     if ( group->chosen_tail_state[position] == CHOSEN_TAIL_PLUS_FIRST_TAIL_START ) {
1170       /* otherwise, max_re_width_ft=0, and we don't need first_tail->value_re at all */
1171       if ( chosen_tail == first_tail ) {
1172         /* if chosen_tail == first_tail, we would overwrite chosen_tail->value_re */
1173         first_tail->value_re = chosen_tail->value_re;
1174       } else {
1175         first_tail->value_re  = regexp_value( first_tail->value,  max_re_width_ft );
1176       }
1177     }
1178   } /* for position 1..max_position */
1179 }
1180
1181 static void choose_pretty_width(struct group *group) {
1182   unsigned int position;
1183   int value_len;
1184   for(position=1; position<=group->max_position; position++) {
1185     struct tail *pretty_tail;
1186     if( group->chosen_tail_state[position] == CHOSEN_TAIL_PLUS_FIRST_TAIL_START ) {
1187       pretty_tail = group->first_tail[position]->tail;
1188     } else {
1189       pretty_tail = group->chosen_tail[position];
1190     }
1191     if( use_regexp ) {
1192       value_len = pretty_tail->value_re == NULL ? 0 : strlen(pretty_tail->value_re);
1193     } else {
1194       value_len = pretty_tail->value_qq == NULL ? 0 : strlen(pretty_tail->value_qq);
1195     }
1196     group->pretty_width_ct[position] = value_len;
1197   }
1198   /* find the highest pretty_width_ct for each unique chosen_tail->simple_tail in the group */
1199   for(position=1; position<=group->max_position; position++) {
1200     unsigned int max_width=0;
1201     unsigned int pos_search;
1202     char *chosen_simple_tail = group->chosen_tail[position]->simple_tail;
1203     for(pos_search=position; pos_search <= group->max_position; pos_search++) {
1204       if(strcmp( group->chosen_tail[pos_search]->simple_tail, chosen_simple_tail) == 0 ) {
1205         value_len = group->pretty_width_ct[pos_search];
1206         if( value_len <= MAX_PRETTY_WIDTH ) {
1207           /* If we're already over the limit, do not pad everything else out too */
1208           max_width = MAX(max_width, value_len);
1209         }
1210         group->pretty_width_ct[pos_search] = max_width; /* so we can start at position+1 */
1211       }
1212     }
1213     max_width = MIN(max_width,MAX_PRETTY_WIDTH);
1214     group->pretty_width_ct[position] = max_width;
1215   } /* for position 1..max_position */
1216 }
1217
1218 /* populate group->chosen_tail[] and group->first_tail[] arrays */
1219 /* Also call choose_re_width() and choose_pretty_width() to populate group->re_width_ct[] ..->re_width_ft[] and ..->pretty_width_ft[] */
1220 static void choose_all_tails(void) {
1221   int ndx;   /* index to all_groups() */
1222   unsigned int position;
1223   struct group *group;
1224   for(ndx=0; ndx<num_groups; ndx++) {
1225     group=all_groups[ndx];
1226     for(position=1; position<=group->max_position; position++) {
1227       /* find_first_tail() - find first "significant" tail
1228        * populate group->first_tail[] before calling choose_tail()
1229        * We need these values for find_or_create_subgroup()
1230        */
1231       group->first_tail[position] = find_first_tail(group->tails_at_position[position]);
1232     }
1233     for(position=1; position<=group->max_position; position++) {
1234       group->chosen_tail[position] = choose_tail(group, position);
1235     }
1236     if( use_regexp ) {
1237       choose_re_width(group);
1238     }
1239     if( pretty ) {
1240       choose_pretty_width(group);
1241     }
1242   }
1243 }
1244
1245 /* Create a quoted value from the value, using single quotes if possible
1246  * Quotes are not strictly required for the value, but they _are_ required
1247  * for values within the path-expressions
1248  */
1249 static char *quote_value(char *value) {
1250   char *s, *t, *value_qq, quote;
1251   int len=0;
1252   int has_q=0;
1253   int has_qq=0;
1254   int has_special=0;
1255   int has_nl=0;
1256   int new_len;
1257   if(value==NULL)
1258     return(NULL);
1259   for(s = value, len=0; *s; s++, len++) {
1260     switch(*s) {
1261       case '"': has_qq++; break;
1262       case '\'': has_q++; break;
1263       case ' ':
1264       case '/':
1265       case '*':
1266       case '.':
1267       case ':':
1268         has_special++; break;
1269       case '\n':
1270       case '\t':
1271       case '\\':
1272         has_nl++; break;
1273       default:
1274         ;
1275     }
1276   }
1277   if( has_q == 0 ) {
1278     /* Normal case, no single-quotes within the value */
1279     new_len = len+2+has_nl;
1280     quote='\'';
1281   } else if ( has_qq == 0 ) {
1282     new_len = len+2+has_nl;
1283     quote='"';
1284   } else {
1285     /* This needs a bugfix in augeas */
1286     new_len = len+2+has_q+has_nl;
1287     quote='\'';
1288   }
1289   value_qq = malloc( sizeof(char) * ++new_len); /* don't forget the \0 */
1290   CHECK_OOM( ! value_qq, exit_oom, "in quote_value()");
1291
1292   t=value_qq;
1293   *t++ = quote;
1294   for(s = value; *s; s++, t++) {
1295     if ( *s == quote ) {
1296       *t++ = '\\';
1297       *t =quote;
1298       continue;
1299     }  else if ( *s == '\n' ) {
1300       *t++ = '\\';
1301       *t = 'n';
1302       continue;
1303     }  else if ( *s == '\t' ) {
1304       *t++ = '\\';
1305       *t = 't';
1306       continue;
1307     }  else if ( *s == '\\' ) {
1308       *t++ = '\\';
1309       *t = '\\';
1310       continue;
1311     }
1312     *t = *s;
1313   }
1314   *t++ = quote;
1315   *t++ = '\0';
1316   return(value_qq);
1317 }
1318
1319 /* Create a quoted regular expression from the value, using single quotes if possible
1320  */
1321 static char *regexp_value(char *value, int max_len) {
1322   char *s, *t, *value_re, quote;
1323   int len=0;
1324   int has_q=0;
1325   int has_qq=0;
1326   int has_special=0;
1327   int has_nl=0;
1328   int new_len;
1329   if(value==NULL)
1330     return(NULL);
1331   for(s = value, len=0; *s; s++, len++) {
1332     switch(*s) {
1333       case '"':  has_qq++; break;
1334       case '\'': has_q++; break;
1335       case '*':
1336       case '?':
1337       case '.':
1338       case '[':
1339       case ']':
1340       case '(':
1341       case ')':
1342       case '^':
1343       case '$':
1344       case '|':
1345         has_special++; break;
1346       case '\n':
1347       case '\t':
1348         has_nl++; break;
1349       case '\\':
1350         has_special+=2; break;
1351       default:
1352         ;
1353     }
1354   }
1355   len++;  /* don't forget the \0 */
1356   if( has_q == 0 ) {
1357     /* Normal case, no single-quotes within the value */
1358     new_len = len+2+has_nl+has_special*2;
1359     quote='\'';
1360   } else if ( has_qq == 0 ) {
1361     new_len = len+2+has_nl+has_special*2;
1362     quote='"';
1363   } else {
1364     /* This needs a bugfix in augeas */
1365     new_len = len+2+has_q+has_nl+has_special*2;
1366     quote='\'';
1367   }
1368   value_re = malloc( sizeof(char) * new_len);
1369   CHECK_OOM( ! value_re, exit_oom, "in regexp_value()");
1370
1371   t=value_re;
1372   *t++ = quote;
1373   for(s = value; *s; s++, t++) {
1374     if ( *s == quote ) {
1375       *t++ = '\\';
1376       *t =quote;
1377       continue;
1378     }  else if ( *s == '\n' ) {
1379       *t++ = '\\';
1380       *t = 'n';
1381       continue;
1382     }  else if ( *s == '\t' ) {
1383       *t++ = '\\';
1384       *t = 't';
1385       continue;
1386     }  else if ( *s == '\\' || *s == ']' ) {
1387       *t = '.';
1388       continue;
1389     }
1390     switch(*s) {
1391       /* Special handling for ] */
1392       case ']':
1393         *t = '.'; continue;
1394       case '[':
1395         *t++ = '\\';
1396         break;
1397       case '*':
1398       case '?':
1399       case '.':
1400       case '(':
1401       case ')':
1402       case '^':
1403       case '$':
1404       case '|':
1405         *t++ = '\\';
1406         *t++ = '\\';
1407         break;
1408       case '\\':
1409       case '\n':
1410       case '\t':
1411         break;  /* already dealt with above */
1412       default:
1413         ;
1414     }
1415     *t = *s;
1416     if( ( s - value ) + 1 >= max_len  && *(s+1)!='\0' && *(s+2)!='\0' && *(s+3)!='\0' ) {
1417       /* don't append .* if there are only one or two chars left in the string */
1418       t++;
1419       *t++='.';
1420       *t++='*';
1421       break;
1422     }
1423   }
1424   *t++ = quote;
1425   *t++ = '\0';
1426   return(value_re);
1427 }
1428
1429 static void usage(const char *progname) {
1430   if(progname == NULL)
1431     progname = "augprint";
1432   fprintf(stdout, "Usage:\n\n%s [--target=realname] [--lens=Lensname] [--pretty] [--regexp[=n]] [--noseq] /path/filename\n\n",progname);
1433   fprintf(stdout, "  -t, --target  ... use this as the filename in the output set-commands\n");
1434   fprintf(stdout, "                    this filename also implies the default lens to use\n");
1435   fprintf(stdout, "  -l, --lens    ... override the default lens and target and use this one\n");
1436   fprintf(stdout, "  -p, --pretty  ... make the output more readable\n");
1437   fprintf(stdout, "  -r, --regexp  ... use regexp() in path-expressions instead of absolute values\n");
1438   fprintf(stdout, "                    if followed by a number, this is the minimum length of the regexp to use\n");
1439   fprintf(stdout, "  -s, --noseq   ... use * instead of seq::* (useful for compatability with augeas < 1.13.0)\n");
1440   fprintf(stdout, "  -h, --help    ... this message\n");
1441   fprintf(stdout, "  -V, --version ... print augeas version information and exit.\n");
1442   fprintf(stdout, "  /path/filename  ... full pathname to the file being analysed (required)\n\n");
1443   fprintf(stdout, "%s will generate a script of augtool set-commands suitable for rebuilding the file specified\n", progname);
1444   fprintf(stdout, "If --target is specified, then the lens associated with the target will be use to parse the file\n");
1445   fprintf(stdout, "If --lens is specified, then the given lens will be used, overriding the default, and --target\n\n");
1446   fprintf(stdout, "Examples:\n");
1447   fprintf(stdout, "\t%s --target=/etc/squid/squid.conf /etc/squid/squid.conf.new\n", progname);
1448   fprintf(stdout, "\t\tOutput an augtool script for re-creating /etc/squid/squid.conf.new at /etc/squid/squid.conf\n\n");
1449   fprintf(stdout, "\t%s --lens=simplelines /etc/hosts\n", progname);
1450   fprintf(stdout, "\t\tOutput an augtool script for /etc/hosts using the lens simplelines instead of the default for /etc/hosts\n\n");
1451   fprintf(stdout, "\t%s --regexp=12 /etc/hosts\n", progname);
1452   fprintf(stdout, "\t\tUse regular expressions in the resulting augtool script, each being at least 12 chars long\n");
1453   fprintf(stdout, "\t\tIf the value is less than 12 chars, use the whole value in the expression\n");
1454   fprintf(stdout, "\t\tRegular expressions longer than 12 chars may be generated, if the 12 char regexp\n");
1455   fprintf(stdout, "\t\twould be match more than one value\n");
1456 }
1457
1458 static void print_version_info(const char *progname) {
1459     const char *version;
1460     int r;
1461
1462     r = aug_get(aug, "/augeas/version", &version);
1463     if (r != 1)
1464         goto error;
1465
1466     fprintf(stderr, "%s %s <https://augeas.net/>\n", progname, version);
1467     return;
1468  error:
1469     fprintf(stderr, "Something went terribly wrong internally - please file a bug\n");
1470 }
1471
1472 int main(int argc, char **argv) {
1473   int opt;
1474   char *augeas_root = getenv("AUGEAS_ROOT");
1475   char *inputfile = NULL;
1476   char *target_file = NULL;
1477   char *program_name = basename(argv[0]);
1478   char *value;  /* result of aug_get() */
1479
1480   while (1) {
1481     int option_index = 0;
1482     static struct option long_options[] = {
1483         {"help",    no_argument,       &help,          1 },
1484         {"version", no_argument,       &print_version, 1 },
1485         {"verbose", no_argument,       &verbose,       1 },
1486         {"debug",   no_argument,       &debug,         1 },
1487         {"lens",    required_argument, 0,              0 },
1488         {"noseq",   no_argument,       &noseq,         1 },
1489         {"seq",     no_argument,       &noseq,         0 },
1490         {"target",  required_argument, 0,              0 },
1491         {"pretty",  no_argument,       &pretty,        1 },
1492         {"regexp",  optional_argument, &use_regexp,    1 },
1493         {0,         0,                 0,              0 } /* marker for end of data */
1494       };
1495
1496     opt = getopt_long(argc, argv, "vdhVl:sSr::pt:", long_options, &option_index);
1497     if (opt == -1)
1498        break;
1499
1500     switch (opt) {
1501       case 0:
1502         if(debug) {
1503           fprintf(stderr,"option %d %s", option_index, long_options[option_index].name);
1504           if(optarg) fprintf(stderr," with arg %s", optarg);
1505           fprintf(stderr,"\n");
1506         }
1507         if (strcmp(long_options[option_index].name, "lens") == 0 ) {
1508           lens = optarg;
1509           flags |= AUG_NO_MODL_AUTOLOAD;
1510         } else if (strcmp(long_options[option_index].name, "target") == 0) {
1511           target_file = optarg;
1512           if( *target_file != '/' ) {
1513             fprintf(stderr,"%s: Error: target \"%s\" must be an absolute path\neg.\n\t--target=/etc/%s\n", program_name, target_file, target_file);
1514             exit(1);
1515           }
1516         } else if (strcmp(long_options[option_index].name, "regexp") == 0) {
1517           if(optarg) {
1518             int optarg_int = strtol(optarg, NULL, 0);
1519             if(optarg_int > 0)
1520               use_regexp = optarg_int;
1521             /* else use the default 1 set by getopt() */
1522           } else {
1523             use_regexp = 8;
1524           }
1525         }
1526         break;
1527
1528       case 'h':
1529         help=1;
1530         break;
1531       case 'V':
1532         print_version=1;
1533         break;
1534       case 'v':
1535         verbose=1;
1536         break;
1537       case 'd':
1538         debug=1;
1539         fprintf(stderr,"option d with value '%s'\n", optarg);
1540         break;
1541       case 'S':
1542         noseq=0;
1543         break;
1544       case 's':
1545         noseq=1;
1546         break;
1547       case 'l':
1548         lens = optarg;
1549         flags |= AUG_NO_MODL_AUTOLOAD;
1550         break;
1551       case 't':
1552         target_file = optarg;
1553         if( *target_file != '/' ) {
1554           fprintf(stderr,"%s: Error: target \"%s\" must be an absolute path\neg.\n\t--target=/etc/%s\n", program_name, target_file, target_file);
1555           exit(1);
1556         }
1557         break;
1558       case 'r':
1559         if(optarg) {
1560           int optarg_int = strtol(optarg, NULL, 0);
1561           use_regexp = optarg_int > 0 ? optarg_int : 8;
1562         }
1563         use_regexp = use_regexp ? use_regexp : 8;
1564         break;
1565
1566       case '?':    /* unknown option */
1567         break;
1568
1569       default:
1570         fprintf(stderr,"?? getopt returned character code 0x%x ??\n", opt);
1571     }
1572   }
1573
1574   if( help ) {
1575     usage(program_name);
1576     exit(0);
1577   }
1578   if( print_version ) {
1579     aug = aug_init(NULL, loadpath, flags|AUG_NO_ERR_CLOSE|AUG_NO_LOAD|AUG_NO_MODL_AUTOLOAD);
1580     print_version_info(program_name);
1581     exit(0);
1582   }
1583   if (optind == argc-1) {
1584     /* We need exactly one non-option argument - the input filename */
1585     if( *argv[optind] == '/' ) {
1586       /* filename is an absolute path - use it verbatim */
1587       inputfile = argv[optind];
1588     } else {
1589       /* filename is a relative path - prepend the current PWD */
1590       int result = asprintf(&inputfile, "%s/%s", getenv("PWD"), argv[optind] );
1591       CHECK_OOM( result < 0, exit_oom, NULL);
1592     }
1593     if(debug) {
1594       fprintf(stderr,"non-option ARGV-elements: ");
1595       while (optind < argc)
1596         fprintf(stderr,"%s ", argv[optind++]);
1597       fprintf(stderr,"\n");
1598     }
1599   } else if( optind == argc ) {
1600     /* No non-option args given (missing inputfile) */
1601     fprintf(stderr,"Missing command-line argument\nPlease specify a filename to read eg.\n\t%s %s\n", program_name, "/etc/hosts");
1602     fprintf(stderr, "\nTry '%s --help' for more information.\n", program_name);
1603     exit(1);
1604   } else {
1605     /* Too many args - we only want one */
1606     fprintf(stderr,"Too many command-line arguments\nPlease specify only one filename to read eg.\n\t%s %s\n", program_name, "/etc/hosts");
1607     fprintf(stderr, "\nTry '%s --help' for more information.\n", program_name);
1608     exit(1);
1609   }
1610
1611   cleanup_filepath(inputfile);
1612   char *inputfile_real;
1613   if( augeas_root != NULL ) {
1614     int result = asprintf(&inputfile_real, "%s/%s", augeas_root, inputfile );
1615     if ( result == -1 ) {
1616       perror(program_name);
1617       exit(1);
1618     }
1619   } else {
1620     inputfile_real = inputfile;
1621   }
1622   if( access(inputfile_real, F_OK|R_OK) ) {
1623     fprintf(stderr, "%s: Could not access file %s: %s\n", program_name, inputfile_real, strerror(errno));
1624     exit(1);
1625   }
1626
1627   aug = aug_init(NULL, loadpath, flags|AUG_NO_ERR_CLOSE|AUG_NO_LOAD);
1628
1629   if ( target_file != NULL && lens == NULL ) {
1630     /* Infer the lens which applies to the --target_file option */
1631     lens = find_lens_for_path(target_file);
1632   }
1633
1634   if ( lens != NULL ) {
1635     /* Explict lens given, or inferred from --target */
1636     char *filename;
1637     if ( aug_transform(aug, lens, inputfile, 0) != 0 ) {
1638       fprintf(stderr, "%s\n", aug_error_details(aug));
1639       exit(1);
1640     }
1641     if ( target_file ) {
1642       filename = target_file;
1643     } else {
1644       filename = inputfile;
1645     }
1646     printf("setm /augeas/load/*[incl='%s' and label() != '%s']/excl '%s'\n", filename, lens, filename);
1647     printf("transform %s incl %s\n", lens, filename);
1648     printf("load-file %s\n", filename);
1649
1650   } else {
1651     /* --lens not specified, print the default lens as a comment if --verbose specified */
1652     if( verbose ) {
1653       char *default_lens;
1654       default_lens = find_lens_for_path( inputfile );
1655       printf("# Using default lens: %s\n# transform %s incl %s\n", default_lens, default_lens, inputfile);
1656     }
1657   }
1658
1659   if ( aug_load_file(aug, inputfile) != 0 || aug_error_details(aug) != NULL ) {
1660     const char *msg;
1661     fprintf(stderr, "%s: Failed to load file %s\n", program_name, inputfile);
1662     msg = aug_error_details(aug);
1663     if(msg) {
1664       fprintf(stderr,"%s\n",msg);
1665     } else {
1666       msg = aug_error_message(aug);
1667       if(msg)
1668         fprintf(stderr,"%s\n",msg);
1669       msg = aug_error_minor_message(aug);
1670       if(msg)
1671         fprintf(stderr,"%s\n",msg);
1672     }
1673     exit(1);
1674   }
1675
1676   if ( target_file ) {
1677     /* Rename the tree from inputfile to target_file, if specified */
1678     move_tree(inputfile, target_file);
1679   }
1680
1681   /* There is a subtle difference between "/files//(star)" and "/files/descendant::(star)" in the order that matches appear */
1682   /* descendant::* is better suited, as it allows us to prune out intermediate nodes with null values (directory-like nodes) */
1683   /* These would be created implicity by "set" */
1684   num_matched = aug_match(aug, "/files/descendant::*", &all_matches);
1685   if( num_matched == 0 ) {
1686     if( lens == NULL )
1687       lens = find_lens_for_path(inputfile);
1688     fprintf(stderr,"%s: Failed to parse file %s using lens %s\n", program_name, inputfile, lens);
1689     exit(1);
1690   }
1691   all_augeas_paths = (struct augeas_path_value **) malloc( sizeof(struct augeas_path_value *) * num_matched);
1692   CHECK_OOM( all_augeas_paths == NULL, exit_oom, NULL);
1693
1694   for (int ndx=0; ndx < num_matched; ndx++) {
1695     all_augeas_paths[ndx] = (struct augeas_path_value *) malloc( sizeof(struct augeas_path_value));
1696     CHECK_OOM( all_augeas_paths[ndx] == NULL, exit_oom, NULL);
1697     all_augeas_paths[ndx]->path = all_matches[ndx];
1698     aug_get(aug, all_matches[ndx], (const char **) &value );
1699     all_augeas_paths[ndx]->value    = value;
1700     all_augeas_paths[ndx]->value_qq = quote_value(value);
1701     all_augeas_paths[ndx]->segments = split_path(all_augeas_paths[ndx]);
1702   }
1703   choose_all_tails();
1704   output();
1705
1706   exit(0);
1707 }
1708