1 /* vim: expandtab:softtabstop=2:tabstop=2:shiftwidth=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.
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.
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/>.
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
23 * Where augeas print may produce a set of paths and values like this:
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
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
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
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
48 * /files/some/path/label[1]/tail_a value_1a
49 * `--------------------' \ `-----' `------'
50 * `--- head \ \ `--- value
54 * For more complex paths
55 * /files/some/path/1/segement/label[1]/tail_a value_1a
56 * `----------------------'
59 * /segement/label/tail_a
60 * `--------------------'
67 #include <stdlib.h> /* for exit, strtoul */
72 #include <libgen.h> /* for basename() on FreeBSD and MacOS */
73 #include <sys/param.h> /* for MIN() MAX() */
77 #define CHECK_OOM(condition, action, arg) \
85 #define MAX_PRETTY_WIDTH 30
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 */
95 static int out_of_memory=0;
101 static int print_version=0;
102 static int use_regexp=0;
103 static char *lens = NULL;
104 static char *loadpath = NULL;
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);
113 static void exit_oom(const char *msg) {
114 fprintf(stderr, "Out of memory");
116 fprintf(stderr, " %s\n", msg);
118 fprintf(stderr, "\n");
123 /* Remove /./ and /../ components from path
124 * because they just don't work with augeas
126 static void cleanup_filepath(char *path) {
127 char *to=path, *from=path;
130 if( *(from+1) == '/' ) {
131 /* // skip over 2nd / */
134 } else if( *(from+1) == '.' ) {
135 if( *(from+2) == '/' ) {
136 /* /./ skip 2 spaces */
139 } else if ( *(from+2) == '.' && *(from+3) == '/' ) {
140 /* /../ rewind to previous / */
142 while( to > path && *(--to) != '/' )
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;
158 filename_tail = filename;
159 for (char *s1 = filename; *s1; s1++ ) {
161 filename_tail = s1+1;
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);
167 fprintf(stderr,"path expr: %s\n",aug_load_path);
168 aug_print(aug, stderr, aug_load_path);
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);
175 lens = matching_lenses[0] + 13; /* skip over /augeas/load */
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]);
182 fprintf(stderr, "Warning: multiple lenses apply to target %s - using %s\n", filename, lens);
189 static void move_tree(char *inputfile, char *target_file) {
190 char *files_inputfile;
191 char *files_targetfile;
197 result = asprintf(&files_inputfile, "/files%s", inputfile );
198 CHECK_OOM( result < 0, exit_oom, NULL);
200 result = asprintf(&files_targetfile, "/files%s", target_file );
201 CHECK_OOM( result < 0, exit_oom, NULL);
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;
207 /* dirname(prune_path), without copying anything */
208 for( s=last=dangling_path; *s; s++ ) {
214 result = asprintf(&aug_rm_path, "%s[count(*)=0 and .='']", dangling_path );
215 CHECK_OOM( result < 0, exit_oom, NULL);
217 removed = aug_rm(aug, aug_rm_path);
220 } while ( removed == 1 );
222 free(files_inputfile);
223 free(files_targetfile);
226 /* ----- split_path() str_next_pos() str_simplified_tail() add_segment_to_group() ----- */
228 * Break up a path like this
229 * /head/label_a[123]/middle/label_b[456]/tail
231 * into (struct path_segment) segments like this
233 * head = "/head/label_a"
234 * segement = "/head/label_a"
236 * simplified_tail = "/middle/label_b/tail"
238 * head = "/head/label_a[123]/middle/label_b"
239 * segement = "/middle/label_b"
241 * simplified_tail = "/tail"
243 * head = "/head/label_a[123]/middle/label_b[456]/tail"
245 * position = UINT_MAX (-1)
246 * simplified_tail = ""
248 * If label_b is absent, use seq::* or * instead in the simplified tail, head is unaffected
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;
257 char *path_seg_start=path;
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");
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);
276 this_segment->group = NULL;
279 return(first_segment);
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
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
297 static char *str_next_pos(char *start, char **head_end, unsigned int *pos) {
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 == ']' ) {
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 == '/' ) {
326 static char *str_simplified_tail(char *tail_orig) {
329 char *from, *to, *scan;
331 /* first work out how much space we will need to allocate */
334 if( *tail == '[' && *(tail+1) >= '0' && *(tail+1) <= '9' ) {
335 /* Look for matching ']' */
338 while (*scan >= '0' && *scan <= '9')
344 } else if ( *tail == '/' && *(tail+1) >= '0' && *(tail+1) <= '9' ) {
345 /* Look for next '/' or '\0' */
348 while (*scan >= '0' && *scan <= '9')
350 if(*scan == '/' || *scan == '\0' ) {
352 tail_len += 7; /* allow for /seq::* */
359 simple = (char *) malloc( sizeof(char) * (tail_len+1));
360 CHECK_OOM( ! simple, exit_oom, "allocating simple_tail in str_simplified_tail()");
365 if( *from == '[' && *(from+1) >= '0' && *(from+1) <= '9' ) {
366 /* skip over [123] */
369 while (*scan >= '0' && *scan <= '9')
375 } else if ( *from == '/' && *(from+1) >= '0' && *(from+1) <= '9' ) {
376 /* replace /123 with /seq::* */
379 while (*scan >= '0' && *scan <= '9')
381 if(*scan == '/' || *scan == '\0' ) {
387 strcpy(to,"/seq::*");
388 to += 7; /* len("/seq::*") */
393 *to++ = *from++; /* copy */
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)
405 static int value_cmp(char *v1, char *v2, unsigned int *matched) {
407 if( v1 == NULL && v2 == NULL ) {
411 if( v1 == NULL || v2 == NULL ) {
419 /* Compare values, allowing for the fact that ']' is replaced with '.' */
420 while( *s1 || *s2 ) {
422 if( *s1 =='\0' || *s2 == '\0')
424 if( *s1 != ']' && *s2 != ']' )
427 s1++; s2++; (*matched)++;
431 while( *s1 == *s2 ) {
435 s1++; s2++; (*matched)++;
439 return(1); /* unreachable */
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
446 static struct group *find_or_create_group(char *head) {
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];
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()");
465 all_groups=all_groups_realloc;
467 /* Create new group */
468 group = malloc(sizeof(struct group));
469 CHECK_OOM( ! group, exit_oom, "allocating struct group in find_or_create_group()");
471 all_groups[num_groups++] = group;
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;
484 group->pretty_width_ct = NULL;
486 group->re_width_ct = NULL;
487 group->re_width_ft = NULL;
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
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 */
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;
519 all_tails_end=&tail->next;
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()");
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()");
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()");
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;
539 for( unsigned int ndx=0; ndx<=group->max_position; ndx++ ) {
540 tail->tail_found_map[ndx] = found_tail->tail_found_map[ndx];
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;
550 *all_tails_end = tail;
553 return(found_tail_value);
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;
561 for( tail_stub_pp=&(group->tails_at_position[position]); *tail_stub_pp != NULL; tail_stub_pp=&(*tail_stub_pp)->next ) {
563 *tail_stub_pp = malloc(sizeof(struct tail_stub));
564 CHECK_OOM( ! *tail_stub_pp, exit_oom, "in append_tail_stub()");
566 (*tail_stub_pp)->tail = tail;
567 (*tail_stub_pp)->next = NULL;
570 /* Grow memory structures within the group record and associated tail records
571 * to accommodate additional positions
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;
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;
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()");
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;
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 */
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()");
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;
629 tail->tail_found_map = tail_found_map_realloc;
630 tail->tail_value_found_map = tail_value_found_map_realloc;
632 group->position_array_size = new_size;
636 static void add_segment_to_group(struct path_segment *path_seg, struct augeas_path_value *path_value) {
637 struct group *group = NULL;
639 group = find_or_create_group(path_seg->head);
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);
650 tail = find_or_create_tail(group, path_seg, path_value);
652 /* Append a tail_stub record to the linked list @ group->tails_at_position[position] */
653 append_tail_stub(group, tail, path_seg->position);
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
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 ) {
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()");
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()");
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()");
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 */
701 subgroup_ptr->matching_positions[ndx] = 0; /* 0 = end of list */
702 return(subgroup_ptr);
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
710 static int str_ischild(char *parent, char *child) {
712 if( *parent != *child ) {
718 if( *child == '/' ) {
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:
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
734 * then head/123/tail/child is significant, and that becomes the first_tail
736 static struct tail_stub *find_first_tail(struct tail_stub *tail_stub_ptr) {
737 if( tail_stub_ptr == 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' ) {
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 */
748 return(tail_stub_ptr);
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;
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.
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;
770 first_tail_stub = group->first_tail[position];
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);
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 */
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 */
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 ) {
799 group->chosen_tail_state[position] = CHOSEN_TAIL_START;
800 return(tail_stub_ptr->tail);
802 } /* if ... tail_value_found == 1 */
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
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 */
822 if( tail_stub_ptr->tail->tail_found_map[pos] == 0 ) {
823 /* tail does not exist for every position within this subgroup */
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 ) {
838 group->chosen_tail_state[position] = CHOSEN_TAIL_PLUS_FIRST_TAIL_START;
839 return(tail_stub_ptr->tail);
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);
847 /* simple_tail_expr()
848 * given a simple_tail of the form "/path" or ""
849 * return "path" or "."
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] */
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) {
868 struct tail *chosen_tail;
869 unsigned int position;
870 chosen_tail_state_t chosen_tail_state;
871 struct tail_stub *first_tail;
873 char *value_qq = path_value_seg->value_qq;
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 */
880 /* sequential position .../123 */
882 printf("%s*", ps_ptr->segment);
884 printf("%sseq::*", ps_ptr->segment);
886 /* label with a position .../label[123], or no position ... /last */
887 printf("%s", ps_ptr->segment);
889 group = ps_ptr->group;
890 if( group == NULL ) {
891 /* last segment .../last_tail No position, nothing else to print */
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");
903 first_tail = find_first_tail(group->tails_at_position[position]);
904 chosen_tail_state = group->chosen_tail_state[position];
907 switch( chosen_tail_state ) {
908 case CHOSEN_TAIL_START:
909 group->chosen_tail_state[position] = CHOSEN_TAIL_WIP;
910 __attribute__ ((fallthrough)); /* drop through */
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
924 simple_tail_expr(chosen_tail->simple_tail),
925 -(group->pretty_width_ct[position]), /* minimum field width */
926 chosen_tail->value_qq
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] );
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));
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));
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;
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
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
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 );
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 );
988 group->chosen_tail_state[position] = CHOSEN_TAIL_PLUS_FIRST_TAIL_WIP;
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)
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)
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)
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)
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;
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
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
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
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
1059 case NO_CHILD_NODES:
1061 printf("[*]"); /* /head/label with no child nodes */
1066 printf("[ %s=%s ]", simple_tail_expr(chosen_tail->simple_tail),chosen_tail->value_qq);
1070 static void output_path(struct augeas_path_value *path_value_seg) {
1071 struct path_segment *ps_ptr;
1073 for( ps_ptr=path_value_seg->segments; ps_ptr != NULL; ps_ptr=ps_ptr->next) {
1074 output_segment(ps_ptr, path_value_seg);
1076 if( path_value_seg->value_qq != NULL ) {
1077 printf(" %s\n", path_value_seg->value_qq);
1083 static void output(void) {
1084 int ndx; /* index to matches() */
1085 struct augeas_path_value *path_value_seg;
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' )
1093 if ( value == NULL )
1094 fprintf(stdout,"# %s\n", path_value_seg->path);
1096 fprintf(stdout,"# %s %s\n", path_value_seg->path, path_value_seg->value_qq);
1098 /* weed out null paths here, eg
1100 * /head/123/tail (null)
1102 * ie. if value==NULL AND this node has child nodes
1103 * does not apply if there is no
1106 if ( value == NULL && ndx < num_matched-1 ) {
1107 if(str_ischild(all_augeas_paths[ndx]->path, all_augeas_paths[ndx+1]->path)) {
1111 output_path(path_value_seg);
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 )
1121 /* New group, put in a newline for visual seperation */
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
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;
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;
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;
1175 first_tail->value_re = regexp_value( first_tail->value, max_re_width_ft );
1178 } /* for position 1..max_position */
1181 static void choose_pretty_width(struct group *group) {
1182 unsigned int position;
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;
1189 pretty_tail = group->chosen_tail[position];
1192 value_len = pretty_tail->value_re == NULL ? 0 : strlen(pretty_tail->value_re);
1194 value_len = pretty_tail->value_qq == NULL ? 0 : strlen(pretty_tail->value_qq);
1196 group->pretty_width_ct[position] = value_len;
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);
1210 group->pretty_width_ct[pos_search] = max_width; /* so we can start at position+1 */
1213 max_width = MIN(max_width,MAX_PRETTY_WIDTH);
1214 group->pretty_width_ct[position] = max_width;
1215 } /* for position 1..max_position */
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()
1231 group->first_tail[position] = find_first_tail(group->tails_at_position[position]);
1233 for(position=1; position<=group->max_position; position++) {
1234 group->chosen_tail[position] = choose_tail(group, position);
1237 choose_re_width(group);
1240 choose_pretty_width(group);
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
1249 static char *quote_value(char *value) {
1250 char *s, *t, *value_qq, quote;
1259 for(s = value, len=0; *s; s++, len++) {
1261 case '"': has_qq++; break;
1262 case '\'': has_q++; break;
1268 has_special++; break;
1278 /* Normal case, no single-quotes within the value */
1279 new_len = len+2+has_nl;
1281 } else if ( has_qq == 0 ) {
1282 new_len = len+2+has_nl;
1285 /* This needs a bugfix in augeas */
1286 new_len = len+2+has_q+has_nl;
1289 value_qq = malloc( sizeof(char) * ++new_len); /* don't forget the \0 */
1290 CHECK_OOM( ! value_qq, exit_oom, "in quote_value()");
1294 for(s = value; *s; s++, t++) {
1295 if ( *s == quote ) {
1299 } else if ( *s == '\n' ) {
1303 } else if ( *s == '\t' ) {
1307 } else if ( *s == '\\' ) {
1319 /* Create a quoted regular expression from the value, using single quotes if possible
1321 static char *regexp_value(char *value, int max_len) {
1322 char *s, *t, *value_re, quote;
1331 for(s = value, len=0; *s; s++, len++) {
1333 case '"': has_qq++; break;
1334 case '\'': has_q++; break;
1345 has_special++; break;
1350 has_special+=2; break;
1355 len++; /* don't forget the \0 */
1357 /* Normal case, no single-quotes within the value */
1358 new_len = len+2+has_nl+has_special*2;
1360 } else if ( has_qq == 0 ) {
1361 new_len = len+2+has_nl+has_special*2;
1364 /* This needs a bugfix in augeas */
1365 new_len = len+2+has_q+has_nl+has_special*2;
1368 value_re = malloc( sizeof(char) * new_len);
1369 CHECK_OOM( ! value_re, exit_oom, "in regexp_value()");
1373 for(s = value; *s; s++, t++) {
1374 if ( *s == quote ) {
1378 } else if ( *s == '\n' ) {
1382 } else if ( *s == '\t' ) {
1386 } else if ( *s == '\\' || *s == ']' ) {
1391 /* Special handling for ] */
1411 break; /* already dealt with above */
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 */
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");
1458 static void print_version_info(const char *progname) {
1459 const char *version;
1462 r = aug_get(aug, "/augeas/version", &version);
1466 fprintf(stderr, "%s %s <https://augeas.net/>\n", progname, version);
1469 fprintf(stderr, "Something went terribly wrong internally - please file a bug\n");
1472 int main(int argc, char **argv) {
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() */
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 */
1496 opt = getopt_long(argc, argv, "vdhVl:sSr::pt:", long_options, &option_index);
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");
1507 if (strcmp(long_options[option_index].name, "lens") == 0 ) {
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);
1516 } else if (strcmp(long_options[option_index].name, "regexp") == 0) {
1518 int optarg_int = strtol(optarg, NULL, 0);
1520 use_regexp = optarg_int;
1521 /* else use the default 1 set by getopt() */
1539 fprintf(stderr,"option d with value '%s'\n", optarg);
1549 flags |= AUG_NO_MODL_AUTOLOAD;
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);
1560 int optarg_int = strtol(optarg, NULL, 0);
1561 use_regexp = optarg_int > 0 ? optarg_int : 8;
1563 use_regexp = use_regexp ? use_regexp : 8;
1566 case '?': /* unknown option */
1570 fprintf(stderr,"?? getopt returned character code 0x%x ??\n", opt);
1575 usage(program_name);
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);
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];
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);
1594 fprintf(stderr,"non-option ARGV-elements: ");
1595 while (optind < argc)
1596 fprintf(stderr,"%s ", argv[optind++]);
1597 fprintf(stderr,"\n");
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);
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);
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);
1620 inputfile_real = inputfile;
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));
1627 aug = aug_init(NULL, loadpath, flags|AUG_NO_ERR_CLOSE|AUG_NO_LOAD);
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);
1634 if ( lens != NULL ) {
1635 /* Explict lens given, or inferred from --target */
1637 if ( aug_transform(aug, lens, inputfile, 0) != 0 ) {
1638 fprintf(stderr, "%s\n", aug_error_details(aug));
1641 if ( target_file ) {
1642 filename = target_file;
1644 filename = inputfile;
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);
1651 /* --lens not specified, print the default lens as a comment if --verbose specified */
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);
1659 if ( aug_load_file(aug, inputfile) != 0 || aug_error_details(aug) != NULL ) {
1661 fprintf(stderr, "%s: Failed to load file %s\n", program_name, inputfile);
1662 msg = aug_error_details(aug);
1664 fprintf(stderr,"%s\n",msg);
1666 msg = aug_error_message(aug);
1668 fprintf(stderr,"%s\n",msg);
1669 msg = aug_error_minor_message(aug);
1671 fprintf(stderr,"%s\n",msg);
1676 if ( target_file ) {
1677 /* Rename the tree from inputfile to target_file, if specified */
1678 move_tree(inputfile, target_file);
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 ) {
1687 lens = find_lens_for_path(inputfile);
1688 fprintf(stderr,"%s: Failed to parse file %s using lens %s\n", program_name, inputfile, lens);
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);
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]);